[开源] 审计日志说自己没被改过——这算不算自证清白?

ryan-wong 2026-07-21 19:02 1

本帖使用社区开源推广,符合推广要求。我申明并遵循社区要求的以下内容:



  • 我的帖子已经打上 开源推广 标签:

  • 我的开源项目完整开源,无未开源部分:

  • 我的开源项目已链接认可 LINUX DO 社区:

  • 我帖子内的项目介绍,AI生成、润色内容部分已截图发出:

  • 以上选择我承诺是永久有效的,接受社区和佬友监督:


以下为项目介绍正文内容,AI生成、润色内容已使用截图方式发出




在全都是ai项目的开源社区中,我做了一个这样的开源软件。 我不擅长写推广贴,但是有推广之心,大家也可以把这个看成技术分享。


老板问:



昨晚谁改了生产权限?



运维查了一遍:



审计日志里没有这条操作。



老板又问:



审计日志存在哪?




我们数据库里。




谁能改?




还是我们。



这就有点尴尬了。


审计日志如果是内部审计肯定没问题,但真出了事故,仅仅拿出一张自己保管的日志表,很难说服另一方:



  • 你怎么证明这条记录不是后来补的?

  • 又怎么证明原来的记录没有被删掉或换掉?

  • 掌握日志的人,能不能同时改日志?


TrustDB 是我对这个问题的一次尝试。


它做的是把已经产生的关键审计记录,变成一条可以独立验证的证据链。


项目地址:GitHub - ryan-wong-coder/trustdb: 高可信的存证系统 · GitHub

官网:https://www.trustdb.ryan-wong.cn/


一个数据有没有变动,不能只告诉第三方一句"请相信我们"


一条比较完整的审计记录,通常会写明:



  • 谁执行了操作

  • 操作了什么对象

  • 执行了什么动作

  • 操作前后的状态摘要

  • 请求编号或任务编号

  • 记录产生时间

  • 业务附加信息


这些内容可以来自业务系统、网关、发布平台或独立审计代理。


TrustDB 首先计算记录正文的 SHA-256,再把内容摘要、长度、客户端身份、密钥编号、事件类型、时间、随机数和元数据组成一份客户端声明。


接下来由审计记录的产生方签名。


input =
"trustdb.client-claim.v1"
|| 0x00
|| canonical_cbor(claim)

signature = Ed25519.Sign(client_private_key, input)

这里使用确定性 CBOR,而不是随手生成的 JSON。


因为数字签名面对的是字节,不是"看起来差不多的意思"。JSON 字段顺序、空格和数字写法不同,都会产生不同字节。确定性 CBOR 把编码固定下来,让签名方和验证方得到完全相同的输入。


解码时还会拒绝重复字段、未知字段、CBOR tag、不定长编码、NaN、无穷大和尾随数据。


做这么多主要是怕把两个解析器把同一段数据理解成不同意思。


为什么签名前要写一串固定文字


TrustDB 的客户端声明、服务端回执和全局日志树头,都使用 Ed25519 签名,但它们不能共用同一种签名语义。


所以每类数据都有自己的签名域:



  • trustdb.client-claim.v1

  • trustdb.accepted-receipt.v1

  • trustdb.committed-receipt.v1

  • trustdb.signed-tree-head.v1


客户端声明只能当客户端声明验证,接受回执只能当接受回执验证。


不能拿着一份合法的客户端签名,换个外壳说它是服务端回执;也不能把旧格式里的签名塞进新格式继续使用。


记录编号同样绑定了声明和客户端签名:


record_id =
SHA-256(
"trustdb.record-id.v1"
|| 0x00
|| claim_cbor
|| client_signature
)

因此,两条正文相同的审计记录,只要产生方、时间、随机数或签名不同,就会得到不同编号。


内容相同,不代表是同一次操作。


客户端签名,只能证明"记录是谁生成的"


客户端签名能够证明:



持有对应私钥的一方,签过这条审计声明。



但它不能证明 TrustDB 服务端确实收到。


所以服务端还会签发两种回执。


接受回执表示:



我验证了客户端签名,并且接受了这条记录。



提交回执表示:



这条记录已经进入某个 Merkle 批次,位置和批次根如下。



"收到"和"已经进入可验证批次"被明确分开。


如果客户端声称提交过,服务端却说没收到,可以检查接受回执;如果服务端声称记录已经入账,可以继续检查提交回执和 Merkle 证明。


这比查一列 status=success 麻烦,但数据库里的状态可以被管理员直接修改,签名回执却不能在没有私钥的情况下重新生成。


Merkle 树让一条记录牵动整批记录


每条服务端记录先计算叶子哈希:


leaf = SHA-256(0x00 || canonical_cbor(server_record))

两个节点合并时:


node = SHA-256(0x01 || left || right)

0x000x01 用来区分叶子与内部节点,避免两种对象互相冒充。


一条记录进入批次后,会得到一条通往批次根的证明路径。


验证器重新计算这条路径,就能检查:



这条审计记录是否真的属于这个批次根。



有人修改记录正文,内容哈希会变;客户端声明和记录编号随之变化;Merkle 叶子、路径和批次根也会跟着变化。


想只改一条审计记录,却保持原来的批次根不变,需要找到 SHA-256 碰撞。


这已经不是改一行数据库的问题了。


如果管理员把整批记录一起重做呢


只做一层 Merkle 树还不够。


服务器管理员可以丢掉旧批次,重新生成一批记录,再拿出一个新的批次根。


因此 TrustDB 会把每个批次根继续写入全局透明日志。


