本周主要的内容是:(1)实现填充RSA加密算法以及其攻击原理。(2)多个数字签名的历史地位,明白其各个签名所产生的背景。
PKCS1 v1.5 加密算法实现
定义
x
x
x如下图所示,图中数字的单位均为字节:
PKCS1 RSA加密的v1.5的具体实现过程分为以下几个步骤:
1.公私钥对生成
2.随机生成
117
117
117字节的字符串作为消息
D
D
D
2.在
D
D
D前面进行填充:“
00
00
00”和“
02
02
02”填充、
P
S
PS
PS(非零随机数)填充、“
00
00
00”填充
3.将八位字节字符串转换成整数
4.RSA计算
5.将整数转换成八位字节字符串
6.以
16
16
16进制输出密文
7.将八位字节字符串复制到
c
i
p
h
e
r
cipher
cipher中(供解密使用)
解密过程:
1.将八位字节字符串转换成整数
2.RSA计算
3.将整数转换成八位字节字符串
4.加密块解析:去掉前面的“
00
00
00”、“
02
02
02”、
P
S
PS
PS以及“
00
00
00”,得到
D
D
D
5.将
D
D
D复制到
m
e
s
s
a
g
e
message
message中
6.以
16
16
16进制输出
m
e
s
s
a
g
e
message
message
// 填充与加密过程
//这里N=BLOCK_SIZE=128字节
for(int i=0;len>0;i++){
int d_len = (len >= (BLOCK_SIZE - 11)) ? BLOCK_SIZE - 11 : len;
/* 消息填充 */
// 00 || 02
mess_block[i++] = 0x00;
mess_block[i++] = 0x02;
// PS
while(i < (BLOCK_SIZE - d_len - 1))
mess_block[i++] = (rand() % (0xFF - 1)) + 1; //1-255
mess_block[i++] = 0x00;// 00
memcpy(mess_block + i, message + (length - len), d_len);// D
printf("填充后的消息为:");
print_hex(mess_block,BLOCK_SIZE);
printf("\n");
// 将八位字节字符串转换成整数
mpz_import(m, BLOCK_SIZE, 1, sizeof(mess_block[0]), 0, 0, mess_block);
// RSA计算
block_encrypt(c, m, pk);
// 最后生成的密文比BLOCK_SIZE短,就在密文前填充0
int off = block_count * BLOCK_SIZE;
off += (BLOCK_SIZE - (mpz_sizeinbase(c, 2) + 8 - 1)/8);
// 整数到八位字节字符串
mpz_export(FD + off, NULL, 1, sizeof(char), 0, 0, c);
printf("输出密文:");
print_hex(FD, BLOCK_SIZE);
printf("\n");
memcpy(cipher , FD , BLOCK_SIZE);
block_count++;
len -= d_len;
}
// 解密过程
int i;
for(i = 0; i < (length / BLOCK_SIZE); i++){
// 将八位字节字符串转换成整数
mpz_import(c, BLOCK_SIZE, 1, sizeof(char), 0, 0, cipher + i * BLOCK_SIZE);
// RSA计算
block_decrypt(m, c, sk);
int off = (BLOCK_SIZE - (mpz_sizeinbase(m, 2) + 8 - 1)/8);
// 整数到八位字节字符串
mpz_export(buf + off, NULL, 1, sizeof(char), 0, 0, m);
/* 加密块解析 */
// 去掉前面的0x00和0x02,以及PS,直到遇到一个“0”
int j;
for(j = 2; ((buf[j] != 0) && (j < BLOCK_SIZE)); j++);
j++;// 跳过“00”
// 将D复制到message中
memcpy(message + msg_idx, buf + j, BLOCK_SIZE - j);
printf("解密:");
print_hex(message + msg_idx, BLOCK_SIZE-j);
printf("\n");
msg_idx += BLOCK_SIZE - j;
}
main方法核心代码:
char buf[117];
for(i = 0; i < 117; i++)
buf[i] = rand() % 0xFF;
mpz_import(M, 117, 1, sizeof(buf[0]), 0, 0, buf);
printf("明文:%s\n\n", mpz_get_str(NULL, 16, M));
char bufed[BLOCK_SIZE];
encrypt(bufed,buf,117,pk);
char DDC[117];
decrypt(DDC,bufed,BLOCK_SIZE,sk);
system("pause");
return 0;
其中的一个运行结果为:
Bleichenbacher(布莱申巴赫)攻击
bleichenbacher攻击
①首先,将明文消息message进行PKCS1 v1.5格式填充,然后将其转换成整型,进行 R S A RSA RSA加密计算,得到密文 c i p h e r t e x t ciphertext ciphertext。
②其次,
B
l
e
i
c
h
e
n
b
a
c
h
e
r
Bleichenbacher
Bleichenbacher攻击。攻击者先将
c
i
p
h
e
r
t
e
x
t
ciphertext
ciphertext转换成整型,得到
c
c
c。此时,
B
=
2
8
(
k
−
2
)
,
M
=
[
2
B
,
3
B
−
1
]
B=2^{8(k-2)},M=[2B,3B-1]
B=28(k−2),M=[2B,3B−1]。
③然后查找一个
s
,
s
≥
n
/
3
B
s,s \geq n/3B
s,s≥n/3B,使得
o
r
a
c
l
e
oracle
oracle收到
c
′
=
c
⋅
s
e
m
o
d
n
=
(
m
⋅
s
)
e
m
o
d
n
c'=c·s^e \bmod n=(m·s)^e \bmod n
c′=c⋅semodn=(m⋅s)emodn并解密,得到的
m
⋅
s
m·s
m⋅s是符合PKCS1 v1.5格式填充的。
④然后更新
M
M
M。此时,
M
=
[
a
,
b
]
M=[a,b]
M=[a,b],故可得
r
r
r的区间为:
(
a
s
−
3
B
+
1
)
/
n
≤
r
≤
(
b
s
−
2
B
)
/
n
(as-3B+1)/n \leq r \leq (bs-2B)/n
(as−3B+1)/n≤r≤(bs−2B)/n,在该区间内,每一个
r
r
r都可以得到一个可能的
m
⋅
s
m·s
m⋅s的所在区间,即
m
a
x
(
a
,
(
2
b
+
r
n
)
/
s
)
≤
m
s
≤
m
i
n
(
b
,
3
B
+
r
n
−
1
)
/
s
max(a,(2b+rn)/s) \leq ms \leq min(b,3B+rn-1)/s
max(a,(2b+rn)/s)≤ms≤min(b,3B+rn−1)/s。由于此时
M
′
M'
M′集合为空,故直接插入。一般情况下,
r
r
r的值只有一个。那么此时
M
M
M集合更新为
M
′
M'
M′。然后将
M
′
M'
M′赋值给
M
M
M。见图
1
1
1。当
r
r
r的值有两个时,如图
2
2
2所示。
注意1:每找一个新的 s s s, M ′ M' M′都会更新为空集合。
注意2: M M M是 m ⋅ s m·s m⋅s所在的可能区间的集合,一般为一个,也可能出现两个甚至更多。
⑤然后判断
M
M
M的长度,即
M
M
M内有几个集合。如果只有一个集合,判断该集合的上界和下界是否相等,如果相等,则找到了这个
m
⋅
s
m
o
d
n
m·s \bmod n
m⋅smodn的值。如果不相等,则在
M
M
M区间内,找一个新的
s
s
s,使得
o
r
a
c
l
e
oracle
oracle收到
c
′
=
c
⋅
s
e
m
o
d
n
=
(
m
⋅
s
)
e
m
o
d
n
c'=c·s^e \bmod n=(m·s)^e \bmod n
c′=c⋅semodn=(m⋅s)emodn并解密,得到的
m
⋅
s
m·s
m⋅s是符合PKCS1 v1.5格式填充的,其中
r
i
≥
2
(
b
s
前
−
2
B
)
/
n
r_i \geq 2(bs_前-2B)/n
ri≥2(bs前−2B)/n,在该区间内,每一个
r
i
r_i
ri都可定一个
s
s
s的区间,即
(
2
B
+
r
i
n
)
/
b
≤
s
≤
(
3
B
+
r
i
n
)
/
a
(2B+r_in)/b \leq s \leq (3B+r_in)/a
(2B+rin)/b≤s≤(3B+rin)/a,在
s
s
s的这个区间内找这个符合前述要求的
s
s
s,若未找到,则
r
i
r_i
ri增加
1
1
1,直到找到
s
s
s为止。如果有至少
2
2
2个集合,如图
2
2
2中的第二种情况,则找一个新的
s
s
s,
s
=
s
前
+
1
s=s_前+1
s=s前+1,找到后更新
M
M
M,此时
M
M
M有两个集合,分别在这两个集合中查找
m
⋅
s
m·s
m⋅s可能的区间,类似步骤④,最后会得到一个区间长度为
1
1
1的区间。
⑥然后更新
M
M
M。此时,
M
M
M为④中的
M
M
M,即回到步骤④。
基于RSA的签名方案
①“教科书式RSA”签名方案:是Diffie和Hellman提出数字签名思想后的第一个数字签名体制,它是由Rivest、Shamir和Adleman三人共同完成的。
p k = ( N , e ) , s k = ( N , d ) pk=(N,e),sk=(N,d) pk=(N,e),sk=(N,d)
签名: σ = m d m o d N \sigma=m^d \bmod N σ=mdmodN
验证: m = σ e m o d N m=\sigma^e \bmod N m=σemodN
面对多种攻击,RSA都是不安全的。比如无消息攻击、对任意消息伪造签名等。
②Rabin数字签名类似于①中的签名,RSA中的 e e e是奇数( g c d ( e , ϕ ( N ) ) = 1 gcd(e,\phi(N))=1 gcd(e,ϕ(N))=1),而这里的 e e e是偶数。该方案由Michael O.Rabin于1979年提出的一种数字签名方法。它是最早提出的数字签名方案之一,并且是唯一将伪造的难度直接与整数的因式分解问题直接联系起来的方案。假设整数分解问题是棘手的,那么在随机预言机模型中,Rabin签名算法在本质上是不可伪造的。它利用Rabin公钥密码算法构造而成,它要求被签消息必须是模 m m m的平方剩余,如果不是,就要处理成是这种要求的 m ′ m' m′。这个方案的签名验证要比RSA签名方案快几百倍,而且反演也快。当p和q已知时,比RSA签名中的相应算法快8倍。
p k = n , s k = ( p , q ) pk=n,sk=(p,q) pk=n,sk=(p,q)
签名:如果 m m m是模 N N N的二次剩余,然后计算 x 2 = H ( m ) m o d N x^2=H(m) \bmod N x2=H(m)modN,签名为: x x x
验证:给定 m m m和 x x x,验证 x 2 x^2 x2和 H ( m ) H(m) H(m)是否相等。
由于不能保证要签名的消息都是模 N N N的二次剩余,所以出现了改进的Rabin签名
改进的Rabin签名
p k = N , s k = d = ( n − p − q + 5 ) / 8 , p = 3 m o d 8 , q = 7 m o d 8 pk=N,sk=d=(n-p-q+5)/8,p=3 \bmod 8,q=7 \bmod 8 pk=N,sk=d=(n−p−q+5)/8,p=3mod8,q=7mod8
签名: a = H ( m ) = 16 m + 6 a=H(m)=16m+6 a=H(m)=16m+6,如果 a a a是模 N N N的二次剩余,则 σ = a d m o d N \sigma=a^d \bmod N σ=admodN,如果不是,则 σ = ( a / 2 ) d m o d N \sigma=(a/2)^d \bmod N σ=(a/2)dmodN
验证: b = σ 2 m o d N b=\sigma^2 \bmod N b=σ2modN,如果 b = 6 m o d 8 b=6 \bmod 8 b=6mod8,则 a = b a=b a=b;如果 b = 3 m o d 8 b=3 \bmod 8 b=3mod8,则 a = 2 b a=2b a=2b;如果 b = 7 m o d 8 b=7 \bmod 8 b=7mod8,则 a = N − b a=N-b a=N−b;如果 b = 2 m o d 8 b=2 \bmod 8 b=2mod8, a = 2 ( N − b ) a=2(N-b) a=2(N−b),如果 a a a确实是经过 H ( m ) = 16 m + 16 H(m)=16m+16 H(m)=16m+16计算出来的,那么, m = H − 1 ( a ) = ( a − 6 ) / 16 m=H^{-1}(a)=(a-6)/16 m=H−1(a)=(a−6)/16
对于改进的Rabin签名方案,存在性伪造签名是容易实现的,只要找到一个签名 σ , 1 ≤ σ ≤ N − 1 \sigma,1 \leq \sigma \leq N-1 σ,1≤σ≤N−1,使得 σ 2 \sigma^2 σ2或 N − σ 2 N-\sigma^2 N−σ2或 2 σ 2 2\sigma^2 2σ2或 2 ( N − σ 2 ) m o d N 2(N-\sigma^2) \bmod N 2(N−σ2)modN与 6 m o d 16 6 \bmod 16 6mod16相等,那么 σ \sigma σ就是 m ′ = σ 2 m o d N m'=\sigma^2 \bmod N m′=σ2modN的签名。
与具有相同模数大小的RSA签名生成相比,生成Rabin签名的计算强度并不大。当e = 2时,签名验证非常快;它只需要一个模乘法。平方运算比一般的模乘运算稍微有效率一些。这也比RSA签名验证更有利,即使RSA公开指数是e = 3。
③RSA-FDH签名方案由Bellare和Rogaway在他们关于随机oracle模型的原始论文中提出的(1996年),他们使用加密哈希函数来防止代数攻击的想法(没有证明)可以追溯到Rabin。该方案对任意长度的消息都能签名,但是实际中很难创建这样的哈希函数。它可以在随机预言机模型中可证明是安全的。后来的改进(PSS)被标准化为PKCS #1 v2.1的一部分。(PSS:probabilistic signature scheme)
签名: σ = H ( m ) d m o d N \sigma=H(m)^d \bmod N σ=H(m)dmodN
验证: H ( m ) = σ e m o d N H(m)=\sigma^e \bmod N H(m)=σemodN
像这种先哈希后签名的安全性在很大程度上取决于实现哈希的精确方式。如果是实际运用中的哈希函数,像MD5,它产生的散列值的集合只有 2 128 2^{128} 2128个,是非常有结构的,非常稀疏的。所以,除了RSA是单向陷门的,哈希函数也得是理想的。哈希函数除了是抗碰撞的,还要符合别的要求。
FDH(全域哈希函数)就像是一个有指定的定义域和范围的随机函数。只要RSA是陷门置换,FDH的安全性就可以被证明。这使得FDH方案的安全性保证优于使用实际运用中的哈希函数。
④RSA PKCS #1 v2.1标准包括一个签名方案,可以看作是RSA- FDH的一个变体。PSS就是加入了一个随机种子
r
r
r,要求
r
r
r的长度要小于模
N
N
N的长度。该方案保持了FDH的效率和可证明的安全性,又以更好的安全限度实现了后者。
基于离散对数的签名方案
**⑤ElGamal签名方案:**1985年由ElGamal设计的数字签名。由于其具有存在性伪造,而且为了无法计算出离散对数问题,需要模 p p p长度至少为 1024 1024 1024比特,这样导致签名长度达到了 2024 2024 2024比特,不利于应用到存储有限的智能卡中。因此,这些不安全性使得ElGamal一般不用于实际应用中,其变形算法有Schnorr和DSA。
p k = ( p , g , h ) , h = g x m o d p , s k = x pk=(p,g,h),h=g^x \bmod p,sk=x pk=(p,g,h),h=gxmodp,sk=x
签名: a = g k m o d p , b = ( m − x a ) ∗ k − 1 m o d ( p − 1 ) a=g^k \bmod p,b=(m-xa)*k^{-1} \bmod (p-1) a=gkmodp,b=(m−xa)∗k−1mod(p−1),完整签名为: σ = ( a , b ) \sigma=(a,b) σ=(a,b)
验证: h a ⋅ a b = g m m o d p h^a·a^b=g^m \bmod p ha⋅ab=gmmodp
⑥Schnorr签名方案:1989年Schnorr提出了Elgmal方案的变形版,也就是Schnorr签名,可以大大缩短签名的长度。其安全性是基于离散对数困难性和哈希函数的单向性,特别适合于智能卡。该方案提出了一种有效的随机数求幂预处理算法,这种预处理使得签名生成非常快。它也提高了其他离散对数密码系统的效率。该方案的通信比特数量比其他方案少一半。
p k = ( p , g , h ) , h = g x m o d p , s k = x pk=(p,g,h),h=g^x \bmod p,sk=x pk=(p,g,h),h=gxmodp,sk=x
签名: a = g k m o d p ( 0 < k < p ) , r = H ( m ∣ ∣ a ) , b = ( k + r ∗ x ) m o d q , σ = ( r , b ) a=g^k \bmod p(0<k<p),r=H(m||a),b=(k+r*x) \bmod q,\sigma=(r,b) a=gkmodp(0<k<p),r=H(m∣∣a),b=(k+r∗x)modq,σ=(r,b)
验证: v = g b ⋅ h − r = g k + r ∗ x ⋅ g − r ∗ x = g k = a v=g^b·h^{-r}=g^{k+r*x}·g^{-r*x}=g^k=a v=gb⋅h−r=gk+r∗x⋅g−r∗x=gk=a, H ( m ∣ ∣ v ) = H ( m ∣ ∣ a ) H(m||v)=H(m||a) H(m∣∣v)=H(m∣∣a)
⑦DSA:1994年NIST将DSA采纳为标准,它是首个获政府认可的数字签名方案。它吸收了Schnorr签名方案的一些设计思想(不是在 ( Z p ∗ , ⋅ ) (Z_p^*,·) (Zp∗,⋅)上计算,而是在 ( Z p ∗ , ⋅ ) (Z_p^*,·) (Zp∗,⋅)的 q q q阶子群上计算),是Elgmal和Schnorr签名方案的混合体,只能用于数字签名。DSA和Schnorr的签名速度比ElGamal快6倍,并且能产生比ElGamal小6倍的签名。
与ElGamal方案的比较:
ElGamal签名 | DSA |
---|---|
r = g k m o d p r=g^k \bmod p r=gkmodp | r = ( g k m o d p ) m o d q r=(g^k \bmod p) \bmod q r=(gkmodp)modq |
b = ( m − x r ) ∗ k − 1 m o d ( p − 1 ) b=(m-xr)*k^{-1} \bmod (p-1) b=(m−xr)∗k−1mod(p−1) | b = ( H ( m ) + x r ) ∗ k − 1 m o d q b=(H(m)+xr)*k^{-1} \bmod q b=(H(m)+xr)∗k−1modq |
H H H函数为 S H A − 1 SHA-1 SHA−1。
优点:在模 p p p长度一样的情况下,DSA的签名长度比ElGamal和Schnorr都短。
缺点:首先,DSA评选过程不公开,不被公众所信任。其次,DSS最初建议使用 p = 512 b i t s p=512~bits p=512 bits的素数, q = 160 b i t s q=160~bits q=160 bits的素数,由于算法的安全性依赖于计算模数的离散对数的难度,在当时,512位勉强算得上安全。后来在众多的批评下,NIST将DSS的密钥提高为 512 512 512到 1024 1024 1024位。
⑧ECDSA:是DSA在椭圆曲线上的应用变形。
基于加密哈希函数的签名方案
⑨Lamport的一次性签名方案:是Rabin签名方案的改善,Rabin的方案要求发送方P在某个可信的公共存储库中存放一块数据 d d d(用来签名的数据,如果 d d d被窃取了,攻击者就可以伪造签名了)。这个公共存储库可以使得任何想要验证P的签名的人都可以读取 d d d,P可以在法律上证明这个数据 d d d是自己的,然后P就可以使用 d d d来构建数字签名。这样,在验证的时候,第三方就可以读取这个 d d d进行验证。而Lamport的方案不需要这么麻烦,他在签名后可以删去这个数据 d d d,第三方验证的时候不需要去读取这个私有数据。
但是,Lamport的签名方案也存在缺陷,那就是密钥和签名实在是太大了,而且,该方案存在严重的安全局限:每个密钥只能被用来签名一个消息。如果一个密钥被用来签名两个消息,这将很不安全。因为攻击者可以发起“混合搭配”攻击,成功伪造签署第三条签名者从未签名过的消息。在实际情况中,这种攻击的容易程度与签名的消息的相似程度以及签名的数量紧密相关。
⑩基于Chain的签名方案:Lamport的一次性签名方案无法用单一的私钥签名多条消息,基于Chain的签名方案允许签名者对任意多条消息进行签名。基于Chain的签名方案优于一次性签名方案之处在于:其对消息的签名不受公私钥对的数量的限制,需要的时候可以生成新的公私钥对,而一次性签名方案是事先根据消息的数量生成相应数量的公私钥对,如果这时候增加新的消息,就需要生成一个新的公私钥对进行分发。而且,一次性签名方案的公私钥对的长度与消息的数量成线性关系,效率相对低下。
⑪基于Tree的签名方案:在基于Chain的签名方案中,它的签名状态就像一棵度为 1 1 1,根为公钥的树,它签名的效率比较低下。将这棵树改造成一颗二叉树,其叶子节点用于验证消息 m m m,将大大改善验签的效率。
基于树的签名最典型的就是Merkle树,其优点在于:它可被认为是可以抵抗量子计算机算法的。如果可以构建量子计算机,则传统的公钥加密算法将变得不再安全,但由于基于Merkle树的签名方案取决于哈希函数的安全性,故它是可以抵抗的。