DSA数字签名的介绍以及针对DSA签名算法的攻击思路


前言

DSA(Digital Signature Algorithm)是一种用于数字签名的公钥密码算法,基于离散对数问题作为其数学基础。该算法被美国政府采用为其数字签名标准,并广泛应用于数字证书、电子商务和电子邮件等领域。公钥的分发是DSA数字签名算法中非常重要的一环。在实际应用中,需要根据具体情况选择合适的公钥分发方式,以确保公钥的安全性和真实性。如果公钥的选取存在一定的规律,这将给我们留下可乘之机。在正式介绍DSA之前我们先简单了解一下ElGamal签名算法来学习一下前置知识。


一、ElGamal是什么?

1.ElGamal基本原理

(只做签名算法介绍,正确性证明就不赘述了)

我们将(m,(r,s))打包发送给验签者

以下为验签过程:

二、DSA签名算法

上面所描述的 ElGamal 签名算法在实际中并不常用,更常用的是其变体 DSA。这里有几个原因:

①ElGamal 签名的长度通常比 DSA 长。这意味着在存储和传输时,ElGamal 签名会占用更多的空间

②DSA 在实际的签名和验证速度上通常比 ElGamal 更快。这是因为 DSA 经过了优化

1.DSA基本原理

同样将(H(m),(r,s))打包发送给验签者

验签过程如下:

2.正确性证明

3.DSA优缺点

优点:

  1. 安全性高:DSA算法基于离散对数问题,已被证明是一种安全的数字签名算法。只要私钥足够长,DSA算法就可以保证签名的安全性。
  2. 效率高:DSA算法的运算速度快,签名和验证的时间复杂度都是线性对数级别,因此可以在较短的时间内完成数字签名和验证。
  3. 公开性好:DSA算法的公钥可以公开,私钥需要保密。这种公开性好的特点使得DSA算法可以广泛应用于数字证书、电子商务等领域。
  4. 可验证性强:DSA算法的签名可以被公开验证,任何人都可以验证签名的合法性,从而保证了数字签名的可靠性和真实性。
  5. 应用广泛:DSA算法已经被广泛应用于数字证书、电子商务、电子邮件等领域,成为保证信息安全的重要手段之一。

缺点:   

  1. 密钥管理困难:DSA算法需要生成一对公私钥,私钥需要保密。因此,需要对私钥进行保护和管理,这对密钥管理提出了更高的要求。
  2. 依赖于随机数:DSA算法的安全性依赖于随机数的质量,如果随机数不够随机或者不够安全,就会导致签名的安全性受到威胁。
  3. 不支持加密:DSA算法只能用于数字签名,不能用于加密。如果需要同时进行加密和签名,就需要使用其他算法,如RSA算法等。

三、攻击示例

1.对私钥x的攻击

对私钥x的攻击本质上是解决一个DLP问题

让G为一个阿贝尔群(交换群).我们把G中的二元操作写成乘法*.

1)给定G,g和h=g^{a},计算a是困难的.

2)这里a就叫做h的以g为底的离散对数.

from Crypto.Util.number import *
import random
from gmpy2 import *

q = getPrime(32)
while True:
    t = 2*getPrime(10) * q
    if isPrime(t+1):
        p = t+1 #8361306460427
        break
        
h = random.randint(1, p-2)
g = pow(h,(p-1)//q, p)#1772320310198
y = pow(g, x, p)#4940794325855

分析本题发现p和q的长度都不是很大,我们可以通过BSGS(大步小步法)来进行简单的攻击。

BSGS大步小步法

BSGS用于求解离散对数问题,其时间复杂度为O(\sqrt{p}) , 空间复杂度为O(\sqrt{p})

题解

from gmpy2 import *
from Crypto.Util.number import *
from sympy.ntheory import *

p, g, y = 8361306460427,1772320310198,4940794325855
def bsgs(g, y, p):
    m = isqrt(p)+1
    S = {powmod(g, j, p): j for j in range(m)}
    gs = powmod(g, -m, p)
    for i in range(m):
        if y in S:
            return i * m + S[y]
        y = y * gs % p

x = bsgs(g, y, p)

print(x)

Pollard系列算法

Pollard-Rho和Pollard-lamda是如今解决DLP问题最高效和最常用的算法,其目的和大步小步法相同都是为了寻找碰撞,首先我们了解一下Pollard算法的原理。

1).Pollard-Rho