全局日志本身也是一棵持续增长的 Merkle 树。每次追加后,服务端都会生成带签名的树头 STH,里面有:



  • 当前树的大小;

  • 当前根哈希;

  • 日志编号;

  • 节点编号;

  • 生成时间;

  • 服务端签名。


这里有两种证明。


包含证明回答:



这批审计记录是否真的进入了全局日志?



一致性证明回答:



今天的全局日志,是否由昨天的日志继续追加得到?



假设昨天有一万个批次,今天有一万零三个。一致性证明检查的不是"今天这棵树能不能算通",而是昨天那一万个批次有没有被原样保留下来。


这样就不能趁夜里换掉一段历史,再重新生成一棵结构正确的新树。


如果服务端给不同的人看不同历史呢


透明日志仍然可能出现分裂视图。


管理员给甲看一份 STH,给乙看另一份 STH,两边单独验证都能通过,但看到的历史不同。


所以,TrustDB 可以把 STH 的全局根继续提交给 OpenTimestamps。


提交出去的只是一个 32 字节哈希,不包含审计记录正文,也无法从中还原业务数据。


证明关系变成:


审计记录
↓ SHA-256
客户端声明
↓ 客户端 Ed25519 签名
服务端回执
↓ 批次 Merkle 证明
批次根
↓ 全局透明日志
签名 STH
↓ OpenTimestamps
外部时间见证

OpenTimestamps 日历服务器会先返回待确认的时间戳证明,后续还可以升级到比特币区块见证。


它能帮助证明的是:



与这条审计记录相连的全局根,最迟在某个时间以前已经存在。



它不能证明审计内容本身符合事实,也不表示审计记录正文被上传到了外部网站。


验证器不会相信证明文件自报家门


TrustDB 可以把客户端声明、服务端回执、Merkle 路径、全局日志证明、STH 和外部锚定放进一个 .sproof 文件。


文件里虽然有 proof_level,但验证器不会因为它写着 L5,就直接认定达到 L5。


验证时会重新计算:



  • 审计记录正文的长度和 SHA-256;

  • 客户端声明的确定性 CBOR;

  • 客户端 Ed25519 签名;

  • 记录编号;

  • 服务端接受回执;

  • 服务端提交回执;

  • Merkle 叶子和批次根;

  • 批次根到全局 STH 的包含证明;

  • STH 的服务端签名;

  • 外部锚定与 STH 根是否一致。


能算到哪一层,才算达到哪一级。


证明文件说自己是 L5,和嫌疑人说自己无罪一样,只能作为提示,不能作为结论。


它能防事后改账,不能防一开始就不记账


这是审计场景里必须说明的边界。


如果某次操作从一开始就没有进入审计采集端,TrustDB 不可能凭空知道它发生过。


如果审计客户端私钥已经被控制,攻击者也可以签署新的虚假记录。


如果业务系统把错误的操作者身份交给审计端,密码学只会忠实地保护这个错误身份。


TrustDB 主要解决的是:



一条审计记录一旦被签名、接受并进入证明链,后来还能不能悄悄修改或替换。



至于"是否每次操作都生成了审计记录",仍要依靠独立采集、连续序号、权限隔离和业务制度。


一个防改,一个防漏,不是一回事。


最后


TrustDB 目前使用:



  • SHA-256;

  • Ed25519;

  • Core Deterministic CBOR;

  • RFC 6962 风格 Merkle 树;

  • 签名树头;

  • 包含证明和一致性证明;

  • OpenTimestamps 外部锚定;

  • 可离线验证的 .sproof v1。


代码、证明格式和测试向量全部公开。


如果佬友们做过操作审计、发布审计、权限变更审计、数据流通记录或者密码学、区块链研究,欢迎从实际情况里挑问题,提issue


注:使用大模型从邮件中复制后重新md格式,当前版本无润色内容(确信)

最新回复 (8)
  • Ys Ltr 07-21 19:05
    1

    只看了前面欸,但是区块链技术不就能解决防篡改的问题吗。是不是某些旧技术完全失传了

  • ryan-wong 楼主 07-21 19:07
    2

    区块链有成本和性能问题,和日志的短时大量输出存在张力

    此项目最后依然应用了区块链作为外置挂载证据,但是大幅压缩了上链篇幅

  • 过期绿坝粮 07-21 19:18
    3

    简单来说就是区块链签名?

  • ryan-wong 楼主 07-21 19:20
    4

    简单来说应该是上链前的高吞吐压缩,同时除了单独证明以外,还附带了一致性证明回答,证明自己是在一个连续的历史里进行的追加,而非孤立生成

  • ryan-wong 楼主 07-21 19:23
    5

    补充:单靠区块链,很难实现高吞吐的日志存证,此项目相当于是高度节省的链前压缩

  • airline233 07-21 20:07
    6

    听上去和这个是一样的

    6.43 复制打开抖音,看看【老头们的快乐生活的作品】王处祝大家六一快乐 你们就说王处这个大春链,有没有… https://v.douyin.com/RA0LeBNBGrU/ [email protected] mQX:/ 06/15 :1pm

    https://www.douyin.com/video/7646082737879902705

    人家还更节约算力,只算md5而非SHA256

  • ryan-wong 楼主 07-21 21:09
    7

    那到不是,因为merkle tree保留了完整的证明语义,具体证明语义可以看readme,其实merkle tree压缩后上链是很多存证系统成熟的实现方案,但是我进一步整理了证明语义,并且将上链调用压缩到固定时间窗口常数级,这个可以省下大量开销

  • ryan-wong 楼主 07-21 21:12
    8

    我可以尽可能的善意回复你,但是你的这个视频蛮有侮辱性质的

* 帖子来源Linux.do
返回