2. 基于RSA盲签名的隐私求交(PSI)协议实现

基于RSA盲签名的隐私求交(PSI)协议实现 学习卡片

协议原理与角色分工

RSA盲签名PSI的核心思想是:接收方(Bob)将自己的集合元素通过盲化因子隐藏后发送给签名方(Alice),Alice用自己的RSA私钥对盲化值签名,Bob去盲后得到Alice对原始元素的签名。双方各自对交集元素计算哈希,Bob将哈希集合发给Alice,Alice比对后返回交集。整个过程中Alice无法得知Bob集合的具体元素,Bob也无法伪造Alice的签名。

协议涉及三个关键数学操作:盲化(Bob用随机因子r隐藏元素)、盲签名(Alice对盲化值做RSA私钥运算)、去盲(Bob用r的逆元还原签名)。安全性依赖于RSA假设和随机预言机模型。

Alice(签名方) 持有 RSA 私钥 (n, d),集合 S_A Bob(接收方) 持有 RSA 公钥 (n, e),集合 S_B ① 盲化 x' = H(x) · r^e mod n 发送盲化值 x' ② 盲签名 s' = (x')^d mod n 返回盲签名 s' ③ 去盲 s = s' · r⁻¹ mod n ④ 计算签名哈希 h = H(s),得到集合 H_B 发送哈希集合 H_B ⑤ 比对交集 H_A ∩ H_B → 交集索引 返回交集结果 ⑥ 得到交集 S_A ∩ S_B(双方均不泄露差集) 安全保证 Alice 不知 Bob 集合内容 Bob 无法伪造 Alice 签名

关键实现步骤

下面给出完整的Python实现,使用cryptography库生成RSA密钥对,并手动实现盲化、签名、去盲和比对逻辑。为便于教学演示,集合元素使用小整数,实际应用中应对元素做哈希映射到模数空间。

import hashlib
import secrets
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization

# ========== 1. 密钥生成 ==========
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
pub_numbers = public_key.public_numbers()
n, e = pub_numbers.n, pub_numbers.e
priv_numbers = private_key.private_numbers()
d = priv_numbers.d

# ========== 2. 工具函数 ==========
def H_int(x: int) -> int:
    """将整数映射到 [0, n-1] 的哈希值"""
    h = hashlib.sha256(str(x).encode()).digest()
    return int.from_bytes(h, 'big') % n

def H_bytes(b: bytes) -> bytes:
    return hashlib.sha256(b).digest()

# ========== 3. Bob 盲化 ==========
def bob_blind(x: int):
    """返回 (盲化值 x', 盲化因子 r)"""
    r = secrets.randbelow(n - 2) + 2  # r ∈ [2, n-1]
    while True:
        try:
            r_inv = pow(r, -1, n)
            break
        except ValueError:
            r = secrets.randbelow(n - 2) + 2
    x_hash = H_int(x)
    x_blind = (x_hash * pow(r, e, n)) % n
    return x_blind, r, r_inv

# ========== 4. Alice 盲签名 ==========
def alice_sign(x_blind: int) -> int:
    """对盲化值做 RSA 私钥运算"""
    return pow(x_blind, d, n)

# ========== 5. Bob 去盲 ==========
def bob_unblind(s_blind: int, r_inv: int) -> int:
    """去盲得到原始签名"""
    return (s_blind * r_inv) % n