生日悖论启示我们,如果我们不断在某个范围内生成随机整数,很快便会生成到重复的数,期望大约在根号级别。

Pollard使用类似于f(x)=(x^{2}+c)modn的伪随机数生成器来生成一个伪随机数序列

虽然看起来十分随机,但当我们把N换成9400时生成的100个数变成了

因为每个数都是由前一个数决定的,可以生成的数又是有限的,那么迟早会进入循环。所以生成的序列常常形成这样的 ρ 形,这也是pollard-rho名称的由来。

通过上述分析,我们定义y=xx=(x^{2}+c)modng=gcd(\left | x-y \right |,n)为一步操作,

我们发现找到碰撞的几率出奇的高,根据国际上大佬的疯狂测试,在 n中找到一对y−x 使两者有公约数的概率接近n^{\frac{1}{4}}

判环

这种随机生成方法虽然优秀,但也有一些需要注意之处,比如有可能会生成一个环,并不断在这个环上生成以前生成过一次的数,所以我们必须写点东西来判环:

  1. 我们可以让y根据生成公式以比x快两倍的速度向后生成,这样当y再次与x相等时,x一定跑完了一个圈
  2. 我们可以用倍增的思想,让y记住x的位置,然后x再跑当前跑过次数的一倍的次数。这样不断让y记住x的位置,x再往下跑,因为倍增所以当x跑到y时,已经跑完一个圈,并且也不会多跑太多

from gmpy2 import *

print("y=g^x(modp)\n求x=log g (y)\n")
p=int(input('输入p='))
a=int(input('输入a='))
b=int(input('输入b='))
n=int(input('输入阶数n='))
#x序列生成函数
def fun_x(x):
    if (x % 3 ==1):
        x_next = x * b % p
    elif(x % 3 ==0):
        x_next = x ** 2 % p
    elif(x % 3 == 2):
        x_next = x * a % p
    return x_next
#a序列生成函数
def fun_a(a,x):
    if (x % 3 ==1):
        a_next = a
    elif(x % 3 ==0):
        a_next = a * 2 % (n)
    elif(x % 3 == 2):
        a_next = (a + 1) % n
    return a_next
#b序列生成函数
def fun_b(b,x):
    if (x % 3 ==1):
        b_next = (b + 1) % n
    elif(x % 3 ==0):
        b_next = b * 2 % n
    elif(x % 3 == 2):
        b_next = b
    return b_next

xn=[1]
an=[0]
bn=[0]
i=0

while True:
    #找出x[i]=x[2i]对应的i,用flag存储对应的2i的值
    xn.append(fun_x(xn[i]))
    an.append(fun_a(an[i],xn[i]))
    bn.append(fun_b(bn[i],xn[i]))
    if i >3 and i%2 ==0 and xn[i]==xn[int(0.5*i)]:
        print('i=',i,'\t','x[i]=x[2i]=',xn[i],'\n')
        break

    i += 1
flag = i

r = (bn[int(flag*0.5)]-bn[flag] )%n
reverse_r = gmpy2.invert(r,n) 
x= (reverse_r * (an[flag]-an[int(flag*0.5)] ))% n
print('离散对数为x=',x)

运行结果:

这种算法相比于BSGS,时间复杂度上同样是O(\sqrt{p}),但是它的空间复杂度表现要优秀得多。

2).Pollard-lambda
from Crypto.Util.number import *
import random
from gmpy2 import *

q = getPrime(160)
while True:
    t = 2*getPrime(1024-160) * q
    if isPrime(t+1):
        p = t+1 #170348437069105675857023420369852707107199919792133184452360056253809337668300908125853674590750392947050710727732463955472261258268602007044222277390154053179972622853725777103969911116086298972690976389030851269618265250128626615573340600153282617025741773802362792814749643535242494474688828746111924564347
        break
        
