0%

离散对数 | DH | ElGamal

  • 注意:若公式显示不全,请您点击它以全部显示

离散对数 | DH | ElGamal

学习参考

引子

DH 和 ElGamal都是基于离散对数问题的。

离散对数(Discrete Logarithm)

在任何群G中可为所有整数k定义一个幂数为$b^k$,而离散对数$loga^{b}$是指使得 $b^{k}=a$的整数k。离散对数在一些特殊情况下可以快速计算。

而公钥密码学中几个加密算法的陷门是基于寻找离散对数的解的困难。

生成元

在一个群 G 中,如果 g 是 G 的生成元,即所有 G 中的所有元素都可以被表示成 $y=g^x$,此时的 x 称为 y 在 G 中的对数。

设 m≥1,gcd(a,m)=1,使得 $a^d≡1(\mod m)$ 成立的最小正整数 d 称为 a 对模 m 的阶。一般将其计为 δm(a)。

满足 $a^d≡1(\mod m) $的 d 一定满足 d∣φ(m)

原根¶

当 δm(a)=φ(m) 时,称 a 是模 m 的原根,简称 m 的原根。

只有$ m=2,4,p^α,2p^α$(p 为奇素数,α 为正整数)时,模 m 的剩余系存在原根。(充要条件)

以题代入

2020网鼎杯-青龙组-you raise me up
1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import *
import random

n = 2 ** 512
m = random.randint(2, n-1)
c = pow(m, bytes_to_long(flag), n)
print 'm = ' + str(m)
print 'c = ' + str(c)

# m = 391190709124527428959489662565274039318305952172936859403855079581402770986890308469084735451207885386318986881041563704825943945069343345307381099559075
# c = 6665851394203214245856789450723658632520816791621796775909766895233000234023642878786025644953797995373211308485605397024123180085924117610802485972584499

可知,flag被当作e了,即:$c = m^{flag} \mod n$

那朴素的想,这里最直接的方法就是求出在mod n的前提下,求出$\log{m}^{c}$

  • 看到n = 2**512,则:phi= 2**511

    即:phi易求


离散对数分解问题(DLP)

sage中的DLP相关函数

参数说明:

  • 求解以base为底
  • a的对数
  • ordbase的阶,可以忽略
  • operation可以是+*,默认为*
  • bounds是一个区间(ld,ud),需要保证所计算的对数在此区间内