# ========== 6. 完整协议流程 ==========
def rsa_blind_psi(set_a: set, set_b: set):
    """
    RSA盲签名PSI协议
    返回: 交集集合
    """
    # Bob 对每个元素盲化
    blind_data = {}
    for x in set_b:
        x_blind, r, r_inv = bob_blind(x)
        blind_data[x] = (x_blind, r_inv)

    # Alice 对盲化值签名
    signed_data = {}
    for x, (x_blind, r_inv) in blind_data.items():
        s_blind = alice_sign(x_blind)
        signed_data[x] = (s_blind, r_inv)

    # Bob 去盲并计算哈希
    bob_hashes = {}
    for x, (s_blind, r_inv) in signed_data.items():
        s = bob_unblind(s_blind, r_inv)
        h = H_bytes(s.to_bytes((s.bit_length() + 7) // 8, 'big'))
        bob_hashes[h] = x

    # Alice 对自身集合元素计算签名哈希
    alice_hashes = {}
    for y in set_a:
        y_hash = H_int(y)
        s = pow(y_hash, d, n)
        h = H_bytes(s.to_bytes((s.bit_length() + 7) // 8, 'big'))
        alice_hashes[h] = y

    # 求交集
    common_hashes = set(bob_hashes.keys()) & set(alice_hashes.keys())
    intersection = {bob_hashes[h] for h in common_hashes}
    return intersection

# ========== 7. 测试验证 ==========
if __name__ == "__main__":
    S_A = {101, 202, 303, 404, 505}
    S_B = {202, 404, 606, 707, 808}

    result = rsa_blind_psi(S_A, S_B)
    expected = S_A & S_B

    print(f"Alice 集合: {S_A}")
    print(f"Bob   集合: {S_B}")
    print(f"协议输出交集: {result}")
    print(f"预期交集:     {expected}")
    print(f"验证结果: {'✓ 通过' if result == expected else '✗ 失败'}")

正确性验证与结果判读

运行上述代码,预期输出如下(交集为 {202, 404}):

Alice 集合: {101, 202, 303, 404, 505}
Bob   集合: {202, 404, 606, 707, 808}
协议输出交集: {202, 404}
预期交集:     {202, 404}
验证结果: ✓ 通过
验证要点:协议正确性依赖三个条件同时成立——(1) 盲化因子 r 与 n 互素,保证 r 的逆元存在;(2) 去盲运算 s = s' · r⁻¹ mod n 能还原出 H(x)^d mod n;(3) 双方对签名值使用相同的哈希函数。若交集为空,说明哈希比对未命中,需检查元素编码方式是否一致。

安全性分析与参数选择

安全属性实现机制参数建议
Alice 无法获知 Bob 集合Bob 发送的是盲化值 x' = H(x)·r^e mod n,Alice 无法从 x' 反推 H(x)r 必须使用密码学安全随机数,且每次盲化独立选取
Bob 无法伪造签名RSA 签名依赖私钥 d,Bob 只有公钥 e,无法对任意值生成有效签名密钥长度 ≥ 2048 位,e 取 65537
交集之外元素不泄露Bob 只发送哈希值,Alice 无法从哈希反推原始元素;Alice 只返回交集索引哈希函数使用 SHA-256,输出截断至模数长度
抗重放/链接攻击每次协议运行使用独立随机因子 r,相同元素在不同会话中盲化值不同r 不可复用,协议结束后立即销毁
注意事项:本实现为教学演示版本,直接对整数做 RSA 运算。生产环境中需注意:(1) 元素应先通过哈希映射到模数空间,避免小整数攻击;(2) 应使用 RSA-OAEP 或 PKCS#1 v1.5 填充方案;(3) 需加入零知识证明或承诺机制防止恶意参与方篡改中间值;(4) 通信层应使用 TLS 保护传输安全。

计算开销与优化方向

协议的主要计算开销集中在 RSA 模幂运算上。设 Bob 集合大小为 m,Alice 集合大小为 n,则:

  • Bob 端:m 次模幂(盲化,指数为 e)+ m 次模乘(去盲)+ m 次哈希
  • Alice 端:m 次模幂(签名,指数为 d)+ n 次模幂(自身集合签名)+ n 次哈希
  • 通信量:Bob 发送 m 个模 n 整数,Alice 返回 m 个模 n 整数,Bob 再发送 m 个哈希值

优化方向包括:使用中国剩余定理(CRT)加速私钥运算、批量模幂、以及将哈希比对改为布隆过滤器或 cuckoo filter 以降低通信轮次。