h = random.randint(1, p-2)
g = pow(h,(p-1)//q, p)  #165897807691210138242402562954872978913428506002086890183488722074489316352258963927481837058576723028094761779902049658188277585007037781070468378089752847174284810042043509739186585150021863898785139309655899362319277284062985948365352003897500446368706636436322274748243327625738381556542611629507069843590
y = pow(g, x, p)  #137261382691605813386002060971037192576990902114543987122319043412750149427553984596309208612435032869944636556014997562080112842853873993200670664292253373789312207761559974016876136547508448082441175904892258008489730428622596221443832423875069612364113749289160065933164504747254869418301359752739411555852

在这道题中我们发现p的比特长度已经来到了800多位,哪怕pollard-rho只需计算O(\sqrt{p})的复杂度,但是也还有30多位比特长度(通常认为20位以内比特长度可以计算)

实际上这是个没有被证明的算法,虽然它经常表现良好,下面给出参考资料供大家学习。

Monte Carlo Methods for Index Computation (mod p) By J. M. Pollard

2.对临时秘密k的攻击

1.已知k,直接得到x

from Crypto.Util.number import *

k = 516081660287290857303106041337362876434325919397
p, q, g, y, h, r, s = ...

x = (s*k - h) * inverse(r, q) % q
print(long_to_bytes(x))

2.爆破k尝试x

当k的范围已知且区间较小时,我们遍历k的取值,去尝试每一次解出的x是否符合题目要求。

from Crypto.Util.number import *
p, q, g, y, h, r, s = ...
for k in range(999900, 2**20):  # 999957
    x = (s*k - h) * inverse(r, q) % q
    if long_to_bytes(x).count(b'-') == 2:
        print(long_to_bytes(x))

3.共同k攻击

from Crypto.Util.number import *
from gmpy2 import *
import random, os
from hashlib import sha1

p, q, g, y=210145691501773578738377348033311975109097030624229264466788375283767533919326464465174636163332688722224581089450096402386769629298291083760904660611942708672827379160413284668161925839781496461631351601204438246469229385099502482935026018822981911090812522940670041497153171870525509599105379465080807836739,1198259187878727821038260641949026509111920270433,196070047258907618917994526171930725445041293594930451395813866076072912063450682578080857729624238303488600108328510272125657269217146364809009485238108834676036204139548225631723795405961936284355847299908680455823548490632994637690236187388967162639874250381038952479751971000815634264164616859879415711204,113892660154815927896065262713895897930759494276164055918916161302422466569511088272910136873131800900714081704069756811621255412857706485959053239368822240528099295009222226245910132521725945595438150866573053321959800267582969690297868819195328552315818030736239776841162267931134372314713955308734418315441
(h1, r1, s1) = 288608925920757761908146359265652114672439915971, 70578181835631077171027353972389074292538336310, 10138006399945530096316239936933893062913122896
(h2, r2, s2) = 292826952671130597654686220387088149924959682815, 70578181835631077171027353972389074292538336310, 601690991376973737187439170192344609140533841457

k=(h1-h2)*inverse(s1-s2,q)%q
x=(s1*k-h1)*inverse(r1,q)%q
print(long2str(x))

4.k之间存在其他关系

线性关系

除了上面k1=k2的条件以外,k1和k2存在其他线性关系时也可以将k破解

from Crypto.Util.number import *
from gmpy2 import *
import random, os
from hashlib import sha1

q = getPrime(160)
while True:
    t = 2*getPrime(1024-160) * q
    if isPrime(t+1):
        p = t+1
        break
        
h = random.randint(1, p-2)
g = pow(h, (p-1)//q, p)
y = pow(g, x, p)

k1 = random.randint(1, q-1)
h1 = bytes_to_long(sha1(os.urandom(20)).digest())
h2 = bytes_to_long(sha1(os.urandom(20)).digest())

r1 = pow(g, k1, p) % q
s1 = (h1 + x*r1) * inverse(k1, q) % q

k2 = 123123*k1 + 213123
r2 = pow(g, k2, p) % q
s2 = (h2 + x*r2) * inverse(k2, q) % q

p, q, g, y=248604602422910519027498055793201627104398247250051630675851204123046532565091226817355596701504144088229653099743376808895289529496613830342512762129413510736292280512125238597842272278759062316841642879931389189133077729867472969222059167595875408174942099331637968192646102751148170581062955479624795726803,1104888844039545494900451432921843691528361275421,179257043110102699137458477420103918554872480317804287592976758715562911175633485646459879116166908433112797107120185385797578936816467598792615613604506581942838034541566199558185747738124918980520382612117236824821289695971682490339435434475068166996448327818163252231908864987994621701200833277417006819871,159005646554391816923406887830979293509940608900317516799440723663025433835806129396573183140255812264469834382283265708315756408213239261422711978866078853083466087419416562334241054267280145144390020259686908268717687033486580768464112616050989149109970819378634556467410938187635262996429292198522330176893
(h1, r1, s1) = 1120509804671964533658263171593312514868852942664, 1092962395012982010125401435695438064740884031414, 870170073609429297690585216747619586014108208356
(h2, r2, s2) = 656005973206825385654744797203106611919992582404, 670071713626039247317315700175834986360787995677, 628224540815786948931138021701729862880600004465
a=123123
b=213123
k = (h1*r2 - h2*r1 + b*s2*r1) * inverse(s1*r2 - a*s2*r1, q) % q
x = (k*s1 - h1) * inverse(r1, q) %q
print(long_to_bytes(x))

平方关系

from Crypto.Util.number import *
from gmpy2 import *
import random, os
from hashlib import sha1

flag = b'NSSCTF{******}'
x = bytes_to_long(flag[7:-1])
q = getPrime(160)
while True:
    t = 2*getPrime(1024-160) * q
    if isPrime(t+1):
        p = t+1
        break
        
h = random.randint(1, p-2)
g = pow(h, (p-1)//q, p)
y = pow(g, x, p)

k1 = getPrime(64)
h1 = bytes_to_long(sha1(os.urandom(20)).digest())
h2 = bytes_to_long(sha1(os.urandom(20)).digest())

r1 = pow(g, k1, p) % q
s1 = (h1 + x*r1) * inverse(k1, q) % q

k2 = k1**2
r2 = pow(g, k2, p) % q
s2 = (h2 + x*r2) * inverse(k2, q) % q

p, q, g, y=149328490045436942604988875802116489621328828898285420947715311349436861817490291824444921097051302371708542907256342876547658101870212721747647670430302669064864905380294108258544172347364992433926644937979367545128905469215614628012983692577094048505556341118385280805187867314256525730071844236934151633203,829396411171540475587755762866203184101195238207,66574026244680091069518316704198662248616273869450543744119730925010741440009253221024269611702409992941009990810332788277765645172259247006372628490621965540198848398426564970679555614131868432980284836439911133867262442690193568710848637395672849408343310213459158340521055585119320384998345666167582343319,113160705347753807573510544961675737216163465780410091146961355899888740070887609372690358436441644814541660853774651122835982545831124611677315480368181887207540230682166723644914975259836636643623433521449469020855846403587460946141523029279424148079870427795307567353090927085416942020200370785081813797653
(h1, r1, s1) = 592632528802360292619666791193487295190074169778, 795968565942507694843762914966982971870045417145, 487794348943414262409771614228723658312500553511
(h2, r2, s2) = 1058580579731370773060403038853538332541169097744, 583365243104095833266690387387608816520877657593, 382650490310903109923939919364642244260295044662

分析上题我们的到关键信息k_{2} = k_{1}^{2},所以直接在sagemath上套用。

from Crypto.Util.number import *
from sympy.ntheory import *
p, q, g, y=149328490045436942604988875802116489621328828898285420947715311349436861817490291824444921097051302371708542907256342876547658101870212721747647670430302669064864905380294108258544172347364992433926644937979367545128905469215614628012983692577094048505556341118385280805187867314256525730071844236934151633203,829396411171540475587755762866203184101195238207,66574026244680091069518316704198662248616273869450543744119730925010741440009253221024269611702409992941009990810332788277765645172259247006372628490621965540198848398426564970679555614131868432980284836439911133867262442690193568710848637395672849408343310213459158340521055585119320384998345666167582343319,113160705347753807573510544961675737216163465780410091146961355899888740070887609372690358436441644814541660853774651122835982545831124611677315480368181887207540230682166723644914975259836636643623433521449469020855846403587460946141523029279424148079870427795307567353090927085416942020200370785081813797653
(h1, r1, s1) = 592632528802360292619666791193487295190074169778, 795968565942507694843762914966982971870045417145, 487794348943414262409771614228723658312500553511
(h2, r2, s2) = 1058580579731370773060403038853538332541169097744, 583365243104095833266690387387608816520877657593, 382650490310903109923939919364642244260295044662

PR.<x> = PolynomialRing(GF(q)) #创建多项式环

f = s2*x^2*r1 - s1*x*r2 - h2*r1 + h1*r2  #定义多项式

print(f.roots())
k = int(f.roots()[1][0])
x = (s1*k - h1) * inverse(r1, q) % q
print(long_to_bytes(x))


四、总结

        以上就是今天要讲的内容,本文仅仅简单介绍了DSA的使用和常见的攻击手段,而sagemath提供了大量能使我们快速便捷地处理数据的函数和方法,我们也将在下期提供sagemath的简单教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值