高级签名协议 -- 盲签名
高级签名协议 -- 盲签名
高级签名协议概述
一般地,一个数字签名体制包括:密钥生成(keygen)、签名(sign)、验签(verify)。对普通签名体制的安全需求是,攻击者即使知道签名人的公钥和若干签名对的有效信息也无法伪造该签名人的有效签名。但是随着对数字签名的不断研究,以及电子商务、电子政务的快速发展,简单模拟手写签名的一般数字签名已不能完全满足现实中的应用需求,研究具有特殊性质或特殊功能的数字签名成为数字签名研究的主要方向。在密码学的研究中有许多学者提出了具有特定性质的数字签名,本次博客主要介绍其中的盲签名、群签名、环签名和基于身份的数字签名。
盲签名
盲签名的基本概念
在一个盲签名体制中存在两个参与实体:签名人和用户,其中,签名人拥有自己的公私钥,用户有一个消息\(m\),并希望得到签名人对\(m\)的签名。一个盲签名方案一般由满足如下条件的3个算法Setup、Sign、Verify构成。
- Setup是一个概率多项式时间算法。其输出为系统参数\(params\)和签名人的公/私钥对\((pk,sk)\)。
- Sign是一个概率多项式时间的交互协议,公共输入是系统参数和签名人的公钥\(pk\),签名人的秘密输入为自己的私钥\(sk\),用户的秘密输入为待签名的消息\(m\),双方交互执行签名协议,在多项式时间内停止,停止后用户输出签名\(s\)。
- Verify是一个多项式时间算法,其输入为系统公开参数\(params\)、签名人的公钥\(pk\)和待验证的签名对\((m,s)\),其输出为\(1\)(表示签名有效)或\(0\)(表示签名无效),记作\(1\)或\(0\leftarrow Verify(params,pk,m,s)\)。
盲签名的安全性需求
盲签名的安全需求,通俗地说,我们称一个盲签名体制是安全的,它至少需要满足下面的3个性质:
- 正确性(也称完备性与一致性) 如果\(s\)是\(Sign\)算法正确执行后输出的对于消息\(m\)的签名,则总有\(Verify(params,pk,m,s)\rightarrow 1\)。
- 不可伪造性 任意不知道签名人私钥\(sk\)的人,无法有效地计算出一个能够通过签名验证方程地消息签名对\((m^{'},s^{'})\)。
- 盲性 除请求签名地用户外,任何人(包括签名人)都无法将交互协议\(Sign\)产生地会话信息(签名人与用户在公共信道上交互的信息的集合)与最终的盲签名正确匹配起来。如果签名人能将其会话信息与最终所得的签名正确匹配,他就能够跟踪签名。
盲签名的基本设计思路
假设用户有一个消息\(m\),并希望得到签名人对该消息的签名,一种比较典型的设计盲签名体制的做法是:
- 在发送消息给签名人之前,用户先引入盲化因子,由消息\(m\)计算出数据\(m^{'}\),发送\(m^{'}\)给签名人,这个过程称为盲化。
- 签名人对\(m^{'}\)执行签名操作得到签名\(s^{'}\),发回给用户。
- 用户从\(s^{'}\)中计算出消息\(m\)的签名\(s\),这个过程称为去盲。
在签名过程中,签名人不知道消息\(m\)是什么;在签名过后,如果把签名\(s\)交给签名人,他不知道这是什么时候签的,也不知道是签给谁的。盲签名的这些良好特性使得它在如电子货币、电子拍卖、电子选举等诸多同时需要匿名性和认证性的应用场合起到关键作用,比如可以用来实现不可跟踪电子货币或无记名选举。
Chaum盲签名协议
在接下来的描述中,将参照教材的语言进行描述。“本节描述算法时使用电子选举的语言,方案适用于其他应用。选举委员会发布候选人名单,投票人在选票上标记自己的意向候选人。为了使得选票有效,该选票需要经过选举委员会签名确认。既要得到选举委员会对选票的签名,又不能泄露选票的内容,此时投票人可以和选举委员会交互执行盲签名协议。”
协议流程:
The attack I attempted on an impulse
阅读教材时发现,在选举系统中,投票结束后会公开(m,s),本人最初认为如果公开(m,s)就不再满足防追踪性(不可关联性)。具体攻击(后续也证明这种思路是不可行的)思路如下:
from Crypto.Util.number import getPrime, inverse
import random
###################### 初始化阶段 ######################
p = getPrime(512); q = getPrime(512)
n = p * q
e = 65537 # RSA加密常用公钥指数
phi = (p - 1) * (q - 1); d = inverse(e, phi)
###################### 预处理 ######################
# 设有100名人员参与投票
x = 100
# k_list为每位选票人员盲化时生成的随机数k, k ∈ [1, n - 1]
k_list = [random.randrange(1, n) for _ in range(x)]
# m_list为每为选票人员所投出去的票,为验证结果情况,取值为 1..100
m_list = [i for i in range(1, x + 1)]
###################### 签名阶段 ######################
# ss_list 存储选举委员会签名后的 s'; s_list存储最终提交的验证结果; mm_list存储盲化后的 m'
# 至此,我们有了 (m_list, s_list) 与 (mm_list, ss_list),前者对应教材公开后的(m, s); 后者对应选举委员会进行签名时收到的结果
# k_list并非 kgroup; 设 kk_list 表示公开(m,s)后委员会计算的 kgroup
ss_list = []; s_list = []; mm_list = []
for i in range(x):
m = m_list[i]; k = k_list[i]
# 盲化 mm 代替协议中的 m'
mm = m * pow(k, e, n) % n # m' = m * k ^ e (mod n)
ss = pow(mm, d, n) # s' = m' ^ d (mod n)
s = ss * inverse(k, n) % n
mm_list.append(mm); ss_list.append(ss); s_list.append(s)
###################### 验签阶段 ######################
# 若结果为 False ,输出 "Error" 后退出验证
for i in range(x):
m = m_list[i]; s = s_list[i]
if pow(s, e, n) != m:
print("Error")
break
###################### 攻击阶段 ######################
print("Attack begin.")
# 我们选取第 51 组的 (m', s'),找到其对应的(m, s)即攻击成功,即若后续结果校验时,在 i = 51 处显示正确,则说明攻击成功
mm = mm_list[51]; ss = ss_list[51]
# 1. 计算 kgroup
kk_list = []
for i in range(x):
s = s_list[i]
k_guess = ss * inverse(s, n) % n # k_guess = s' * s ^ {-1} (mod n)
kk_list.append(k_guess)
# 2. 令 m'' = m * k_guess ^ e (mod n) ,采用 m'' == m' 进行校验
for i in range(x):
m = m_list[i]
k = kk_list[i]
# mmm 表示当前计算的 m'', mm = m * k ^ e (mod n)
mmm = m * pow(k, e, n) % n
if mmm == mm:
print(f"The origin of (m', s') is {i}")
print("Attack over.")
Code
from Crypto.Util.number import getPrime, bytes_to_long
import random
class TA:
def __init__(self):
self.p, self.q = [getPrime(1024) for _ in range(2)]
self.n = self.p * self.q
self.e = 65537
phi = (self.p - 1) * (self.q - 1)
self.d = pow(self.e, -1, phi)
class USER:
def __init__(self, TA):
self.TA = TA
self.k = random.randrange(1, self.TA.n)
def blinding(self, message):
if isinstance(message, bytes):
message = bytes_to_long(message)
mm = message * pow(self.k, self.TA.e, self.TA.n) % self.TA.n
return mm
def unblinding(self, ss):
return pow(self.k, -1, self.TA.n) * ss
class Signer:
def __init__(self, TA):
self.TA = TA
def sign(self, mm):
return pow(mm, self.TA.d, self.TA.n)
class Verifier:
def __init__(self, TA):
self.TA = TA
def verify(self, mspair:tuple) -> bool:
m, s = mspair
if isinstance(message, bytes):
m = bytes_to_long(m)
return m == pow(s, self.TA.e, self.TA.n)
if __name__ == "__main__":
ta = TA()
user = USER(ta)
signer = Signer(ta)
verifier = Verifier(ta)
message = b'I choose Alice to go.'
mm = user.blinding(message)
ss = signer.sign(mm)
s = user.unblinding(ss)
flag = verifier.verify((message, s))
if flag:
print("Verify Successfully.")
else:
print("Error...")
基于离散对数的盲签名
教材提要
设\(G\)是一个阶为素数\(p\)的乘法循环群,其上的\(CDH(computational Diffie-Hellman)\)问题和\(DDH(decisional Diffie-Hellman)\)分别定义如下:
- \(CDH\) 给定\(G\)中的3个随机元素\((g,u,v)\),计算\(h=g^{log_gulog_gv}\)。
- \(DDH\) 给定\(G\)中的4个元素\((g,u,v,h)\),要么①\(G\)中的随机元素,要么是②满足等式\(log_gu=log_vh\),且两种情况概率相等。如果是第一种情况输出为\(0\),如果是第二种情况则输出为\(1\)。判断\((g,u,v,h)\)属于哪种情况。
给定一个素数阶群\(G\),如果存在一个有效的算法\(V_{DDH}(·)\)能够求解其上的\(DDH\)问题,但不存在有效算法求解其上的\(CDH\)问题,则将这样的群称为\(GDH(gap Diffie-Hellman)\)群。在2001年,斯坦福大学的3位学者\(Boneh\)、\(Lynn\)和\(Shacham\)利用\(GDH\)群设计了一个短签名方案,以下称之为\(BLS\)体制,该方案是这样工作的:\(G\)为\(GDH\)群,\(|G|=p,H:\{0,1\}^{*}\rightarrow G^{*}\)为一个哈希函数,签名人选择\(x\leftarrow _R\mathbb{Z}_{p}^{*}\)作为自己的私钥,将\(y\leftarrow g^x\)作为自己的公钥;对于任意消息\(m\),其数字签名为\(s=H(m)^x\);给定消息对\((m,s)\),如果\(V_{DDH}(g,y,H(m),s)=1\),则验证者接受签名有效,否则拒绝。
利用\(BLS\)体制,容易得到一个\(GDH\)群上的盲签名方案。这个工作是由\(Boldyreva\)在2002年完成的。
协议流程
双线性配对
在进行代码实现之前,对课本中提到的\(CDH\)与\(DDH\)进行以下详细的解释,更加直观一些:
上述在描述协议时,只是在假设存在这样的一个\(V_{DDH}\)算法,然后基于这个假设去推导盲签名以及\(BLS\)签名的安全性,至于这个\(V_{DDH}\)算法如何通过计算机实现,教材中并未提及。在计算机中,如何实现\(V_{DDH}\)就是一个问题,在已知的数学中,除了穷举,该如何构建一个能够防住\(CDH\)又能有效解决\(DDH\)的群?密码学界找了很久发现:配对友好型椭圆曲线上的双线性配对(Bilinear Piring),完美契合了这个要求。
双线性配对\(e(·,·)\)天然具有一个性质,它可以把指数上的乘法“提取”出来:\(e(g^a,g^b)=e(g,g)^{ab}\)。联系到教材中描述的,以及上方我简化的\(DDH\)问题:给定公钥\(y=g^x(g^a)\),以及哈希点\(H(m)(g^b)\),验证者在那道签名\(s\),需要判断\(s==H(m)^x(g^{ab})\),所以如果我们用配对来实现,验证过程就会及其简单,我们只需要计算并对比两边的配对结果:\(left = e(g,s)\),\(right = e(y, H(m))\),如果签名\(s\)真的是用私钥\(x\)签名所得,即\(s=H(m)^x\),那么\(e(g,s)=e(g, H(m)^x)=e(g,H(m))^x=e(g^x, H(m))=e(y, H(m))\)等式成立。这个计算配对的等式,就是在数学中抽象的\(V_{DDH}\)在计算机中的实现。
为何\(CDH\)依然困难?虽然配对能够依据上述等式来判断是否正确,但是它并不能帮你算出签名。配对的结果会落在另一个群\(G_T\)中,无法通过\(e(g,g)^{ab}\)反推回原来的群元素\(g^{ab}\),这就实现了判定容易,计算困难的\(Gap\)属性。
Code
#include <pbc/pbc.h>
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
// 初始化配对环境
char param[1024] = "type a\nq 8780710799663312522437781984754049815806883199414208211028653399266475630880222957078625179422662221423155858769582317459277713367317481324925129998224791\nh 12016012264891146079388821366740534204802954401251311822919615131047207289359704531102844802183906537786776\nr 730750818665451621361119245571504901405976559617\nexp2 159\nexp1 107\nsign1 1\nsign0 1\n";
pairing_t pairing;
pairing_init_set_buf(pairing, param, strlen(param));
// 定义需要的元素
element_t g, x, y, Hm, r, M_prime, S_prime, r_inv, s, temp_left, temp_right;
// 初始化元素所在的群
element_init_G2(g, pairing);
element_init_G2(y, pairing);
element_init_G1(Hm, pairing);
element_init_G1(M_prime, pairing);
element_init_G1(S_prime, pairing);
element_init_G1(s, pairing);
element_init_Zr(x, pairing);
element_init_Zr(r, pairing);
element_init_Zr(r_inv, pairing);
element_init_GT(temp_left, pairing);
element_init_GT(temp_right, pairing);
cout << "Setup: keygen." << endl;
element_random(g); // 随机生成元g
element_random(x); // 私钥x
element_pow_zn(y, g, x); // 公钥y = g ** x
cout << "Interactive..." << endl;
// (1) 模拟消息哈希到群G1上的点Hm
element_random(Hm);
// (2) 盲化 M' = Hm ** r
element_random(r);
element_pow_zn(M_prime, Hm, r);
// (3) Signer sign message S' = (M') ** x
element_pow_zn(S_prime, M_prime, x);
// (4) User 去盲 s = (S') ** (r ** -1)
element_invert(r_inv, r); // 计算r的逆元
element_pow_zn(s, S_prime, r_inv); // 得到真实签名
cout << "Verify..." << endl;
// 验证e(s, g) == e(Hm, y)
element_pairing(temp_left, s, g);
element_pairing(temp_right, Hm, y);
if (!element_cmp(temp_left, temp_right)) {
cout << "验证成功,双线性配对等式成立。" << endl;
} else {
cout << "验证失败!" << endl;
}
// free the memory
element_clear(g);
element_clear(x);
element_clear(y);
element_clear(Hm);
element_clear(M_prime);
element_clear(S_prime);
element_clear(s);
element_clear(r);
element_clear(r_inv);
element_clear(temp_left);
element_clear(temp_right);
pairing_clear(pairing);
return 0;
}
部分盲签名
普通的盲签名体制中,被签名的消息完全由用户控制,签名人对此一无所知,也不知道关于最终签名的任何信息,这有可能造成签名被非法使用等问题。基于盲签名潜在的问题。部分盲签名缓解了相关问题,在一个部分盲签名方案中,签名人可以在签名中嵌入一个和用户实现约定好的公共信息。比如,在电子货币系统中银行可以将有效期作为公共信息嵌入电子货币,有效期过后所有电子货币均失效。这里还是采用教材中的例子,以\(Zhang\)等人利用双线性配对设计的部分盲签名为例,介绍部分盲签名体制。与盲签名一样,一个部分盲签名体制也包括系统初始化算法\(Setup\)、签名生成算法\(Sign\)、签名验证算法\(Verify\)。
协议流程
正确性验证
“一方面在产生\(U\)的过程中,由于\(User\)使用了随机数\(r\),所以由此计算出的\(U\)是群\(G_1\)中的一个随机元素,签名者\(Signer\)无法从\(U\)和公共信息\(c\)中得到关于消息\(m4的任何信息。另一方面,给定一个有效的消息签名对\)(m||c,S)\(,用户也无法私自将公共信息改成对自己有利的其他数据。对此,签名者可以拥有足够的自信。这是因为,如果\)User\(能够将\)(m||c,S)\(改为\)(m||c',S)$的话,则下面两个式子同时成立”
即\((H(c')-H(c))S=H_0(m||c')-H_0(m||c)\),但是由于\(H(·)\)与\(H_0(·)\)都是哈希函数,这是计算上不可能的。因此,\(Signer\)可以确幸用户无法修改公共信息。
Code
#include <pbc/pbc.h>
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
// 1. Setup 初始化对称配对环境 (Type A)
char param[1024] = "type a\nq 8780710799663312522437781984754049815806883199414208211028653399266475630880222957078625179422662221423155858769582317459277713367317481324925129998224791\nh 12016012264891146079388821366740534204802954401251311822919615131047207289359704531102844802183906537786776\nr 730750818665451621361119245571504901405976559617\nexp2 159\nexp1 107\nsign1 1\nsign0 1\n";
pairing_t pairing;
pairing_init_set_buf(pairing, param, strlen(param));
// 定义变量
element_t P, x, P_pub; // 基础参数
element_t hc, H0_mc; // 哈希值 H(c) 和 H0(m||c)
element_t r, U, V, S; // 盲化与签名变量
element_t temp_G1, temp_Zr, P_neg_r; // 临时计算变量
element_t left, right;
// 初始化群
element_init_G1(P, pairing);
element_init_G1(P_pub, pairing);
element_init_G1(H0_mc, pairing);
element_init_G1(U, pairing);
element_init_G1(V, pairing);
element_init_G1(S, pairing);
element_init_G1(temp_G1, pairing);
element_init_G1(P_neg_r, pairing);
element_init_Zr(x, pairing);
element_init_Zr(hc, pairing);
element_init_Zr(r, pairing);
element_init_Zr(temp_Zr, pairing);
element_init_GT(left, pairing);
element_init_GT(right, pairing);
cout << "1. Setup & KeyGen" << endl;
element_random(P); // 生成元
element_random(x); // sk
element_pow_zn(P_pub, P, x); // pk
cout << "2. Sign..." << endl;
// 模拟哈希函数输出
element_random(hc); // 模拟H(c) 输出Zr中的整数
element_random(H0_mc); // 模拟H0(m||c) 输出 G1 中的点
// User choose random r, compute U
element_random(r);
// conpute (P ** H(c)) * Ppub
element_pow_zn(temp_G1, P, hc);
element_mul(temp_G1, temp_G1, P_pub);
// conpute temp_G1 ** r
element_pow_zn(temp_G1, temp_G1, r);
// U = H0(m||c) * temp_G1
element_mul(U, H0_mc, temp_G1);
cout << "[User] send U to [Signer]..." << endl;
// Signer conpute V = U ** (1 / (hc + x))
element_add(temp_Zr, hc, x); // temp_Zr = hc + x
element_invert(temp_Zr, temp_Zr); // temp_Zr = (hc + x) ^ -1
element_pow_zn(V, U, temp_Zr); // V = U ** temp_Zr
cout << "[Signer] reply V to [User]..." << endl;
// User compute S = V * P ^ (-r)
element_neg(temp_Zr, r); // temp_Zr = -r
element_pow_zn(P_neg_r, P, temp_Zr); // P_neg_r = P ^ (-r)
element_mul(S, V, P_neg_r); // S = V * P_neg_r
cout << "[User] 完成去盲操作,输出签名S..." << endl;
cout << "3. Verify..." << endl;
// Verify e(P ** hc * Ppub, S) == e(P, H0_mc)
// 重新计算左侧配对的第一个参数 temp_G1 = P ** hc * Ppub
element_pow_zn(temp_G1, P, hc);
element_mul(temp_G1, temp_G1, P_pub);
element_pairing(left, temp_G1, S);
element_pairing(right, P, H0_mc);
if (!element_cmp(left, right)) {
cout << "Successfully Verify!" << endl;
} else {
cout << "Failed..." << endl;
}
// Free the memory
element_clear(P);
element_clear(P_pub);
element_clear(x);
element_clear(H0_mc);
element_clear(U);
element_clear(V);
element_clear(S);
element_clear(temp_G1);
element_clear(P_neg_r);
element_clear(hc);
element_clear(r);
element_clear(temp_Zr);
element_clear(left);
element_clear(right);
pairing_clear(pairing);
return 0;
}
写在最后
针对盲签名的学习,是第一次接触双线性配对,其实现原理比较复杂,但是对于上层调用而言并没有过多的难点。这里实现的过程中我采用的是PBC库进行的上层调用,目前应该是存在有更好用的一些库,这里并没有进行深究。其中对于代码实现中的一个问题就是,对于椭圆曲线中点的运算在映射回整数域的时候,其加法运算以及乘法运算会映射到乘法以及幂运算上。因此在阅读代码的时候,如果发现协议流程与Code存在出入的话是正常的,后续没有使用Python去进行编程实现很大一部分原因是我的计算机上刚好存在PBC库,也不想花更多的经历去在sagemath中去配置环境了,所以存在前面所说的出入很大部分原因是PBC库有点老了的原因。😀😀
最后在这里写一下我对于盲签名的认知吧,其实对于所有的高级签名协议而言,我认为都是需求在驱动着研究。拿部分盲签名举个例子,我有一张钞票,如果我要花出去,商户需要确定这是一张经过银行认可的电子钞票,故其会接受,换句话说,认证成功才能实现电子钞票的最基本的功能。我既然需要银行来签名,就需要把钞票呈现给银行,如果没有盲签名,那么对于银行而言,他就可以跟踪用户“我”的开销,我这张钞票在哪里花了?买了什么?这属于用户的隐私,为了保证电子钞票的钞票职能以及用户的隐私,盲签名就可以完美解决这一问题,我用纸质的钞票去兑换电子钞票,此后这张钞票我如何花就不会被银行跟踪监测。
后续有时间会把群签名、环签名等高级签名协议也进行总结和编程复现验证一下。

高级签名协议 -- 盲签名
浙公网安备 33010602011771号