概述
基本原理
密钥生成
- 选择一个合适的哈希函数,目前一般选择 SHA1,当前也可以选择强度更高的哈希函数 H。
- 选择密钥的长度 L 和 N,这两个值决定了签名的安全程度。在最初的 DSS( Digital Signature Standard )中建议 L 必须为 64 的倍数,并且 512 ≤ L ≤ 1024 512≤L≤1024 512≤L≤1024 ,当然,也可以更大。N 大小必须不大于哈希函数 H 输出的长度。FIPS 186-3 给出了一些建议的 L 和 N 的取值例子:(1024, 160), (2048, 224), (2048, 256),以及 (3,072, 256)。
- 选择 N 比特的素数 q。
- 选择 L 比特的素数 p,使得 p-1 是 q 的倍数。
- 选择满足 g k ≡ 1 m o d p g^k≡1mod p gk≡1modp 的最小正整数 k 为 q 的 g,即在模 p 的背景下,ord(g)=q 的 g。即 g 在模 p 的意义下,其指数次幂可以生成具有 q 个元素的子群。这里,我们可以通过计算 g = h p − 1 q m o d p g=h^{frac{p−1}{q}}mod p g=hqp−1modp 来得到 g,其中 1<h<p−1 。
- 选择私钥 x, 0<x<q ,计算
y
≡
g
x
m
o
d
p
y≡g^xmod p
y≡gxmodp ,公钥为 (p,q,g,y),私钥为 (x)。
签名
签名步骤如下 - 选择随机整数数 k 作为临时密钥, 0<k<q 。
- 计算 r ≡ ( g k m o d p ) m o d q r ≡ (g^kmod p)mod q r≡(gkmodp)modq
- 计算
s
≡
(
H
(
m
)
+
x
r
)
k
−
1
m
o
d
q
s≡(H(m)+xr)k^{−1}mod q
s≡(H(m)+xr)k−1modq
签名结果为 (r,s)。需要注意的是,这里与 Elgamal 很重要的不同是这里使用了哈希函数对消息进行了哈希处理。
验证
验证过程如下 - 计算辅助值, w = s − 1 m o d q w=s^{−1}mod q w=s−1modq
- 计算辅助值, u 1 = H ( m ) s − 1 m o d q u_1=H(m)s^{−1}mod q u1=H(m)s−1modq
- 计算辅助值, u 2 = r s − 1 m o d q u2=rs^{-1}mod q u2=rs−1modq
- 计算 v = ( g u 1 y u 2 m o d p ) m o d q v=(g^{u_1}y^{u_2}mod p)mod q v=(gu1yu2modp)modq
- 如果 v 与 r 相等,则校验成功。
正确性推导
首先,g 满足
g
k
≡
1
m
o
d
p
g^k≡1mod p
gk≡1modp 的最小正整数 k 为 q。所以
g
q
≡
1
m
o
d
p
g^q≡1mod p
gq≡1modp。
所以
g
x
≡
(
g
x
m
o
d
q
)
m
o
d
p
g^x≡(g^xmod q)mod p
gx≡(gxmodq)modp 。进而
v
=
(
g
u
1
y
u
2
m
o
d
p
)
m
o
d
q
=
g
u
1
g
x
u
2
≡
g
H
(
m
)
s
−
1
g
x
r
s
−
1
≡
g
(
H
(
m
)
+
x
r
)
s
−
1
≡
g
k
v=(g^{u1}y^{u_2}mod p)mod q =g^{u_1}g^{xu_2}≡g^{H(m)s^{-1}}g^{xrs^{-1}}≡g^{(H(m)+xr)s^{-1}} ≡g^k
v=(gu1yu2modp)modq=gu1gxu2≡gH(m)s−1gxrs−1≡g(H(m)+xr)s−1≡gk
安全性
如果知道了随机密钥 k,那么我们就可以根据
s
≡
(
H
(
m
)
+
x
r
)
k
−
1
m
o
d
q
s≡(H(m)+xr)k^{−1}mod q
s≡(H(m)+xr)k−1modq 计算私钥 d,几乎攻破了 DSA。
这里一般情况下,消息的 hash 值都会给出。
x
≡
r
−
1
(
k
s
−
H
(
m
)
)
m
o
d
q
x≡r^{−1}(ks−H(m))mod q
x≡r−1(ks−H(m))modq
题目
题目大部分都是基于上面那个式子进行各种数学关系推导,最后求x
DSA本身是基于离散岁数问题的困难性设计的,所以曾经尝试过用discrete_log但失败了
[Imaginaryctf round30]Easy DSA: The beginning
from Crypto.Util.Padding import pad
from Crypto.Util.number import isPrime, getPrime, long_to_bytes
from Crypto.Cipher import AES
from hashlib import sha256
from random import randrange
def gen_key():
p = 0
while not isPrime(p):
q = getPrime(300)
p = 2*q + 1
g = randrange(2, p)**2 % p
k = randrange(2, q)
x = randrange(2, q)
y = pow(g, x, p)
return p, q, g, x, y, k
def H(msg):
return int.from_bytes(sha256(msg).digest(), 'big')
def sign(m):
r = pow(g, k, p) % q
s = (H(m) + x*r) * pow(k, -1, q) % q
return r, s
def verify(m, r, s):
assert 0 < r < q and 0 < s < q
u = pow(s, -1, q)
v = pow(g, H(m) * u, p) * pow(y, r * u, p) % p % q
return v == r
flag = b"ictf{REDACTED}"
p, q, g, x, y, k = gen_key()
ms = b"jctf{powered_by_caffeine}", b"jctf{totally_real_flag}"
sigs = [sign(m) for m in ms]
assert all(verify(m, *sig) for m, sig in zip(ms, sigs))
aes = AES.new(long_to_bytes(x)[:16], AES.MODE_CBC, b'