Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/attacks/hnp/lattice_attack.py
2589 views
1
import os
2
import sys
3
4
from sage.all import QQ
5
from sage.all import ZZ
6
from sage.all import matrix
7
from sage.all import vector
8
9
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(os.path.abspath(__file__)))))
10
if sys.path[1] != path:
11
sys.path.insert(1, path)
12
13
from shared.lattice import shortest_vectors
14
15
16
def attack(a, b, m, X):
17
"""
18
Solves the hidden number problem using an attack based on the shortest vector problem.
19
The hidden number problem is defined as finding y such that {xi = {aij * yj} + bi mod m}.
20
:param a: the aij values
21
:param b: the bi values
22
:param m: the modulus
23
:param X: a bound on the xi values
24
:return: a generator generating tuples containing a list of xi values and a list of yj values
25
"""
26
assert len(a) == len(b), "a and b lists should be of equal length."
27
28
n1 = len(a)
29
n2 = len(a[0])
30
B = matrix(QQ, n1 + n2 + 1, n1 + n2 + 1)
31
for i in range(n1):
32
for j in range(n2):
33
B[n1 + j, i] = a[i][j]
34
35
B[i, i] = m
36
B[n1 + n2, i] = b[i] - X // 2
37
38
for j in range(n2):
39
B[n1 + j, n1 + j] = X / QQ(m)
40
41
B[n1 + n2, n1 + n2] = X
42
43
for v in shortest_vectors(B):
44
xs = [int(v[i] + X // 2) for i in range(n1)]
45
ys = [(int(v[n1 + j] * m) // X) % m for j in range(n2)]
46
if all(y != 0 for y in ys) and v[n1 + n2] == X:
47
yield xs, ys
48
49
50
def dsa_known_msb(n, h, r, s, k):
51
"""
52
Recovers the (EC)DSA private key and nonces if the most significant nonce bits are known.
53
:param n: the modulus
54
:param h: a list containing the hashed messages
55
:param r: a list containing the r values
56
:param s: a list containing the s values
57
:param k: a list containing the partial nonces (PartialIntegers)
58
:return: a generator generating tuples containing the possible private key and a list of nonces
59
"""
60
assert len(h) == len(r) == len(s) == len(k), "h, r, s, and k lists should be of equal length."
61
a = []
62
b = []
63
X = 0
64
for hi, ri, si, ki in zip(h, r, s, k):
65
msb, msb_bit_length = ki.get_known_msb()
66
shift = 2 ** ki.get_unknown_lsb()
67
a.append([(pow(si, -1, n) * ri) % n])
68
b.append((pow(si, -1, n) * hi - shift * msb) % n)
69
X = max(X, shift)
70
71
for k_, x in attack(a, b, n, X):
72
yield x[0], [ki.sub([ki_]) for ki, ki_ in zip(k, k_)]
73
74
75
def dsa_known_lsb(n, h, r, s, k):
76
"""
77
Recovers the (EC)DSA private key and nonces if the least significant nonce bits are known.
78
:param n: the modulus
79
:param h: a list containing the hashed messages
80
:param r: a list containing the r values
81
:param s: a list containing the s values
82
:param k: a list containing the partial nonces (PartialIntegers)
83
:return: a generator generating tuples containing the possible private key and a list of nonces
84
"""
85
assert len(h) == len(r) == len(s) == len(k), "h, r, s, and k lists should be of equal length."
86
a = []
87
b = []
88
X = 0
89
for hi, ri, si, ki in zip(h, r, s, k):
90
lsb, lsb_bit_length = ki.get_known_lsb()
91
inv_shift = pow(2 ** lsb_bit_length, -1, n)
92
a.append([(inv_shift * pow(si, -1, n) * ri) % n])
93
b.append((inv_shift * pow(si, -1, n) * hi - inv_shift * lsb) % n)
94
X = max(X, 2 ** ki.get_unknown_msb())
95
96
for k_, x in attack(a, b, n, X):
97
nonces = [ki.sub([ki_]) for ki, ki_ in zip(k, k_)]
98
yield x[0], nonces
99
100
101
def dsa_known_middle(n, h1, r1, s1, k1, h2, r2, s2, k2):
102
"""
103
Recovers the (EC)DSA private key and nonces if the middle nonce bits are known.
104
This is a heuristic extension which might perform worse than the methods to solve the Extended Hidden Number Problem.
105
More information: De Micheli G., Heninger N., "Recovering cryptographic keys from partial information, by example" (Section 5.2.3)
106
:param n: the modulus
107
:param h1: the first hashed message
108
:param r1: the first r value
109
:param s1: the first s value
110
:param k1: the first partial nonce (PartialInteger)
111
:param h2: the second hashed message
112
:param r2: the second r value
113
:param s2: the second s value
114
:param k2: the second partial nonce (PartialInteger)
115
:return: a tuple containing the private key, the nonce of the first signature, and the nonce of the second signature
116
"""
117
k_bit_length = k1.bit_length
118
assert k_bit_length == k2.bit_length
119
lsb_unknown = k1.get_unknown_lsb()
120
assert lsb_unknown == k2.get_unknown_lsb()
121
msb_unknown = k1.get_unknown_msb()
122
assert msb_unknown == k2.get_unknown_msb()
123
K = 2 ** max(lsb_unknown, msb_unknown)
124
l = k_bit_length - msb_unknown
125
126
a1 = k1.get_known_middle()[0] << lsb_unknown
127
a2 = k2.get_known_middle()[0] << lsb_unknown
128
t = -(pow(s1, -1, n) * s2 * r1 * pow(r2, -1, n))
129
u = pow(s1, -1, n) * r1 * h2 * pow(r2, -1, n) - pow(s1, -1, n) * h1
130
u_ = a1 + t * a2 + u
131
132
B = matrix(ZZ, 5, 5)
133
B[0] = vector(ZZ, [K, K * 2 ** l, K * t, K * t * 2 ** l, u_])
134
B[1] = vector(ZZ, [0, K * n, 0, 0, 0])
135
B[2] = vector(ZZ, [0, 0, K * n, 0, 0])
136
B[3] = vector(ZZ, [0, 0, 0, K * n, 0])
137
B[4] = vector(ZZ, [0, 0, 0, 0, n])
138
139
A = matrix(ZZ, 4, 4)
140
b = []
141
for row, v in enumerate(shortest_vectors(B)):
142
A[row] = v[:4].apply_map(lambda x: x // K)
143
b.append(-v[4])
144
if row == A.nrows() - 1:
145
break
146
147
assert len(b) == 4
148
x1, y1, x2, y2 = A.solve_right(vector(ZZ, b))
149
assert (x1 + 2 ** l * y1 + t * x2 + 2 ** l * t * y2 + u_) % n == 0
150
151
k1 = k1.sub([int(x1), int(y1)])
152
k2 = k2.sub([int(x2), int(y2)])
153
private_key1 = (pow(r1, -1, n) * (s1 * k1 - h1)) % n
154
private_key2 = (pow(r2, -1, n) * (s2 * k2 - h2)) % n
155
assert private_key1 == private_key2
156
return int(private_key1), int(k1), int(k2)
157
158