密码学 18:mTLS 与 TLS 终止后,客户端、代理和后端分别信谁
客户端通过 HTTPS 把合成订单发给服务,服务器不仅给出证书,还要求客户端也出示证书,握手成功,于是配置里写了“已启用 mTLS”。如果 TLS 实际终止在反向代理,而代理使用 HTTP 明文把订单送到后端,哪一段受 mTLS 保护?后端的业务程序究竟在信任客户端、代理,还是某个随请求转来的 X-Client-Name 字符串?把“开了 mTLS”写成“整个链路端到端机密、用户订单授权正确”并不严谨。 本篇用回环地址的三个真实组件验证第一层事实:客户端与代理做 TLS 1.3 双向证书认证,代理收到已验证客户端证书,后端却是另起的一条明文 HTTP 连接。没有安装服务网格,后端的订单也没有做真实权限检查;实验只支持连接段和证书验证对象的判断。 三个端点,两段连接 假设客户端拿着一张由测试 CA 签发、扩展用途为 clientAuth 的合成证书;代理有另外一张 serverAuth 的 orders.test 服务器证书。客户端的 TLS 验证器把测试 CA 明确加载到本次上下文并检查服务器名;代理单独加载这张测试 CA 并要求客户端证书。签发者的 CA 私钥只用于发两张短期测...
密码学 17:恢复连接、PSK 与 0-RTT 省掉了什么
一次合成订单访问结束,浏览器很快又连同一服务。TLS 1.3 可以利用旧连接留下的恢复材料,避免每次都完整重复此前的证书握手;更激进的做法是在服务端回应本次 ClientHello 前发送应用字节。查询商品和创建付款订单不能因为都叫“HTTPS 请求”就无差别提前。恢复、PSK-only、PSK-DHE、0-RTT指向不同决策:旧秘密是否被复用,本次是否加入新的临时 DH,以及握手确认之前是否让业务处理早期消息。 本篇实测了一次 TLS 1.3 普通握手,再借内存里的票据成功恢复一次 PSK-DHE 握手;环境没有真实运行 0-RTT 或 PSK-only。没跑过的分支只据 RFC 9846 讨论规则,仍留作 NOT_RUN。 票据把上一轮带到了下一轮 第一次证书认证路径已把预期 orders.test 的身份与本次握手绑定。握手后,服务端可发 NewSessionTicket;客户端保存与票据相关的恢复材料。下一次 ClientHello 提供相应的 pre_shared_key 身份,服务端若接受它,双方才可用先前握手派生的 PSK 引导新的密钥协商。这里的预共享密钥并非把原...
密码学 16:TLS 怎样保护每条记录
15 的握手证实客户端连到了预期的 TLS 服务端并建立共享秘密。接下来的合成订单不是把证书公钥放在每一条 HTTP 请求前,重新签一遍;两端改用各自方向的应用流量密钥,给一条条 TLS 记录做认证加密。为什么记录还需要序号?如果攻击者复制刚才发送的那一条加密记录,再发一次,接收端能否分辨它不是下一条?TLS 记录的复制重放与有权客户端重新提交一笔相同订单是两个不同问题。 本篇实际在 Python/OpenSSL 的 TLS 1.3 MemoryBIO 端点之间截取一份应用数据的受保护记录,分别试正常接收、对端回发相同明文、重复投递同一加密记录以及翻转一位加密记录。它是成熟 TLS 库在内存里的真实协议组件实验;并没有提取/公开会话秘密、手写 TLS 记录格式或自行实现 AES-GCM。 从一份秘密派生多条路 10 的密钥交换得到共同输入,11 的 HKDF 说明可以按上下文派生分用途输出。TLS 1.3 按其自己的派生树取得客户端方向与服务器方向的应用流量秘密,进一步计算各方向的记录密钥和静态 IV。即便两个方向传送一模一样的订单字节,参与认证加密的密钥、记录序号以及上下文也不...
密码学 15:TLS 1.3 握手逐步解释
浏览器连接 orders.test,握手里既出现两端的临时 key share,又出现服务器证书、CertificateVerify 和 Finished。如果证书已经写了公钥和名称,为什么还要后两步?如果 CertificateVerify 是签名,为什么还要 Finished?把这些消息统称为“TLS 验证一下”会掩盖三种攻击入口:服务名冒充、这次 key share 被替换、双方对握手与密钥的理解不一致。本文沿一次证书认证的 TLS 1.3 握手,给每个对象写清谁持有秘密、输入输出和删掉之后的反例。 规范基线是 RFC 9846:它在 2026 年取代 RFC 8446,并重排了部分章节。真实实验的 OpenSSL 3.0.13 发布早于它,所以本机协商到 TLS 1.3、出现规范要求的消息,不等于宣称已通过 RFC 9846 每项修订的实现符合性测试。 第一步:协商需要什么而不是发一把密钥 客户端自己预期访问 orders.test,并持有一次性测试 CA 的可信证书;服务器有叶子证书及对应私钥。两端为本次会话准备临时交换秘密与相应公开值,ClientHello、Serv...
密码学 14:证书为什么能绑定服务名与公钥
有人给浏览器发来一把公钥,说“我是 orders.test”。浏览器用它验证同一人送来的签名,结果当然可能成功。13 已经说明:**签名能证明私钥持有人参与了那次操作,却不能只靠自报的公钥确定持有人叫什么。**网站身份校验必须从浏览器自身预期访问的名称和事先配置的可信根出发,而不能让连接对面临时决定信任起点。 本篇用临时合成 CA 给 orders.test 签发一张测试证书,只在本机回环地址启动 TLS 1.3 服务端。客户端明确加载这一张 CA,匹配 DNS 名称后提交一笔合成订单;另试“名称不符”和“客户端没有这张 CA”两个失败路径。这里跑到了真实 OpenSSL/Python TLS 端点,但不是浏览器实验,更不把 CA 装进系统信任库。 验证者有一个先于网络对话的预期 TLS 连接发往哪个 IP 与客户端要认证哪个服务是两个不同输入。测试里套接字连的是 127.0.0.1 的随机端口,但客户端指定的服务名称是 orders.test。证书的 subjectAltName(SAN)包含 DNS 名称 orders.test;用于验证这张证书的测试 CA 只交给本次客户端...
密码学 13:正确公钥从哪里来
浏览器连上一个声称是 orders.test 的服务,服务把自己的公钥和对某段数据的签名一起发来。浏览器用收到的公钥验证,返回真。现在可以提交订单吗?还不可以:攻击者完全可以自己生成公私钥,给自己编的一段消息签名,再附上自己的公钥。算法做对了,身份却是假的。10 篇说明 DH 可以和中间人分别建立共同秘密;12 篇说明签名可以被正确公钥验证。要把两种能力组合为“与预期服务安全通信”,先得知道应当信任哪个公钥,并确认它签的是这一次、这个角色、这段通信。 本篇用事先固定的合成公钥模拟“浏览器已有可信起点”,再演示恶意自签、错误服务名、换 key share 与重放。固定公钥只是教学前提,不是现实世界的信任来源;14 的证书链才真正回答 HTTPS 的服务名与公钥如何绑定。 相同签名算法,两种截然不同的输入 客户端看到“消息 + 签名 + 公钥”时,至少要决定三个独立输入:希望连接的服务名称,从可信配置、证书链或别的认证制度拿到的预期公钥或公钥约束,以及要验证的确切消息字节。如果把公钥信任和消息验证都留给发件者随意指定,验证会变成自我背书。 12345678本地预期服务 orders....
密码学 12:数字签名验证了什么
钱包要给节点一个授权证据:节点必须能独立检查“这组明确字节经某个有权的私钥持有人签署”,却不应该因此获得生成新授权的能力。04 篇的 HMAC 做不到后一条,因为接收者也持有同一把密钥。数字签名提供另一种分工:私钥参与生成签名,公钥参与验证签名。但是验签函数返回 true 仍有至少两个问题悬而未决:这个公钥属于谁?这段被签的字节是否就是需要授权的那件事? 这篇用成熟库分别运行 RSA-PSS、P-256 上的 ECDSA 与 Ed25519 的正负验签,严格区分“原语实验通过”和“业务或链上的授权已经生效”。 三个输入,不是一句“签过了” 发送者在既定算法与编码约定下对确定字节生成签名;验证者必须同时拥有消息字节、签名和可信来源的正确公钥,还要知道怎样解码签名与选择算法参数。对恶意者自造密钥并签署的消息,verify(恶意者的公钥, 消息, 签名) 也能返回真,不能因此推断发件人是合法钱包主人或预期网站。 1234567甲:私钥(不得外发) + 确定的消息字节 -> 签名 | | ...
密码学 11:一个秘密为什么派生多把密钥
10 篇里,两个临时密钥交换参与者算到同一份秘密。接下来的订单请求和服务端响应,能不能把这串值直接拿来当一把万能密钥,既负责客户端发送又负责服务端发送,未来连请求认证也复用?不该这样做。不同方向、阶段与用途需要隔离:一边的密钥、nonce 进度或使用权限变化,不应无端变成另一边的隐性输入。派生密钥是从一份已经足够不可猜测的输入得到明确用途的多份输出,不是把弱密码凭空升级成强秘密。 这篇用 HKDF 的标准向量验证算法输出,再对一份进程内临时秘密分别派生客户端订单、服务端订单和 RPC 用途密钥。不打印实际临时秘密与派生密钥;公开的十六进制输出仅限 RFC 官方测试向量。 提取与扩展各自输入什么 HKDF 使用 HMAC 构成两步:先对输入密钥材料 IKM 与 salt 进行提取,得到伪随机密钥 PRK;再以 PRK、表示上下文和用途的 info、指定输出长度 L 做扩展,得到所需密钥材料。接口可以简写如下,但真实调用一定要给出字节级参数: 123456789101112 已有秘密 IKM + salt | v HKDF-Ex...
密码学 10:两端如何得到共同秘密,为什么仍怕中间人
浏览器与 HTTPS 端点第一次见面,却能协商供双方使用的流量密钥。是不是把这把密钥直接从网络上发过去了?不是。09 用小整数说明:双方交换各自算出的公开值,再用自己没有外发的私有输入,最终可以得到同一个结果。问题在于:没有人核对交换来的公开值到底属于谁,攻击者也能和两边分别完成运算。本篇将“共同秘密已算出”和“对端身份已确认”拆成两个各自可失败的断言。 这里会用成熟库执行真实 X25519 椭圆曲线密钥交换,但不运行 TLS 端点。真实库结果仅证明在本次输入下原语计算正确;服务名、证书、握手摘要与通信保护要在 13–16 章继续组合。 正常路线:线上传的不是最终秘密 仍用 09 的手算:公开模数 23、生成元 5,甲有私有指数 6,乙有私有指数 15。甲发公开 share 8,乙发公开 share 19;两人将对方公开 share 与自己的私有指数结合,分别算到结果 2。 123456甲 可被旁观的网络 乙私有 6 -> 发送 8 ---------> ...
密码学 09:模运算、群与困难问题需要多少数学
浏览器和 HTTPS 服务端没在线上发一把共同密钥,却能算出一把;钱包用私钥生成签名,节点只拿公钥就能验证。这些关系看着像“颠倒运算方向”,但若先读完一本数论教材才能解释,读者很可能忘了它们解决的原始问题:不安全链路上怎样形成共同秘密,或怎样让不知道私钥的人验证授权。这篇只补能看懂 DH、RSA、椭圆曲线责任分工的数学,不把小整数游戏说成生产密码学。 在本章所有例子中,数字刻意小得能手算;所谓“私有指数”也写在公开文章里。没有任何保密性承诺。真实参数、消息编码、身份验证和侧信道还要另行处理。 模运算是按一个固定范围计算 29 % 23 == 6:每次计算只保留除以 23 后的余数。Python 可直接执行 pow(5, 6, 23),意思是 5 的 6 次方再对 23 取余,不必先真的造一个巨大整数。许多密码学结构需要在这样的有限集合中计算。这里最重要的不是记定义,而是画出什么信息公开,什么输入暂不公开。 12345678公开数字(模数、运算规则) + 私有数字(某人的指数) | v 运算后输...









