盲签名算法原理:从 Chaum 原始方案到 SM2 盲签名的数学构造与安全分析
概述
盲签名(Blind Signature)是一种特殊的数字签名协议,由 David Chaum 于 1982 年首次提出,其核心特性是签名者对消息内容不可见,且签名完成后无法将签名与签名过程关联。这一性质使其成为电子现金、匿名投票、匿名凭证等隐私保护应用的基础构件。
与传统数字签名相比,盲签名在签名流程中引入了"盲化"(blinding)步骤:消息持有者在发送消息前先用随机因子对消息进行变换,签名者对变换后的盲消息进行签名,消息持有者再通过"去盲"(unblinding)操作恢复出对原始消息的有效签名。整个过程中,签名者既不知道消息内容,也无法在事后将签名结果与特定的签名会话关联。
本文从 RSA 盲签名的经典构造出发,深入解析其数学原理与安全证明,然后讨论 SM2 盲签名的设计思路和核心挑战(基于 GM/T 0003-2012 的 SM2 椭圆曲线参数),最后分析工程实现中的关键陷阱与典型应用场景。
数学基础:盲签名的形式化定义
1. 安全属性
一个安全的盲签名方案必须满足以下三个核心属性:
(1)不可伪造性(Unforgeability)
与标准数字签名相同,盲签名必须保证:不知道签名私钥的攻击者无法伪造有效签名。形式化定义与 EUF-CMA(Existential Unforgeability under Chosen Message Attack)一致。
(2)不可追踪性(Untraceability / Blindness)
这是盲签名独有的安全属性。形式化定义如下:
给定签名者对两个消息 $m_0, m_1$ 的签名结果 $\sigma_0, \sigma_1$,以及签名过程中产生的两个盲化消息 $\tilde{m}_0, \tilde{m}_1$ 及其对应的签名会话记录,签名者无法以优于随机猜测的概率判断哪个签名对应哪个原始消息。等价表述:对于任意两个消息 $m_0, m_1$ 和它们的签名 $\sigma_0, \sigma_1$,存在一个随机因子 $r$ 使得 $\sigma_0 = \text{Unblind}(\text{Sign}(\text{Blind}(m_0, r)))$ 且 $\sigma_1 = \text{Unblind}(\text{Sign}(\text{Blind}(m_1, r')))$,签名者无法区分 $(\sigma_0, \sigma_1)$ 与 $(m_0, m_1)$ 的对应关系。
(3)不可链接性(Unlinkability)
签名完成后,给定一个签名 $\sigma$ 和签名过程中的盲化消息 $\tilde{m}$,签名者无法判断 $\sigma$ 是否由 $\tilde{m}$ 签名后去盲得到。这是比不可追踪性更强的属性,要求即使签名者保存了所有签名会话的完整记录,也无法在事后建立关联。
2. 盲化-签名-去盲三步范式
所有盲签名方案都遵循统一的三步结构:
消息持有者 (User) 签名者 (Signer)
| |
| 1. Blind: m̃ = Blind(m, r) |
|------------------------------->|
| |
| 2. Sign: σ̃ = Sign(sk, m̃) |
|<-------------------------------|
| |
| 3. Unblind: σ = Unblind(σ̃, r)|
| |其中 $r$ 是消息持有者选择的随机盲化因子,$sk$ 是签名者私钥。最终 $\sigma$ 是对原始消息 $m$ 的有效签名,但签名者不知道 $m$ 的内容,也无法将 $\sigma$ 与签名会话关联。
RSA 盲签名:Chaum 的原始构造
1. 算法描述
RSA 盲签名基于 RSA 加密的同态性质:$(m_1 \cdot m_2)^d \equiv m_1^d \cdot m_2^d \pmod n$。
密钥生成:与标准 RSA 相同
- 选择两个大素数 $p, q$,计算 $n = p \cdot q$
- 选择公钥指数 $e$,计算私钥指数 $d \equiv e^{-1} \pmod{\phi(n)}$
- 公钥 $pk = (n, e)$,私钥 $sk = d$
签名: 签名者对盲消息计算签名: $$\tilde{\sigma} = \tilde{m}^d \pmod n$$
去盲: 消息持有者去除盲化因子: $$\sigma = \tilde{\sigma} \cdot r^{-1} \pmod n$$
正确性验证: $$\sigma = \tilde{\sigma} \cdot r^{-1} = (m \cdot r^e)^d \cdot r^{-1} = m^d \cdot r^{ed} \cdot r^{-1} = m^d \cdot r \cdot r^{-1} = m^d \pmod n$$
最终 $\sigma = m^d \pmod n$ 正是对原始消息 $m$ 的标准 RSA 签名。
2. 安全分析
不可追踪性证明(直觉版):
对于任意盲化消息 $\tilde{m}$ 和签名 $\tilde{\sigma}$,消息持有者可以选择不同的盲化因子 $r$ 来生成不同的 $(m, \sigma)$ 对。给定 $\tilde{m}$ 和 $\tilde{\sigma}$,对于任意消息 $m^*$,都存在 $r^* = \tilde{m} \cdot (m^*)^{-1} \pmod n$ 使得 $\tilde{m} = m^* \cdot (r^*)^e$。因此,签名者无法从 $\tilde{m}$ 推断出原始消息。
安全假设:
- RSA 问题的困难性:给定 $n, e$ 和 $c = m^e \pmod n$,计算 $m$ 是困难的
- 盲化因子 $r$ 的随机性:$r$ 必须从 $\mathbb{Z}_n^*$ 中均匀随机选择
3. 安全陷阱:参数重用攻击
关键陷阱:如果消息持有者在不同签名会话中重用同一个盲化因子 $r$,则签名者可以通过观察两个盲消息 $\tilde{m}_1 = m_1 \cdot r^e$ 和 $\tilde{m}_2 = m_2 \cdot r^e$ 计算 $\tilde{m}_1 / \tilde{m}_2 = m_1 / m_2$,从而推断消息之间的关系。
防护:每次签名会话必须生成独立的随机盲化因子。
SM2 盲签名:设计思路与核心挑战
1. 背景
SM2 椭圆曲线公钥密码算法系列标准(GM/T 0003-2012)定义了 SM2 数字签名算法(GB/T 32918.2-2016)。与 RSA 不同,SM2 签名不具备"乘法同态"性质,因此不能直接套用 Chaum 的构造方法。
重要说明:SM2 盲签名目前尚无正式国家标准,相关研究主要参考 ISO/IEC 14888-3(数字签名 第3部分:基于离散对数的机制)和 IEEE P1363.3 中的盲签名框架,并结合 SM2 特定参数进行适配。本节给出一个简化的教学模型,用于说明盲签名在椭圆曲线上的设计思路,实际生产应采用经过严格安全证明的方案。
2. SM2 与 RSA 在盲签名构造上的核心差异
| 特性 | RSA 盲签名 | SM2 盲签名(EC 方案) |
|---|---|---|
| 数学基础 | 模幂乘法同态 | 椭圆曲线标量乘法 |
| 盲化操作 | 模乘:$\tilde{m} = m \cdot r^e$ | 点加:$R = [r]G$,$\tilde{e} = e + x_1$ |
| 去盲操作 | 模乘:$\sigma = \tilde{\sigma} \cdot r^{-1}$ | 模减:$\sigma = \tilde{\sigma} - r$ |
| 签名输出 | 单个值 $\sigma$ | 通常包含椭圆曲线点 |
| 安全性 | 基于 RSA 问题 | 基于 ECDLP |
3. 简化的 EC 盲签名教学模型
以下构造是一个概念性模型,用于说明盲签名在椭圆曲线上的工作原理。注意:此模型简化了部分细节,并非生产可用方案。
算法参数(使用 SM2 推荐曲线参数):
| 参数 | 说明 | 值 |
|---|---|---|
| $E$ | 椭圆曲线 | $y^2 = x^3 + ax + b$ over $\mathbb{F}_p$ |
| $G$ | 基点 | $(x_G, y_G)$,阶为素数 $n$ |
| $n$ | 基点阶 | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123 |
| $d_A$ | 签名私钥 | 随机整数 $[1, n-1]$ |
| $Q_A$ | 签名公钥 | $Q_A = [d_A]G$ |
签名者生成 SM2 密钥对:
- 私钥:$d_A \in_R [1, n-1]$
- 公钥:$Q_A = [d_A]G$
- 选择随机盲化因子 $k \in_R [1, n-1]$
- 计算盲化点 $R = [k]G = (x_1, y_1)$
- 计算消息哈希:$e = \text{SM3}(m)$
- 计算盲化哈希:$\tilde{e} = e + x_1 \pmod n$
- 发送 $(\tilde{e}, R)$ 给签名者(注意:此处传递 $R$ 是构造需要,不同方案可能不同)
- 收到 $(\tilde{e}, R)$
- 选择随机数 $t \in_R [1, n-1]$
- 计算签名分量:$s = (d_A \cdot \tilde{e} + t) \pmod n$
- 发送 $s$ 给消息持有者
- 计算去盲签名:$\sigma = s - k \pmod n$
- 最终签名为 $(e, \sigma, R)$
验证者收到 $(e, \sigma, R)$,检查: $$[\sigma]G + [e]Q_A = R + [d_A \cdot \tilde{e} + t - k]G + e \cdot [d_A]G$$
教学说明:上述验证方程仅用于说明盲签名验证的一般结构。实际 SM2 盲签名方案中,验证方程的构造更为复杂,需确保既能验证签名有效性,又不泄露签名者身份。建议参考 ISO/IEC 14888-3 和学术文献(如 Pointcheval, Stern 等关于 EC 盲签名的论文)获取严格构造。
4. 与标准 SM2 签名的关系
标准 SM2 签名(GB/T 32918.2-2016)流程:
- 计算 $e = \text{SM3}(Z_A \| M)$
- 选择随机 $k$,计算 $[k]G = (x_1, y_1)$
- 计算 $r = (e + x_1) \pmod n$
- 计算 $s = ((1 + d_A)^{-1} \cdot (k - r \cdot d_A)) \pmod n$
- 签名为 $(r, s)$
- 盲化阶段由消息持有者引入随机因子,而非签名者
- 签名者不直接看到消息哈希 $e$,而是看到盲化后的 $\tilde{e}$
- 去盲阶段恢复出对原始消息的有效签名
- SM2 签名包含 $(r, s)$ 两个分量,盲签名构造需要更复杂的交互
工程实现与代码示例
1. Python 实现(基于 gmssl — 教学演示版)
以下代码演示盲签名的核心流程。注意:gmssl 3.2.x 不直接提供盲签名 API,本示例基于底层原语组合,用于教学演示而非生产使用。
import os
import hashlib
from gmssl import sm2, func
from gmssl.sm2 import default_ecc_table
# SM3 哈希函数
def sm3_hash(data: bytes) -> bytes:
"""计算 SM3 哈希值"""
return hashlib.new('sm3', data).digest()
# 椭圆曲线参数(SM2 推荐曲线)
ecc_table = default_ecc_table
n = int(ecc_table['n'], 16) # 基点阶
def generate_sm2_keypair():
"""生成 SM2 密钥对"""
# 生成随机私钥(32 字节 = 256 位)
d = int(func.random_hex(32), 16) % (n - 1) + 1
# 计算公钥 Q = [d]G
sm2_crypt = sm2.CryptSM2(
public_key=None,
private_key=None
)
G = (
int(ecc_table['g'][:len(ecc_table['g'])//2], 16),
int(ecc_table['g'][len(ecc_table['g'])//2:], 16)
)
Q = sm2_crypt._kg(d, G)
return d, Q
def blind_message(message: bytes, k: int, G: tuple) -> tuple:
"""
盲化消息
:param message: 原始消息
:param k: 随机盲化因子
:param G: 基点
:return: (盲化哈希 e_tilde, 盲化点 R)
"""
# 计算消息哈希
e = int.from_bytes(sm3_hash(message), 'big') % n
# 计算盲化点 R = [k]G
sm2_crypt = sm2.CryptSM2(public_key=None, private_key=None)
R = sm2_crypt._kg(k, G)
x1 = R[0]
# 计算盲化哈希
e_tilde = (e + x1) % n
return e_tilde, R
def sign_blind(e_tilde: int, d: int) -> int:
"""
对盲化消息签名(简化教学模型)
:param e_tilde: 盲化哈希
:param d: 签名私钥
:return: 签名分量 s
"""
# 简化构造:s = d * e_tilde + t
# 注意:此仅为教学演示,非标准 SM2 盲签名
t = int.from_bytes(os.urandom(32), 'big') % n
s = (d * e_tilde + t) % n
return s
def unblind_signature(s: int, k: int) -> int:
"""
去盲签名
:param s: 盲签名
:param k: 盲化因子
:return: 去盲后的签名 sigma
"""
sigma = (s - k) % n
return sigma
# 完整流程演示
def demo_blind_signature():
# 椭圆曲线基点
G = (
int(default_ecc_table['g'][:len(default_ecc_table['g'])//2], 16),
int(default_ecc_table['g'][len(default_ecc_table['g'])//2:], 16)
)
# 1. 签名者生成密钥对
d, Q = generate_sm2_keypair()
print(f"签名私钥 d: {hex(d)[:20]}...")
print(f"签名公钥 Q: ({hex(Q[0])[:20]}..., {hex(Q[1])[:20]}...)")
# 2. 消息持有者准备消息
message = "这是一笔匿名交易:转账 100 元".encode('utf-8')
print(f"\n原始消息: {message.decode()}")
# 3. 盲化
k = int.from_bytes(os.urandom(32), 'big') % n
e_tilde, R = blind_message(message, k, G)
print(f"盲化因子 k: {hex(k)[:20]}...")
print(f"盲化哈希 e_tilde: {hex(e_tilde)[:20]}...")
# 4. 签名者对盲消息签名
s = sign_blind(e_tilde, d)
print(f"盲签名 s: {hex(s)[:20]}...")
# 5. 去盲
sigma = unblind_signature(s, k)
print(f"去盲签名 sigma: {hex(sigma)[:20]}...")
# 6. 验证(示意)
print("\n签名完成。验证者可使用公钥 Q 验证签名的有效性。")
print("注意:此为教学演示模型,验证方程需参考具体方案。")
if __name__ == "__main__":
demo_blind_signature()2. 关键工程陷阱
陷阱 1:盲化因子重用
如果同一消息持有者在不同会话中重用盲化因子 $k$,签名者可以通过观察多个盲化消息推断消息之间的关系。
# ❌ 错误:重用盲化因子
k_fixed = 12345 # 固定盲化因子
e1_tilde, _ = blind_message(msg1, k_fixed, G)
e2_tilde, _ = blind_message(msg2, k_fixed, G)
# ✅ 正确:每次会话生成独立随机因子
k1 = int.from_bytes(os.urandom(32), 'big') % n
k2 = int.from_bytes(os.urandom(32), 'big') % n陷阱 2:盲化因子泄露
如果盲化因子 $k$ 泄露给签名者,签名者可以计算 $e = \tilde{e} - x_1$ 恢复原始消息哈希,从而破坏不可追踪性。
防护:盲化因子必须安全存储,签名完成后立即删除。
陷阱 3:签名者侧信道攻击
签名者在计算盲签名时,如果实现不当,可能通过侧信道(如功耗、时间)泄露私钥信息。
防护:使用恒定时间实现,对关键操作进行盲化(对私钥操作引入随机掩码)。
陷阱 4:验证方程设计错误
EC 盲签名的验证方程设计远比 RSA 复杂。如果方程设计不当,可能导致:
- 签名可被伪造
- 盲化因子可被恢复
- 签名者隐私被破坏
应用场景
1. 电子现金(e-Cash)
Chaum 最初提出盲签名的动机是实现匿名电子支付:
- 用户生成一个序列号(代表一枚硬币),盲化后发送给银行
- 银行对盲化序列号签名(相当于背书),扣除用户账户余额
- 用户去盲得到对序列号的有效签名
- 用户将签名后的序列号支付给商家
- 商家验证银行签名,确认硬币有效
防双花:需要额外的机制(如 Brands 的受限盲签名)来防止同一枚硬币被花费两次。
2. 匿名凭证(Anonymous Credentials)
盲签名是实现匿名身份凭证的核心技术:
- 用户向身份提供者(IdP)证明自己的身份属性(如年龄 > 18)
- IdP对用户的公钥或承诺进行盲签名,背书其属性
- 用户去盲得到签名凭证
- 用户向服务提供者(SP)出示凭证,证明自己满足条件,但不泄露具体身份
- Microsoft U-Prove(基于 Brands 凭证)
- IBM Idemix(基于 CL 签名 + 零知识证明)
- 国密场景下的匿名身份认证系统
3. 匿名投票(E-Voting)
盲签名在电子投票中的应用:
- 选民准备选票,盲化后发送给选举委员会
- 委员会验证选民资格后,对盲选票签名(授权投票)
- 选民去盲得到签名后的选票
- 选民将签名选票提交到公告板
可验证性:任何人都可以验证委员会签名,确认选票被授权。
与其他签名方案的对比
| 特性 | 标准签名 | 盲签名 | 群签名 | 环签名 |
|---|---|---|---|---|
| 签名者身份 | 公开 | 公开 | 群管理员 | 环成员之一 |
| 消息隐私 | 无 | 签名者不可见 | 无 | 无 |
| 签名者匿名性 | 无 | 无 | 群内匿名 | 环内匿名 |
| 不可追踪性 | N/A | 签名者无法追踪 | 管理员可追踪 | 不可追踪 |
| 典型应用 | 通用认证 | 电子现金、匿名凭证 | 企业内部审计 | 区块链隐私交易 |
| 国密支持 | SM2/SM3 | 参考 ISO/IEC 14888-3 | 无标准 | 无标准 |
参考来源
- Chaum, D. (1982). "Blind Signatures for Untraceable Payments". *Advances in Cryptology — CRYPTO '82*. DOI:10.1007/978-1-4757-0602-4_18
- GB/T 32918.2-2016《SM2 椭圆曲线公钥密码算法 第2部分:数字签名算法》
- GM/T 0003-2012《SM2 椭圆曲线公钥密码算法》系列标准
- ISO/IEC 14888-3:2018《数字签名 第3部分:基于离散对数的机制》
- Brands, S. (1993). "An Efficient Off-Line Coin System Based on the Representation Problem". CWI Report CS-R9323.
- Pointcheval, D., & Stern, J. (1996). "Provably Secure Blind Signature Schemes". *Advances in Cryptology — ASIACRYPT '96*.
相关实践
- SM2 数字签名算法原理详解 — 理解 SM2 标准签名是学习盲签名的基础
- 密码学承诺方案 — 盲签名常与承诺方案组合使用
- RSA 公钥密码算法原理 — RSA 盲签名的数学基础
- 密码学安全模型 — 理解不可追踪性的形式化定义