3. 数字签名原理:签名生成流程、签名验证流程、RSA签名算法详解

数字签名,说白了就是现实世界里「盖章签字」在网络世界的翻版。你想想看,一份纸质合同,我签了名、盖了章,对方就能确认这确实是我干的,而且内容没被人改过。数字签名干的就是同样的事——只不过用的是数学,不是印泥。

我记得刚入行那会儿,总觉得数字签名是个很玄乎的东西。后来亲手实现了一遍RSA签名,才发现核心逻辑其实很清晰。今天咱们就把这块彻底掰开揉碎。

3.1 签名生成流程:私钥才是你的「私章」

签名生成,说白了就是「用私钥给消息打个标记」。这个标记只有持有私钥的人能打出来,别人想仿造?几乎不可能。

流程其实就三步:

  1. 对原始消息做哈希运算——把任意长度的消息,压缩成一个固定长度的「摘要」。
  2. 用私钥加密这个摘要——这就是签名的核心操作。
  3. 把原始消息和签名一起发给对方——注意,消息本身是明文的,不需要加密。

关键点:签名不是加密整个消息,而是加密消息的哈希值。这样做有两个好处:一是速度快(哈希很短),二是安全性高(哈希不可逆)。

我在项目中遇到过有人问:「既然私钥加密了摘要,那公钥解密后得到的不就是摘要吗?这不就等于加密传输了吗?」嗯,这里要区分清楚——签名不是为了隐藏内容,而是为了证明「这个摘要确实来自私钥持有者」。说白了,签名是「身份证明」,不是「保密信封」。

3.2 签名验证流程:公钥就是你的「验章机」

验证流程更简单,也是三步:

  1. 用公钥解密签名——得到发送方声称的「摘要A」。
  2. 对收到的原始消息做同样的哈希运算——得到「摘要B」。
  3. 比较摘要A和摘要B——如果一致,签名有效;否则无效。

为什么会这样?因为只有持有私钥的人,才能生成一个「用公钥能解密出正确摘要」的签名。如果消息被篡改过,哈希值就会变,比对自然失败。

个人经验:我曾经调试过一个签名验证失败的问题,折腾了半天,最后发现是发送方和接收方用的哈希算法不一样——一个用了SHA-256,另一个用了SHA-1。这种坑,你想想看,光看代码根本看不出来,必须两边对齐算法参数。

3.3 RSA签名算法详解

RSA签名,其实就是RSA加密算法在签名场景下的应用。不过要注意:签名用的是私钥,验证用的是公钥——这和加密正好反过来。

3.3.1 RSA密钥对生成

RSA的密钥对生成,核心是依赖大整数分解的数学难题。我简单说一下流程:

步骤 操作 说明
1 选择两个大素数 p 和 q 通常要求 1024 位以上,p 和 q 长度相近
2 计算 n = p × q n 就是公钥和私钥都会用到的模数
3 计算 φ(n) = (p-1)(q-1) 欧拉函数值,用于后续计算
4 选择公钥指数 e 常用值 65537,要求 1 < e < φ(n) 且 gcd(e, φ(n)) = 1
5 计算私钥指数 d d ≡ e⁻¹ mod φ(n),即 e 关于 φ(n) 的模逆元

最终:公钥 = (n, e),私钥 = (n, d)。p、q、φ(n) 必须销毁或严格保密。

3.3.2 RSA签名与验证的数学表达

用公式表示,其实特别简洁:

签名:signature = hash(message) ^ d mod n
验证:hash' = signature ^ e mod n
比较:hash' == hash(message) ? 有效 : 无效

你想想看,这里面的数学原理其实很巧妙。因为 d 和 e 是互为逆元的,所以:

(hash ^ d) ^ e ≡ hash ^ (d × e) ≡ hash (mod n)

也就是说,用私钥签名后再用公钥验证,一定能还原出原始的哈希值。而其他人没有私钥 d,根本算不出正确的签名。

避坑指南:我曾经见过一个项目,直接拿RSA加密整个消息当签名用。这样做有两个问题:一是性能极差(RSA加密长消息很慢),二是存在安全风险(某些攻击场景下会泄露信息)。记住:签名永远是对哈希值操作,不是对原始消息操作。

3.3.3 实际应用中的填充方案

直接对哈希值做RSA运算是不安全的。为什么?因为如果哈希值太短(比如SHA-256只有256位),攻击者可以通过数学变换伪造签名。所以实际应用中必须加填充。

目前主流方案有两种:

方案 特点 推荐场景
PKCS#1 v1.5 兼容性好,实现简单 旧系统兼容,但新系统不推荐
PSS (Probabilistic Signature Scheme) 安全性更高,有严格证明 新系统首选,推荐使用

我个人的习惯:新项目一律用PSS填充。虽然实现上比PKCS#1 v1.5多几行代码,但安全性有数学证明保障。我在一个金融项目中吃过PKCS#1 v1.5的亏——某些攻击工具专门针对它做选择密文攻击。从那以后,我再也不敢偷懒了。

3.4 核心流程可视化

下面这张图,把签名和验证的完整流程画清楚了。我建议你多看几遍,把每一步的输入输出都理清楚。

数字签名完整流程(RSA) 原始消息 M 哈希运算 (SHA-256) 消息摘要 H(M) 私钥加密 (RSA) 签名 S 原始消息 M 哈希运算 (SHA-256) 摘要B 公钥解密 (RSA) 摘要A 传输:M + S(消息和签名一起发送) 比对:摘要A == 摘要B? 一致 → 签名有效;不一致 → 签名无效(消息被篡改或身份伪造)

3.5 常见误区与避坑

做签名开发这些年,我踩过不少坑。挑几个典型的说说:

  • 哈希算法不匹配:签名方用SHA-256,验证方用SHA-1,结果永远对不上。我建议在协议设计阶段就把算法固定下来,写在文档里。
  • 填充方案不一致:一方用PSS,另一方用PKCS#1 v1.5,验证必败。而且这两种填充的输出长度可能不同,会导致更隐蔽的问题。
  • 密钥长度太短:1024位RSA现在已经不安全了。我个人的底线是2048位,敏感场景建议4096位。
  • 签名和加密混用:有人觉得「签名就是加密的反向操作」,于是用同一对密钥既做签名又做加密。这是大忌——会引入严重的安全漏洞。

曾经踩过的坑:有一次,我在对接第三方支付接口时,对方文档里写的是「RSA签名」,但实际用的是「RSA加密整个报文」。结果两边联调了三天,最后发现是对方把签名和加密搞混了。从那以后,我每次对接都会先确认:你们用的是标准签名流程吗?哈希+私钥加密?还是直接加密整个消息?