kryptools 1.1__tar.gz → 1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryptools-1.1 → kryptools-1.2}/PKG-INFO +3 -5
- {kryptools-1.1 → kryptools-1.2}/README.md +2 -4
- kryptools-1.2/kryptools/GF2.py +351 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/Zmod.py +16 -10
- {kryptools-1.1 → kryptools-1.2}/kryptools/__init__.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/aes.py +8 -4
- {kryptools-1.1 → kryptools-1.2}/kryptools/des.py +2 -2
- {kryptools-1.1 → kryptools-1.2}/kryptools/dlp.py +3 -3
- {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_bsgs.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_ic.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_qs.py +3 -3
- {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_rho.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_dix.py +3 -2
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_ecm.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_fmt.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_pm1.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_qs.py +5 -4
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor_rho.py +1 -1
- {kryptools-1.1 → kryptools-1.2}/kryptools/keccak.py +9 -5
- {kryptools-1.1 → kryptools-1.2}/kryptools/la.py +107 -2
- {kryptools-1.1 → kryptools-1.2}/kryptools/poly.py +68 -8
- {kryptools-1.1 → kryptools-1.2}/kryptools/sha1.py +16 -12
- {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/PKG-INFO +3 -5
- {kryptools-1.1 → kryptools-1.2}/pyproject.toml +1 -1
- {kryptools-1.1 → kryptools-1.2}/tests/test_GF2.py +5 -1
- {kryptools-1.1 → kryptools-1.2}/tests/test_Zmod.py +2 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_sha1.py +1 -1
- kryptools-1.1/kryptools/GF2.py +0 -281
- {kryptools-1.1 → kryptools-1.2}/LICENSE +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/blockcipher.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/ec.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/factor.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/intfuncs.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/lat.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/nt.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools/primes.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.1 → kryptools-1.2}/setup.cfg +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_aes.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_des.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_dlog.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_ec.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_factor.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_factor_qs.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_intfuncs.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_keccak.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_la.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_lat.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_nt.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_poly.py +0 -0
- {kryptools-1.1 → kryptools-1.2}/tests/test_primes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
4
4
|
Summary: Implemenation of same basic algorithms used in cryptography.
|
|
5
5
|
Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
|
|
6
6
|
License: MIT
|
|
@@ -49,10 +49,8 @@ The tools contained are:
|
|
|
49
49
|
* AESCipher: individual AES operations
|
|
50
50
|
* DESCipher: individual DES operations
|
|
51
51
|
|
|
52
|
-
* SHA1
|
|
53
|
-
* Keccak individual sponge operations
|
|
54
|
-
* SHA3
|
|
55
|
-
* SHAKE
|
|
52
|
+
* SHA1: custom initial state, padding function
|
|
53
|
+
* Keccak: individual sponge operations, SHA3, SHAKE
|
|
56
54
|
|
|
57
55
|
Documentation can be found in the jupyter notebook
|
|
58
56
|
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -31,10 +31,8 @@ The tools contained are:
|
|
|
31
31
|
* AESCipher: individual AES operations
|
|
32
32
|
* DESCipher: individual DES operations
|
|
33
33
|
|
|
34
|
-
* SHA1
|
|
35
|
-
* Keccak individual sponge operations
|
|
36
|
-
* SHA3
|
|
37
|
-
* SHAKE
|
|
34
|
+
* SHA1: custom initial state, padding function
|
|
35
|
+
* Keccak: individual sponge operations, SHA3, SHAKE
|
|
38
36
|
|
|
39
37
|
Documentation can be found in the jupyter notebook
|
|
40
38
|
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Galois field GF(2^n).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import gcd
|
|
6
|
+
from .poly import Poly
|
|
7
|
+
from .Zmod import Zmod
|
|
8
|
+
from .factor import factorint
|
|
9
|
+
from .conway_polynomials import conway_polynomials2
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GF2:
|
|
13
|
+
"""
|
|
14
|
+
Create a Galois field GF(2^n).
|
|
15
|
+
|
|
16
|
+
Elements of a Galois filed are specified by integers from 0 to 2**n-1, which are
|
|
17
|
+
identified with polynomials whose coefficients are the binary digits of the integer.
|
|
18
|
+
Multiplication is polynomial multiplication modulo a given irreducible polynomial.
|
|
19
|
+
If the polynomial has the n'th bit set, the lowest bit corresponds to the constant coefficient
|
|
20
|
+
(like in AES). Otherwise, the bit order is reversed (like for Ghash). The default modulus is
|
|
21
|
+
choosen from a list of Conway polynomials.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
|
|
25
|
+
To define the Galois field GF(2^8) use
|
|
26
|
+
>>> gf=GF2(8)
|
|
27
|
+
|
|
28
|
+
To declare 3 as an element of our Galois field use (elements are displayed as hex numbers)
|
|
29
|
+
>>> gf(3)
|
|
30
|
+
03
|
|
31
|
+
|
|
32
|
+
The usual arithmetic operations are supported.
|
|
33
|
+
>>> gf(2) * gf(123)
|
|
34
|
+
f6
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
def __init__(self, n: int, modulus: int | None = None):
|
|
38
|
+
if not isinstance(n, int) or n < 1:
|
|
39
|
+
raise ValueError(f"{n} is not a positive integer.")
|
|
40
|
+
if not modulus:
|
|
41
|
+
if n in conway_polynomials2:
|
|
42
|
+
modulus = conway_polynomials2[n]
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError("Unknown power. Please specify a modulus.")
|
|
45
|
+
self.modulus = modulus # integer whose biniary digits are the coefficients of the irreducible modulus polynomial
|
|
46
|
+
if modulus & 2**n == 2**n:
|
|
47
|
+
self.bitreversed = False
|
|
48
|
+
else:
|
|
49
|
+
self.bitreversed = True
|
|
50
|
+
self.power = n # n
|
|
51
|
+
self.order = 2**n # 2**n
|
|
52
|
+
self.print_hex: bool = True
|
|
53
|
+
self.byteorder: str = "big" # big|little
|
|
54
|
+
self.mult_order = self.order -1
|
|
55
|
+
self.factors = {} # factoring of the group order
|
|
56
|
+
|
|
57
|
+
def __repr__(self):
|
|
58
|
+
return f"GF(2^{self.power})"
|
|
59
|
+
|
|
60
|
+
def __call__(self, x: int | list | tuple):
|
|
61
|
+
if isinstance(x, list|tuple):
|
|
62
|
+
return [GF2nPoint(xx, self) for xx in x]
|
|
63
|
+
return GF2nPoint(x, self)
|
|
64
|
+
|
|
65
|
+
def __iter__(self):
|
|
66
|
+
for x in range(self.order):
|
|
67
|
+
yield GF2nPoint(x, self)
|
|
68
|
+
|
|
69
|
+
def __eq__(self, other):
|
|
70
|
+
if isinstance(other, self.__class__):
|
|
71
|
+
return self.order == other.order and self.modulus == other.modulus
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
def __contains__(self, other: "ZmodPoint") -> bool:
|
|
75
|
+
return isinstance(other, GF2nPoint) and self == other.field
|
|
76
|
+
|
|
77
|
+
def _factors(self) -> dict:
|
|
78
|
+
"Return the factroization order of the group order of GF(2^n)^*."
|
|
79
|
+
if not self.factors:
|
|
80
|
+
self.factors = factorint(self.mult_order)
|
|
81
|
+
return self.factors
|
|
82
|
+
|
|
83
|
+
def one(self) -> bool:
|
|
84
|
+
"Return one."
|
|
85
|
+
if self.bitreversed:
|
|
86
|
+
return self(self.order - 1)
|
|
87
|
+
return self(1)
|
|
88
|
+
|
|
89
|
+
def is_cyclic(self) -> bool:
|
|
90
|
+
"GF(2^n)^* is cyclic."
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def is_field(self) -> bool:
|
|
94
|
+
"GF(2^n) is a field."
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def generator(self) -> int | None:
|
|
98
|
+
"Return a generator of the group GF(2^n)^*."
|
|
99
|
+
for a in range(2, self.order):
|
|
100
|
+
a = self(a)
|
|
101
|
+
if a.is_generator():
|
|
102
|
+
return a
|
|
103
|
+
|
|
104
|
+
def generators(self) -> list | None:
|
|
105
|
+
"Return a generator for all generators of the group GF(2^n)^*."
|
|
106
|
+
a = self.generator()
|
|
107
|
+
if a is not None:
|
|
108
|
+
for j in range(1, self.mult_order):
|
|
109
|
+
if gcd(j, self.mult_order) == 1:
|
|
110
|
+
yield a**j
|
|
111
|
+
|
|
112
|
+
def star(self) -> list:
|
|
113
|
+
"Return a generator for all elements of the group GF(2^n)n^*."
|
|
114
|
+
for x in range(1, self.order):
|
|
115
|
+
yield self(x)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class GF2nPoint:
|
|
119
|
+
"Represents a point in the Galois field GF(2^n)."
|
|
120
|
+
|
|
121
|
+
def __init__(self, x: int, field: "GF2n"):
|
|
122
|
+
if isinstance(x, bytes | bytearray):
|
|
123
|
+
x = int.from_bytes(x, byteorder=field.byteorder)
|
|
124
|
+
self.x = int(x) % field.order
|
|
125
|
+
self.field = field
|
|
126
|
+
|
|
127
|
+
def __repr__(self):
|
|
128
|
+
if self.field.print_hex:
|
|
129
|
+
return format(self.x, "0"+str(self.field.power // 4)+"x")
|
|
130
|
+
return self.x
|
|
131
|
+
|
|
132
|
+
def __eq__(self, other):
|
|
133
|
+
if not isinstance(other, self.__class__) or self.field != other.field:
|
|
134
|
+
return False
|
|
135
|
+
return self.x == other.x
|
|
136
|
+
|
|
137
|
+
def __bool__(self):
|
|
138
|
+
return bool(self.x)
|
|
139
|
+
|
|
140
|
+
def __int__(self):
|
|
141
|
+
return self.x
|
|
142
|
+
|
|
143
|
+
def __bytes__(self):
|
|
144
|
+
return self.x.to_bytes(self.field.power // 8, byteorder=self.field.byteorder)
|
|
145
|
+
|
|
146
|
+
def bits(self) -> list:
|
|
147
|
+
"Convert to a list of bits."
|
|
148
|
+
if self.field.bitreversed:
|
|
149
|
+
coeff = [int(d) for d in str(
|
|
150
|
+
format(self.x, "0" + str(self.field.power) + "b"))]
|
|
151
|
+
else:
|
|
152
|
+
coeff = reversed([int(d) for d in str(
|
|
153
|
+
format(self.x, "0" + str(self.field.power) + "b"))])
|
|
154
|
+
return list(coeff)
|
|
155
|
+
|
|
156
|
+
def poly(self) -> "Poly":
|
|
157
|
+
"Convert to a polynomial."
|
|
158
|
+
Z_2 = Zmod(2)
|
|
159
|
+
if self.field.bitreversed:
|
|
160
|
+
coeff = [Z_2(int(d)) for d in str(
|
|
161
|
+
format(self.x, "0" + str(self.field.power) + "b"))]
|
|
162
|
+
coeff_modulus = [Z_2(int(d)) for d in str(
|
|
163
|
+
format((self.field.modulus << 1) + 1, "0" + str(self.field.power) + "b"))]
|
|
164
|
+
else:
|
|
165
|
+
coeff = reversed([Z_2(int(d)) for d in str(
|
|
166
|
+
format(self.x, "0" + str(self.field.power) + "b"))])
|
|
167
|
+
coeff_modulus = reversed([Z_2(int(d)) for d in str(
|
|
168
|
+
format(self.field.modulus, "0" + str(self.field.power) + "b"))])
|
|
169
|
+
return Poly(list(coeff), modulus=list(coeff_modulus))
|
|
170
|
+
|
|
171
|
+
def __hash__(self):
|
|
172
|
+
return hash(self.x)
|
|
173
|
+
|
|
174
|
+
def __add__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
175
|
+
if not isinstance(other, self.__class__):
|
|
176
|
+
return NotImplemented
|
|
177
|
+
if self.field != other.field:
|
|
178
|
+
raise NotImplementedError("Cannot add elements from different fields.")
|
|
179
|
+
return self.field(self.x ^ other.x)
|
|
180
|
+
|
|
181
|
+
def __neg__(self) -> "GF2nPoint":
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
def __pos__(self) -> "GF2nPoint":
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
def __sub__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
188
|
+
if not isinstance(other, self.__class__):
|
|
189
|
+
return NotImplemented
|
|
190
|
+
if self.field != other.field:
|
|
191
|
+
raise NotImplementedError("Cannot add elements from different fields.")
|
|
192
|
+
return self.field(self.x ^ other.x)
|
|
193
|
+
|
|
194
|
+
def __mul__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
195
|
+
if isinstance(other, int):
|
|
196
|
+
if other % 2:
|
|
197
|
+
return self
|
|
198
|
+
return self.field(0)
|
|
199
|
+
if not isinstance(other, self.__class__):
|
|
200
|
+
return NotImplemented
|
|
201
|
+
if self.field != other.field:
|
|
202
|
+
raise NotImplementedError("Cannot add elements from different fields.")
|
|
203
|
+
z = 0
|
|
204
|
+
y = other.x
|
|
205
|
+
if self.field.bitreversed:
|
|
206
|
+
for d in bin(self.x)[2:].zfill(self.field.power):
|
|
207
|
+
if int(d):
|
|
208
|
+
z ^= y
|
|
209
|
+
if y % 2:
|
|
210
|
+
y >>= 1
|
|
211
|
+
y ^= self.field.modulus
|
|
212
|
+
else:
|
|
213
|
+
y >>= 1
|
|
214
|
+
else:
|
|
215
|
+
bitmap1 = self.field.order
|
|
216
|
+
bitmap2 = 1
|
|
217
|
+
for _ in range(self.field.power):
|
|
218
|
+
if self.x & bitmap2:
|
|
219
|
+
z ^= y
|
|
220
|
+
bitmap2 <<= 1
|
|
221
|
+
y <<= 1
|
|
222
|
+
if y & bitmap1:
|
|
223
|
+
y ^= self.field.modulus
|
|
224
|
+
return self.field(z)
|
|
225
|
+
|
|
226
|
+
def __rmul__(self, scalar: int) -> "GF2nPoint":
|
|
227
|
+
if isinstance(scalar, int):
|
|
228
|
+
if scalar % 2:
|
|
229
|
+
return self
|
|
230
|
+
return self.field(0)
|
|
231
|
+
return NotImplemented
|
|
232
|
+
|
|
233
|
+
def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
234
|
+
if not isinstance(other, self.__class__):
|
|
235
|
+
return NotImplemented
|
|
236
|
+
if self.field != other.field:
|
|
237
|
+
raise NotImplementedError("Cannot add elements from different fields.")
|
|
238
|
+
if not other.x:
|
|
239
|
+
raise ValueError("Division by zero.")
|
|
240
|
+
return self * other**(self.field.order - 2)
|
|
241
|
+
|
|
242
|
+
def __pow__(self, j: int) -> "GF2nPoint":
|
|
243
|
+
if not isinstance(j, int):
|
|
244
|
+
return NotImplemented
|
|
245
|
+
if not (self.x) and j < 0:
|
|
246
|
+
raise ValueError("Division by zero.")
|
|
247
|
+
if self.field.bitreversed:
|
|
248
|
+
res = self.field(self.field.order >> 1)
|
|
249
|
+
else:
|
|
250
|
+
res = self.field(1)
|
|
251
|
+
x = self.field(self.x)
|
|
252
|
+
j %= self.field.order - 1
|
|
253
|
+
while j > 0:
|
|
254
|
+
# If j is odd, multiply with x
|
|
255
|
+
if j & 1:
|
|
256
|
+
res *= x
|
|
257
|
+
# Now square
|
|
258
|
+
j = j >> 1 # j= j/2
|
|
259
|
+
x *= x
|
|
260
|
+
return res
|
|
261
|
+
|
|
262
|
+
def __lshift__(self, i: int) -> "GF2nPoint":
|
|
263
|
+
"Cyclic rotation to the left."
|
|
264
|
+
x = self.x << i
|
|
265
|
+
x = (x % self.field.order) + (x // self.field.order)
|
|
266
|
+
return self.field(x)
|
|
267
|
+
|
|
268
|
+
def __rshift__(self, i: int) -> "GF2nPoint":
|
|
269
|
+
"Cyclic rotation to the right."
|
|
270
|
+
x = self.x >> i
|
|
271
|
+
x += (self.field.order >> i) * (self.x % 2**i)
|
|
272
|
+
return self.field(x)
|
|
273
|
+
|
|
274
|
+
def sqrt(self) -> "GF2nPoint":
|
|
275
|
+
"Compute the square root."
|
|
276
|
+
return self**(self.field.order//2)
|
|
277
|
+
|
|
278
|
+
def order(self) -> int:
|
|
279
|
+
"Compute the order of the point in the group GF(2^n)^*."
|
|
280
|
+
self.field._factors()
|
|
281
|
+
order = self.field.mult_order # our current guess
|
|
282
|
+
one = self.field.one()
|
|
283
|
+
for p, k in self.field.factors.items():
|
|
284
|
+
for _ in range(k):
|
|
285
|
+
order_try = order // p
|
|
286
|
+
if self ** order_try == one:
|
|
287
|
+
order = order_try
|
|
288
|
+
else:
|
|
289
|
+
break
|
|
290
|
+
return order
|
|
291
|
+
|
|
292
|
+
def is_generator(self):
|
|
293
|
+
"Test if the point is a generator of the group GF(2^n)^*."
|
|
294
|
+
return self.field.mult_order == self.order()
|
|
295
|
+
|
|
296
|
+
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
297
|
+
"Apply the AES sbox."
|
|
298
|
+
if self.field.power == 8 and self.field.modulus == 0b100011011: # AES
|
|
299
|
+
if inv:
|
|
300
|
+
return aes_sbox_inv[self.x]
|
|
301
|
+
return aes_sbox[self.x]
|
|
302
|
+
elif self.field.power == 4 and self.field.modulus == 0b10011: # MiniAES
|
|
303
|
+
if inv:
|
|
304
|
+
return miniaes_sbox_inv[self.x]
|
|
305
|
+
return miniaes_sbox[self.x]
|
|
306
|
+
raise ValueError("sbox is only availaible for AES/MiniAES field.")
|
|
307
|
+
|
|
308
|
+
# sbox for AES
|
|
309
|
+
GF2_aes = GF2(8, 0b100011011) # x^8 + x^4 + x^3 + x + 1 = AES
|
|
310
|
+
|
|
311
|
+
aes_sbox = [99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118,
|
|
312
|
+
202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192,
|
|
313
|
+
183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21,
|
|
314
|
+
4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9,
|
|
315
|
+
131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83,
|
|
316
|
+
209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208,
|
|
317
|
+
239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81,
|
|
318
|
+
163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205,
|
|
319
|
+
12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96,
|
|
320
|
+
129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224,
|
|
321
|
+
50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231,
|
|
322
|
+
200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186,
|
|
323
|
+
120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112,
|
|
324
|
+
62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225,
|
|
325
|
+
248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140,
|
|
326
|
+
161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22]
|
|
327
|
+
|
|
328
|
+
# inverse sbox for AES
|
|
329
|
+
aes_sbox_inv = [ 0 for i in range(256) ]
|
|
330
|
+
for i in range(256):
|
|
331
|
+
aes_sbox_inv[aes_sbox[i]] = i
|
|
332
|
+
aes_sbox = list(map(GF2_aes, aes_sbox))
|
|
333
|
+
aes_sbox_inv = list(map(GF2_aes, aes_sbox_inv))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# sbox for Mini-AES
|
|
337
|
+
GF2_miniaes = GF2(4, 0b10011) # x^4 + x + 1 = Mini-AES
|
|
338
|
+
|
|
339
|
+
miniaes_sbox = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7]
|
|
340
|
+
|
|
341
|
+
# inverse sbox for MiniAES
|
|
342
|
+
miniaes_sbox_inv = [ 0 for i in range(16) ]
|
|
343
|
+
for i in range(16):
|
|
344
|
+
miniaes_sbox_inv[miniaes_sbox[i]] = i
|
|
345
|
+
|
|
346
|
+
miniaes_sbox = list(map(GF2_miniaes, miniaes_sbox))
|
|
347
|
+
miniaes_sbox_inv = list(map(GF2_miniaes, miniaes_sbox_inv))
|
|
348
|
+
|
|
349
|
+
#Ghash
|
|
350
|
+
|
|
351
|
+
GF2_ghash = GF2(128, 0b11100001 << 120) # 1 + x + x^2 + x^7 (+ x^128) = Ghash
|
|
@@ -4,6 +4,7 @@ Ring of intergers modulo `n`.
|
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
6
|
from .factor import factorint
|
|
7
|
+
from .primes import is_prime
|
|
7
8
|
from .intfuncs import prime_power
|
|
8
9
|
|
|
9
10
|
|
|
@@ -33,11 +34,12 @@ class Zmod:
|
|
|
33
34
|
self.group_order = 0
|
|
34
35
|
self.factors = {} # factoring of the group order
|
|
35
36
|
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return f"Z_{self.n})"
|
|
39
|
+
|
|
36
40
|
def __call__(self, x: int | list | tuple):
|
|
37
|
-
if isinstance(x, list):
|
|
41
|
+
if isinstance(x, list|tuple):
|
|
38
42
|
return [ZmodPoint(xx, self) for xx in x]
|
|
39
|
-
if isinstance(x, tuple):
|
|
40
|
-
return (ZmodPoint(xx, self) for xx in x)
|
|
41
43
|
return ZmodPoint(x, self)
|
|
42
44
|
|
|
43
45
|
def __iter__(self):
|
|
@@ -53,7 +55,7 @@ class Zmod:
|
|
|
53
55
|
return isinstance(other, ZmodPoint) and self.n == other.ring.n
|
|
54
56
|
|
|
55
57
|
def order(self) -> int:
|
|
56
|
-
"
|
|
58
|
+
"Compute the order of the group Z_n^*."
|
|
57
59
|
if self.group_order:
|
|
58
60
|
return self.group_order
|
|
59
61
|
# We compute euler_phi(n) and its factorization in one pass
|
|
@@ -76,7 +78,7 @@ class Zmod:
|
|
|
76
78
|
return self.group_order
|
|
77
79
|
|
|
78
80
|
def is_cyclic(self) -> bool:
|
|
79
|
-
"
|
|
81
|
+
"Test if the group Z_n^* is cyclic."
|
|
80
82
|
n = self.n
|
|
81
83
|
if n < 8:
|
|
82
84
|
return True
|
|
@@ -88,8 +90,12 @@ class Zmod:
|
|
|
88
90
|
return True
|
|
89
91
|
return False
|
|
90
92
|
|
|
93
|
+
def is_field(self) -> bool:
|
|
94
|
+
"Test if Z_n is a field."
|
|
95
|
+
return is_prime(self.n)
|
|
96
|
+
|
|
91
97
|
def generator(self) -> int | None:
|
|
92
|
-
"
|
|
98
|
+
"Return a generator of the group Z_n^*."
|
|
93
99
|
if not self.is_cyclic():
|
|
94
100
|
return None
|
|
95
101
|
for a in range(2, self.n):
|
|
@@ -100,7 +106,7 @@ class Zmod:
|
|
|
100
106
|
return a
|
|
101
107
|
|
|
102
108
|
def generators(self) -> list | None:
|
|
103
|
-
"
|
|
109
|
+
"Return a generator for all generators of the group Z_n^*."
|
|
104
110
|
a = self.generator()
|
|
105
111
|
if a is not None:
|
|
106
112
|
self.order()
|
|
@@ -109,7 +115,7 @@ class Zmod:
|
|
|
109
115
|
yield a**j
|
|
110
116
|
|
|
111
117
|
def star(self) -> list:
|
|
112
|
-
"
|
|
118
|
+
"Return a generator for all elements of the group Z_n^*."
|
|
113
119
|
yield self(1)
|
|
114
120
|
for a in range(2, self.n):
|
|
115
121
|
if gcd(a, self.n) == 1:
|
|
@@ -210,7 +216,7 @@ class ZmodPoint:
|
|
|
210
216
|
return (self.x + tmp) % self.ring.n - tmp
|
|
211
217
|
|
|
212
218
|
def order(self) -> int:
|
|
213
|
-
"
|
|
219
|
+
"Compute the order of the point in the group Z_n^*."
|
|
214
220
|
if self.x == 0 or gcd(self.x, self.ring.n) != 1:
|
|
215
221
|
raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
|
|
216
222
|
order = self.ring.order() # use euler_phi(n) as our current guess
|
|
@@ -224,5 +230,5 @@ class ZmodPoint:
|
|
|
224
230
|
return order
|
|
225
231
|
|
|
226
232
|
def is_generator(self):
|
|
227
|
-
"
|
|
233
|
+
"Test if the point is a generator of the group Z_n^*."
|
|
228
234
|
return self.ring.order() == self.order()
|
|
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
|
|
|
5
5
|
__author__ = "Gerald Teschl"
|
|
6
6
|
__copyright__ = "Copyright 2024-2025, Gerald Teschl"
|
|
7
7
|
__license__ = "MIT License"
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.2"
|
|
9
9
|
__email__ = "Gerald.Teschl@univie.ac.at"
|
|
10
10
|
|
|
11
11
|
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
|
|
@@ -143,15 +143,19 @@ class AESKeySchedule:
|
|
|
143
143
|
self.round += 1
|
|
144
144
|
#RotWord
|
|
145
145
|
tmp = list(self.key[:,3])
|
|
146
|
-
if self.showtmp:
|
|
146
|
+
if self.showtmp:
|
|
147
|
+
print("Start :" + str(tmp))
|
|
147
148
|
tmp = rotate_left(tmp, 1)
|
|
148
|
-
if self.showtmp:
|
|
149
|
+
if self.showtmp:
|
|
150
|
+
print("RotWord :" + str(tmp))
|
|
149
151
|
#SubWord
|
|
150
152
|
tmp = list(map(lambda x: x.sbox(), tmp))
|
|
151
|
-
if self.showtmp:
|
|
153
|
+
if self.showtmp:
|
|
154
|
+
print("SubWord :" + str(tmp))
|
|
152
155
|
#AddRound constant
|
|
153
156
|
tmp[0] += GF2_aes(2)**(self.round - 1)
|
|
154
|
-
if self.showtmp:
|
|
157
|
+
if self.showtmp:
|
|
158
|
+
print("AddConst :" + str(tmp))
|
|
155
159
|
tmp = Matrix(tmp)
|
|
156
160
|
self.key[:,0] += tmp
|
|
157
161
|
self.key[:,1] += self.key[:,0]
|
|
@@ -168,10 +168,10 @@ class FeistelCipher:
|
|
|
168
168
|
def f_box(self, a: list[int], key: list[int], showtmp:bool = False) -> list[int]:
|
|
169
169
|
"Nonlinear f-box"
|
|
170
170
|
if showtmp:
|
|
171
|
-
def printtmp(title:str, a: list) -> None:
|
|
171
|
+
def printtmp(title: str, a: list) -> None:
|
|
172
172
|
print(f'{title}: {"".join(map(str,a))}')
|
|
173
173
|
else:
|
|
174
|
-
def printtmp(title:str, a: list) -> None:
|
|
174
|
+
def printtmp(title: str, a: list) -> None:
|
|
175
175
|
pass
|
|
176
176
|
a = permute(a, self.__class__.E_box) # expansion box
|
|
177
177
|
printtmp("EBox", a)
|
|
@@ -12,7 +12,7 @@ from .factor import factorint
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def dlog_naive(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int | None:
|
|
15
|
-
"""Compute the discrete log_a(b) in Z_n of an element a of order m by exhaustive search."""
|
|
15
|
+
"""Compute the discrete log_a(b) in Z_n of an element `a` of order `m` by exhaustive search."""
|
|
16
16
|
a %= n
|
|
17
17
|
b %= n
|
|
18
18
|
if not m:
|
|
@@ -35,7 +35,7 @@ def dlog_naive(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int |
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def _dlog_switch(a: int, b: int, n: int, m: int, verbose: int = 0) -> int:
|
|
38
|
-
"""Compute the discrete log_a(b) in Z_n of an element a of order m choosing an appropriate method."""
|
|
38
|
+
"""Compute the discrete log_a(b) in Z_n of an element `a` of order `m` choosing an appropriate method."""
|
|
39
39
|
if m < 1000:
|
|
40
40
|
if verbose > 1:
|
|
41
41
|
print(f"Naive search: {a}^x = {b} (order={m})")
|
|
@@ -51,7 +51,7 @@ def _dlog_switch(a: int, b: int, n: int, m: int, verbose: int = 0) -> int:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def _dlog_ph(a: int, b: int, n: int, q: int, k: int, verbose: int = 0) -> int:
|
|
54
|
-
"""Compute the discrete log_a(b) in Z_n of an element a of order q^k using Pohlig-Hellman reduction."""
|
|
54
|
+
"""Compute the discrete log_a(b) in Z_n of an element `a` of order `q^k` using Pohlig-Hellman reduction."""
|
|
55
55
|
if verbose > 1:
|
|
56
56
|
print(f"PH reduction: {a}^x = {b} (order={q}^{k})")
|
|
57
57
|
if k == 1 or q**k < 10000:
|
|
@@ -6,7 +6,7 @@ from math import isqrt
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def dlog_bsgs(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int:
|
|
9
|
-
"""Compute the discrete log_a(b) in Z_n of an element a of order m using Shanks' baby-step-giant-step algorithm."""
|
|
9
|
+
"""Compute the discrete log_a(b) in Z_n of an element `a` of order `m` using Shanks' baby-step-giant-step algorithm."""
|
|
10
10
|
a %= n
|
|
11
11
|
b %= n
|
|
12
12
|
if not m:
|
|
@@ -10,7 +10,7 @@ seed(0)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int = 0) -> int:
|
|
13
|
-
"""Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus."""
|
|
13
|
+
"""Compute the discrete log_a(b) in Z_p of an element `a` of prime order `m` using Index Calculus."""
|
|
14
14
|
|
|
15
15
|
# assert is_prime(m), "The order of a must be prime."
|
|
16
16
|
# assert order(a, n) == m, "The order of a is incorrect."
|
|
@@ -10,7 +10,7 @@ seed(0)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def determine_factorbound(n: int) -> (int, int):
|
|
13
|
-
"""Determines the optimal factor bound and the expected number of trys until a for a given n
|
|
13
|
+
"""Determines the optimal factor bound and the expected number of trys until a relation is found for a given `n`."""
|
|
14
14
|
# Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us the expected running time |factorbase|^2 u^u = u^(u+2) n^(2/u)/log(n).
|
|
15
15
|
# There is no explicit expression for the optimum, hence we use Newton
|
|
16
16
|
u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
|
|
@@ -47,7 +47,7 @@ def determine_trialdivison_bounds(B: int, factorbase: list) -> (int, int):
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: int, pollard_k: int = None) -> list or None:
|
|
50
|
-
"""Try to factor n with respect to a
|
|
50
|
+
"""Try to factor `n` with respect to a given factorbase. Upon success a list of exponents with repect to the factorbase is returned. Otherwise None."""
|
|
51
51
|
# factorbase_len = len(factorbase)
|
|
52
52
|
factors = [0] * factorbase_len
|
|
53
53
|
for i in range(smallprimes_len):
|
|
@@ -70,7 +70,7 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in
|
|
|
70
70
|
|
|
71
71
|
def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
|
|
72
72
|
"""
|
|
73
|
-
Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus with a quadratic sieve.
|
|
73
|
+
Compute the discrete log_a(b) in Z_p of an element `a` of prime order `m` using Index Calculus with a quadratic sieve.
|
|
74
74
|
The problem is assumed solvable.
|
|
75
75
|
"""
|
|
76
76
|
# assert is_prime(m), "The order of a must be prime."
|
|
@@ -7,7 +7,7 @@ from random import randint
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def dlog_rho(a: int, b: int, n: int, m: int = None, brent=0) -> int:
|
|
10
|
-
"""Compute the discrete log_a(b) in Z_p of an element a of order m using Pollard's rho algorithm."""
|
|
10
|
+
"""Compute the discrete log_a(b) in Z_p of an element `a` of order `m` using Pollard's rho algorithm."""
|
|
11
11
|
a %= n
|
|
12
12
|
b %= n
|
|
13
13
|
if not m:
|
|
@@ -9,7 +9,7 @@ from .factor_qs import bytexor, byteset, bytetest
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def is_smooth(n: int, factorbase: list, lfb: int) -> bytes | None:
|
|
12
|
-
"""Try to factor n with respect to a given factorbase.
|
|
12
|
+
"""Try to factor `n` with respect to a given factorbase.
|
|
13
13
|
Upon success a bytestring, whose bits are the exponents with repect to the factorbase mod 2, is returned.
|
|
14
14
|
Otherwise None."""
|
|
15
15
|
factors = bytearray(b"\x00") * lfb # we store the exponents mod 2 as bits
|
|
@@ -29,7 +29,7 @@ def is_smooth(n: int, factorbase: list, lfb: int) -> bytes | None:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def factor_dixon(n: int) -> int | None:
|
|
32
|
-
"""Find factors of n using the method of Dixon."""
|
|
32
|
+
"""Find factors of `n` using the method of Dixon."""
|
|
33
33
|
# first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
|
|
34
34
|
# the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
|
|
35
35
|
# we use Newton
|
|
@@ -55,6 +55,7 @@ def factor_dixon(n: int) -> int | None:
|
|
|
55
55
|
m = isqrt(n - 1) + 1
|
|
56
56
|
|
|
57
57
|
def process_relation(j: int, relation: bytes):
|
|
58
|
+
"Add a relation to the linear system and reduce the new system."
|
|
58
59
|
nonlocal relation_no, values, relations
|
|
59
60
|
relation_no += 1
|
|
60
61
|
# construct the k'th row of the identity matrix
|
|
@@ -89,7 +89,7 @@ def _ecm_parameters(B1: int, B2: int | None = None, D: int | None = None, primes
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def factor_ecm(n: int, B1: int | None = None, B2: int | None = None, curves: int = 150, ecm_parameters: tuple | None = None, verbose: int = 0) -> int | None:
|
|
92
|
-
"Factors a number n using Lentsta's ECM method."
|
|
92
|
+
"Factors a number `n` using Lentsta's ECM method."
|
|
93
93
|
|
|
94
94
|
if ecm_parameters:
|
|
95
95
|
B1, B2, curves, D, stage_one, stage_two_deltas = ecm_parameters
|
|
@@ -6,7 +6,7 @@ from math import isqrt, floor, log10
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def factor_fermat(n: int, maxsteps: int | None = None, verbose: int = 0) -> list:
|
|
9
|
-
"""Find factors of n using the method of Fermat."""
|
|
9
|
+
"""Find factors of `n` using the method of Fermat."""
|
|
10
10
|
if not isinstance(n, int) or n < 1:
|
|
11
11
|
raise ValueError("Number to be factored must be a positive integer!")
|
|
12
12
|
if verbose:
|
|
@@ -27,7 +27,7 @@ def _pm1_parameters(B1: int, B2: int, primes: tuple = None) -> tuple:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def factor_pm1(n: int, B1: int | None = None, B2: int | None = None, x: int = 2, pm1_parameters: tuple = None, verbose: int = 0) -> int | None:
|
|
30
|
-
"Factors a number n using Pollard's p-1 method."
|
|
30
|
+
"Factors a number `n` using Pollard's p-1 method."
|
|
31
31
|
if pm1_parameters:
|
|
32
32
|
B1, B2, stage_one, stage_two_deltas = pm1_parameters
|
|
33
33
|
else:
|