函数使用

  • discrete_log(a,base,ord,operation)

    通用的求离散对数的方法(Pohlig-Hellman+?BSGS)

    (但没有bounds!)

    Pohig-Hellman算法是一种求解光滑阶循环群上的离散对数的方法。

    • 要求$a≡g^x(\mod p)$中 g必须是原根 (对于 g 不是原根的情况,需要利用原根将原方程转化成两个关于原根的离散对数问题。
    • 要求阶是光滑的(p-1可拆为多个素数相乘)
  • discrete_log_rho(a,base,ord,operation)

    求离散对数的Pollard-Rho算法

    Pollard Rho算法是一种随机性的概率型算法

    • 它主要解决有限的循环群上的离散对数问题。

    • Pollard Rho算法主要就是利用了循环群的生成序列呈现一个 ρ 字形状。

    • 适用于生成元的阶的素因子都是大数的情形

  • discrete_log_lambda(a,base,bounds,operation)

    求离散对数的Pollard-kangaroo算法(lambda算法

    • 适用于题目给出bound
  • bsgs(base,a,bounds,operation)

    大步小步法

    from sage.groups.generic import bsgs

    • 用于解高次同余方程的问题
    • 适用于模p 为质数的情况
    • 适用于题目给出bound

    原理介绍

    对求解离散对数问题$g^x ≡ h (\mod p)$,有

    $x = aT+b$, 找 T接近$\sqrt{\text{ord}(g)}$, 则:$g^{aT} ≡ h*g^{-b} (\mod p)$

    复杂度为$O\left (\sqrt{\text{ord}(g)} \right )$的meet in the middle.

    优化

    设$\text{ord}(g) = N = q_1 ^ {e_1} \cdot q_2 ^ {e_2} \cdots q_t ^ {e_t}$,又$g^N ≡ 1 (\mod p)$,有:$r_i = N \bmod q_i ^ {e_i}$

    记$Q_i = q_i ^ {e_i}$,则:
    $$
    \begin{aligned}h ^ {\frac{N}{Q_i}} &= (g ^ x) ^ {\frac{N}{Q_i}} \\ &= g ^ {k_i N} \cdot g ^ {r_i \frac{N}{Q_i}} \\ &= \big (g ^ \frac{N}{Q_i}\big)^{r_i }\end{aligned}
    $$
    将复杂度为$O\left (\sqrt{\text{ord}(g)} \right )$的离散对数问题,转为多个复杂度分别为$O\left (\sqrt{q_i ^ {e_i}} \right )$($\text ord\big (g ^ \frac{N}{Q_i}\big)$)的离散对数问题

    分别求解出$r_i$后CRT得到$x$ (根据x的大小范围可以减少离散对数问题求解数),总复杂度为$O\left(\sum _{i=1} ^ t \sqrt{q _i ^ {e _ i}} + \log N\right)$

  • 多项式DLP(暂未见到题,没什么了解)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #Sage
    def brute_dlp(gi, ci, n, lim):
    bi = gi
    for i in range(1, lim+1):
    if bi == ci:
    return i
    bi = (bi * gi) % n
    print("[-] NOT in the range")
    print("[-] Something's Wrong, you gotta check the range", lim)

    def pohlig_hellman(g, c, s, n, factors):
    res = []
    modulus = []
    for q, e in factors:
    assert pow(g, s//(q**e), n) != 1
    gi = pow(g, s//(q**e), n)
    ci = pow(c, s//(q**e), n)
    dlogi = brute_dlp(gi, ci, n, q**e)
    print("[+] dlog modulo {0} == {1}".format(q**e, dlogi))
    res.append(dlogi)
    modulus.append(q**e)
    print("\n[*] res = ", res)
    print("[*] modulus = ", modulus)
    dlog = CRT(res, modulus)
    print("\n[+] dlog modulo {0} == {1}".format(prod(modulus), dlog))
    return dlog

    p =
    P = PolynomialRing(Zmod(p), name='x')
    x = P.gen()
    n =
    g =
    nfactors = n.factor()
    s = 1
    for i in nfactors:
    s *= p**(i[0].degree()) - 1

    print(s)
    print(factor(s))
    qe =
    dlog = pohlig_hellman(g, c, s, n, qe)

    flag = bytes.fromhex(hex(dlog)[2:])
    print("\n[*] flag = ", flag.decode())

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#生成64位的素数p作为模数,int32位,超过int要在数字后加L
p=random_prime(2L**64)

#定义有限域GF(p)
G=GF(p)

#找一个模p的原根
gp ('znprimroot('+str(p)+')')
#输出Mod(rt,p),则x是模p的原根
g=G(rt)

#生成私钥
x=G(ZZ.random_element(p-1))

#公钥y=g^x mod p,由于已经定义在GF(p)上,因此g**x就是g^x mod p
y=g**x

#计算离散对数的通用方法
discrete_log(y,g)==x
#n为合数(Pohlig-Hellman)
x = discrete_log(mod(b,n),mod(a,n))
#n为质数或质数幂(线性筛Index Calculus)
R = Integers(99)
a = R(4)
b = a^9
b.log(a)
#或
x = int(pari(f"znlog({int(b)},Mod({int(a)},{int(n)}))"))
x = gp.znlog(b, gp.Mod(a, n))

#计算离散对数的lambda方法
discrete_log_lambda(y,g,(floor(ZZ(x)/2),2*ZZ(x)))==x

#小步大步法计算离散对数
bsgs(g,y,(floor(ZZ(x)/2),2*ZZ(x)))==x

回到题目,易得:

1
2
3
4
5
6
7
8
n = 2 ** 512
m = 391190709124527428959489662565274039318305952172936859403855079581402770986890308469084735451207885386318986881041563704825943945069343345307381099559075
c = 6665851394203214245856789450723658632520816791621796775909766895233000234023642878786025644953797995373211308485605397024123180085924117610802485972584499

m = Mod(m, n)
c = Mod(c, n)

discrete_log(c, m)

此外,针对Pohlig-Hellman,给出以下例题

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib
from Crypto.Util.number import *
secret = getPrime(128)
m = getPrime(128)
n = getPrime(1024)
c = pow(m, secret, n)
flag = "flag{"+hashlib.md5(str(secret).encode()).hexdigest()+"}"
print("m = %d" % m)
print("n = %d" % n)
print("c = %d" % c)
# m = 211060723077960779250044744266141176829
# n = 89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119974022969989259739901207135911877936187
# c = 32977205552939587326066964781587345932834856116807593874246357335611184723568494377764636367075033866504466671442418777279691941123340914970584204650416137940499348297233941269700668358058974528742129237158207741614517093787264725738282484698794547919205696467463725584733972956537910246421504887816202122161

求解的思路是很明确的,n是1024bits的素数,过大而无法直接调用sage求离散对数。

尝试分解n-1得到:

1
2
3
n = 89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119974022969989259739901207135911877936187
factor(n-1)
# 2 * 659 * 144811523 * 158091863 * 167642023 * 194814973 * 198114301509256817 * 1524198318315919100927 * 302061228405984819868188635839278249994068296319393442016901959084878254985929326557136330675603997640265462782948042782543029960066166632904951616712643712462381886167331227465971149482608610738439655060064241698902750467248492676743

n-1光滑,且有些数比较小。

从198114301509256817后调用sage能把内核跑挂,聚焦前面的数,发现CRT后结果在120bits左右(可以手动扩大到128bits)

利用Pohlig-Hellman的思路,有以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import hashlib
"""
n = 89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119974022969989259739901207135911877936187
factor(n-1)
# 2 * 659 * 144811523 * 158091863 * 167642023 * 194814973 * 198114301509256817 * 1524198318315919100927 * 302061228405984819868188635839278249994068296319393442016901959084878254985929326557136330675603997640265462782948042782543029960066166632904951616712643712462381886167331227465971149482608610738439655060064241698902750467248492676743
"""
# c = pow(m, secret, n)
# h = g^x mod p
def r(h, g, N, p, qi):
Zp = Zmod(p)
h = pow(h, N//qi, p)
g = pow(g, N//qi, p)
ri = discrete_log(Zp(h), Zp(g))
return int(ri)
m = 211060723077960779250044744266141176829
n = 89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119974022969989259739901207135911877936187
c = 32977205552939587326066964781587345932834856116807593874246357335611184723568494377764636367075033866504466671442418777279691941123340914970584204650416137940499348297233941269700668358058974528742129237158207741614517093787264725738282484698794547919205696467463725584733972956537910246421504887816202122161
tmp_list = [659, 144811523, 158091863, 167642023, 194814973]
r_list = []
for qi in tmp_list:
tmp = r(c,m,n-1,n,qi)
print(tmp)
r_list.append(tmp)
x = crt(r_list, tmp_list)

module = 1
for i in tmp_list:
module *= i
while True:
if int(x).bit_length()>128:
print('fail')
break
if int(pow(m, x, n))==c:
print('x =', x)
flag = "flag{"+hashlib.md5(str(x).encode()).hexdigest()+"}"
print(flag)
break
x += module
# x = 256148714020855512578941590011688772421
# flag{bbb04cf5180893e23e559c63ceae5b8f}

类似的,还有Crypto2022CTF上如下一道题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python3

from Crypto.Util.number import *
from flag import flag


def gen_primes(nbit, imbalance):
p = 2
FACTORS = [p]
while p.bit_length() < nbit - 2 * imbalance:
factor = getPrime(imbalance)
FACTORS.append(factor)
p *= factor
rbit = (nbit - p.bit_length()) // 2

while True:
r, s = [getPrime(rbit) for _ in '01']
_p = p * r * s
if _p.bit_length() < nbit: rbit += 1
if _p.bit_length() > nbit: rbit -= 1
if isPrime(_p + 1):
FACTORS.extend((r, s))
p = _p + 1
break

FACTORS.sort()
return (p, FACTORS)


def genkey(nbit, imbalance, e):
while True:
p, FACTORS = gen_primes(nbit // 2, imbalance)
if len(FACTORS) != len(set(FACTORS)):
continue
q, q_factors = gen_primes(nbit // 2, imbalance + 1)
if len(q_factors) != len(set(q_factors)):
continue
factors = FACTORS + q_factors
if e not in factors:
break
n = p * q
return n, (p, q)


nbit = 2048
imbalance = 19
e = 0x10001

m_1 = bytes_to_long(flag[:len(flag) // 2])
m_2 = bytes_to_long(flag[len(flag) // 2:])

n, PRIMES = genkey(nbit, imbalance, e)

c_1 = pow(m_1, e, n)
c_2 = pow(e, m_2, n)

print(f'n = {n}')
print(f'c_1 = {c_1}')
print(f'c_2 = {c_2}')
"""
n = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
c_1 = 488692928085934899944055554857568564903346089951134051486941368561567330884363274156339625953702601270565654444836193796061118053575538224794730472032345171432952984560662218697488844007827176184413713651118743456250147472678673801728916283759932987216388378211555067885210167894310696549664382751443669387953644382833924884208966436685137553434532738845959014828804809425096115758364136546390809453200055265653531950423111482644330073443545410319576097902472017235065047191133112557289289189187696092145850680765843608118584107829268136014912479701945735063525799796920293418182776436767911172221104640501952880057
c_2 = 109770827223661560471527567179288748906402603483328748683689436879660543465776899146036833470531024202351087008847594392666852763100570391337823820240726499421306887565697452868723849092658743267256316770223643723095601213088336064635680075206929620159782416078143076506249031972043819429093074684182845530529249907297736582589125917235222921623698038868900282049587768700860009877737045693722732170123306528145661683416808514556360429554775212088169626620488741903267154641722293484797745665402402381445609873333905772582972140944493849645600529147490903067975300304532955461710562911203871840101407995813072692212
"""

显然,利用Pollard‘s p-1方法分解n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Pollard‘s p-1 method
# https://blog.csdn.net/m0_62506844/article/details/125774485
from gmpy2 import *
N = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
a = 2
n = 2
while True:
a = powmod(a, n, N)
res = gcd(a-1, N)
if res != 1 and res != N:
q = N // res
print("p=",res)
print("q=",q)
break
n += 1

其后,考察离散对数

根据p、q光滑的性质与flag大小关系,可简化运算

可以先分别求出$m_2 \mod (p-1)$与$m_2 \mod (q-1)$的值,再CRT求出$m_2$

也观察到,对比特位而言p>m2=m1,所以有$m_2 = m_2 \mod (p-1)$, 无需CRT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *

N = 7069789930583271525053215046247773438899869283661158227309691853515987055334306019600324056376312479212090202373516405860759222837585952590589336295698718699890424169542280710721069784487366121478569760563045886361884895363592898476736269784284754788133722060718026577238640218755539268465317292713320841554802703379684173485217045274942603346947299152498798736808975912326592689302969859834957202716983626393365387411319175917999258829839695189774082810459527737342402920881184864625678296442001837072332161966439361793009893108796934406114288057583563496587655548536011677451960307597573257032154009427010069578913
c_1 = 488692928085934899944055554857568564903346089951134051486941368561567330884363274156339625953702601270565654444836193796061118053575538224794730472032345171432952984560662218697488844007827176184413713651118743456250147472678673801728916283759932987216388378211555067885210167894310696549664382751443669387953644382833924884208966436685137553434532738845959014828804809425096115758364136546390809453200055265653531950423111482644330073443545410319576097902472017235065047191133112557289289189187696092145850680765843608118584107829268136014912479701945735063525799796920293418182776436767911172221104640501952880057
c_2 = 109770827223661560471527567179288748906402603483328748683689436879660543465776899146036833470531024202351087008847594392666852763100570391337823820240726499421306887565697452868723849092658743267256316770223643723095601213088336064635680075206929620159782416078143076506249031972043819429093074684182845530529249907297736582589125917235222921623698038868900282049587768700860009877737045693722732170123306528145661683416808514556360429554775212088169626620488741903267154641722293484797745665402402381445609873333905772582972140944493849645600529147490903067975300304532955461710562911203871840101407995813072692212
e = 65537

p = 83408372012221120677052349409462320990177094246143674474872152829440524098582262384066400107950985845255268335597502228206679771838750219696329523257176739436871327238322817403970284015587320158034304282786944710043150568360761457471641695390427267786485448748458445872307883254297662715749746270343116946519

q = N // p

Zp, Zq = Zmod(p), Zmod(q)
d = inverse_mod(e, (p-1)*(q-1))
m_1 = int(pow(c_1, d, N))
print(long_to_bytes(m_1).decode(),end='')

m_2p = discrete_log(Zp(c_2), Zp(e))
# m_2q = discrete_log(Zq(c_2), Zq(e))
# m_2 = crt([m_2p, m_2q], [p-1, q-1])
print(long_to_bytes(m_2p).decode())
# CCTF{5L3Ek_4s__s1lK__Ri9H7?!}

接下来步入主题——DH

密钥交换协议 (Diffie Hellman)

Diffie Hellman

以两位发明者命名的密码方案,其思想为理解ElGamal加密奠定了基础。

总体而言,密钥交换协议不是一种加密明文的算法,而是生成一个较为安全的密钥的算法

流程

  1. 公布一个质数q(最好是2048位),然后在Zp中找到一个生成元g,也公布出来
  2. Alice选一个私钥$X_A$,$X_A∈Z_{p−1}$,得到公钥$Y_A = g^{X_A} \mod q$,并将$Y_A$传给Bob
  3. Bob也选好私钥$X_B$, 得到公钥$Y_B=g^{X_B} \mod q$发给Alice
  4. 公布YA、YB
  5. 收到YB的Alice可以得到$K=(Y_B)^{X_A}=(g^{Y_B})^{X_A}=g^{X_A*Y_B} \mod q$
  6. 收到YA的Bob也可以得到$K=(Y_A)^{X_B}=(g^{Y_A})^{X_B}=g^{X_A*X_B} \mod q$

显然Alice和Bob得到了一份相同的信息,而对于攻击者而言,由YA推出XA是困难的,同理也无法得到XB,所以攻击者无法得知这相同的信息

能不能用这性质直接传递密文呢?

显然是不能的

Alice和Bob无法控制最后K的内容,所以K更适合当作其他加密体系的私钥

中间人攻击

值得一说,密钥交换协议的数学理论是自洽的、安全的

也就是说,中间人攻击不是从数学层面上硬核破解出Alice和Bob所生成的私钥

而是以欺诈的手段,下面先看一张攻击的流程图

中间人攻击

仔细说一下

  1. 攻击者能知道g和q,于是自己随意生成两个私钥$X_{D_1}、X_{D_2}$,并用其生成两个公钥

    • $Y_{D_1} = a^{X_{D_1}} \mod q$
    • $Y_{D_2} = a^{X_{D_2}} \mod q$
  2. 在Alice对外公布$Y_A$时,攻击者截获它(不让Bob收到$Y_A$),然后:

    不能让Bob收不到东西,攻击者给Bob传输$Y_{D_1}$

  3. 同理,攻击者截获Bob发的$Y_B$,然后给Alice发$Y_{D_2}$

  4. Alice以为自己收到的是Bob的,于是开始生成

    $K_2=(Y_{D_2})^{X_A}=(g^{X_{D_2}})^{X_A} = g^{X_{D_2}*X_A}$

  5. 而对于攻击者,则有:

    $K_2=(Y_{A})^{X_{D_2}}=(g^{X_{A}})^{X_{D_2}} = g^{X_{D_2}*X_A}$

    即:攻击者获得了Alice所生成的、用来加密的密钥,也就可以破解Alice后续发的密文了

  6. 同理,Bob也有如下操作

    $K_1=(Y_{D_1})^{X_B}=(g^{X_{D_1}})^{X_B} = g^{X_{D_1}*X_B}$

  7. 而对于攻击者,也有:

    $K_2=(Y_{B})^{X_{D_1}}=(g^{X_{B}})^{X_{D_1}} = g^{X_{D_1}*X_B}$

    同样的,Bob用这个DH协议所生成的密钥加密的密文 都能被攻击者破解

值得一说,若攻击者不想让Alice与Bob察觉异样,他也能做到破解完密文后,修改明文后再加密发给另一方

而DH协议能被破解的关键则在于Alice和Bob无法确认信息的发送方,所以攻击者充当了中转站的角色分别与A、B进行DH协议

更具体的实现、以及实际渗透应用这里暂且省略

ElGamal加密算法

ElGamal

同样是以发明者命名的算法。

简介

ElGamal加密算法是一个基于DH密钥交换非对称加密算法

它在1985年由塔希尔·盖莫尔提出,GnuPG和PGP等很多密码学系统中都应用到了ElGamal算法。

ElGamal加密算法可以定义在任何循环群G上,安全性取决于G上的离散对数难题。

加 \ 解密流程

流程学习参考lazzro相关博客

代码学习参考4XWi11师傅相关博客

这里依然以Alice与Bob举例,即:Alice打算给Bob发消息

加密

Bob

  1. 选择大素数p、其生成元g,以及自己的私钥x(1<x<p−1)
  2. 生成$y ≡ g^x \mod p$
  3. 公布公钥为(y,g,p)

Alice

  1. 处理一下明文格式,对明文分组,使得每组m小于十进制下的p(这里为方便理解,假设只有1组)
  2. 随机选择r(1< ri < p−1)
  3. 生成
    • $c ≡ g^r \mod p$
    • $c’ ≡ m * y^r \mod p$
  4. 公布密文C(c, c’)

解密

Bob

$m ≡ c’ *(c^x)^{-1} \mod p$

证明

Elgamal解密证明

类比

过了一遍流程后,发现Elgamal确实是基于DH协议的

相当于DH协议产生好私密的密钥K后,采用加密方式$C = m * K \mod p$,解密即:$m = C*K^{-1} \mod p$

正常而言,攻击者在不充当中间人的前提下无法得知K,也就无法得知$K^{-1}$,从而无法解出m

换句话说,收到消息后的Bob还可以用K加密他给Alice的回复,这同样也是安全的

再换言之,现实世界里中间人攻击依旧对Elgamal加密有效

(但ctf题目采用不了此方法,只能寄希望于特殊情况的离散对数可求解)

总流程

贴一下la佬的干练精简博客相关内容

image-20221107095832290

实现代码

小改了下来自4XWi11师傅的代码

为与DH协议承上启下,Elgamal加解密过程 相关变量名 是按DH协议命名的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# sage

from Crypto.Util.number import getPrime, bytes_to_long as b2n, long_to_bytes as n2b
from gmpy2 import invert
from random import randint

def find_t(a, m, n):
_y = 1
while m > 0:
r = m % 2
if r == 1:
_y = (_y * a) % n
a = a * a % n
m = m // 2
return _y


def primitive_root(pi): # 找对于模p的有效生成元g
for a in range(2, pi):
flag = 1
for i in range(1, pi):
if find_t(a, i, pi) == 1 and i < pi - 1:
flag = 0
elif flag and find_t(a, i, pi) == 1 and i == pi - 1:
return a


q = getPrime(16)
print("素数p :", q)
g = primitive_root(q)
print("生成元g是 : ", g)

# key generation
XA = randint(1, q)
XB = randint(1, q)

YA = pow(g, XA, q)
YB = pow(g, XB, q)

pk = [q, g, YA] # 公钥
sk = XA # 私钥

# encryption
flag = b"f"
m = b2n(flag)
print(flag)
print(m)

ci = pow(g, XB, q) # 密文第一部分
ciphertext = m * pow(YA, XB, q) % q # Bob用自己的密钥XB来加密(密文第二部分)

# decryption
mi = ciphertext * invert(pow(ci, XA, q), q) % q # Alice用自己的密钥XA即可解密
print(n2b(mi))

Elgamal数字签名

签名流程

Elgamal数字签名

没看懂签名🤔

A要发东西,A签名后产生了r,s

那么也就是其他人利用验证里公式能确定这就是A发的密文,但验证里有m,除A之外的其他人不是不知道m吗?

那是怎么验证的?

或许是说,A还得公布$g^m$?

看完攻击后,我明白了(说到底是没明白数字签名的意义何在)签名中的m是公开的

签名的直接目的不是为了加密、保证安全性,而是让人能确定这m就是你所生成、你所发出的

至于实际应用层面,我想可以直接把上文中的m换成Elgamal加密所得的密文,对此签名即可避免中间人攻击

相关攻击

Elgamal数字攻击

第一次学习具体的数字签名以及攻击、构造验证,比较迟钝哈哈

这里构造验证的意义在于,在不知道私钥r(你不是A)的前提下,也能发出你的m、签名,让别人验证后,觉得这就是A发出来的(你就是A)

但仔细思考,这构造出的m是不由自己控制的,也就是说虽然别人认可这是A发的,但读起来是乱码,构造验证便也没有特别直接的意义(CTF上倒是能拿此出题)

但,话说回来,如果是人与人之间交互

另一方验证完后,直接把你当作A了,然后再进行的交流则意义不菲

(完)