kryptools 1.1__tar.gz → 1.3__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.3}/PKG-INFO +5 -5
- {kryptools-1.1 → kryptools-1.3}/README.md +4 -4
- kryptools-1.3/kryptools/GF2.py +351 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/Zmod.py +21 -11
- {kryptools-1.1 → kryptools-1.3}/kryptools/__init__.py +2 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/aes.py +8 -4
- kryptools-1.3/kryptools/code.py +124 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/des.py +2 -2
- {kryptools-1.1 → kryptools-1.3}/kryptools/dlp.py +3 -3
- {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_bsgs.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_ic.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_qs.py +3 -3
- {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_rho.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_dix.py +3 -2
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_ecm.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_fmt.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_pm1.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_qs.py +5 -4
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor_rho.py +1 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/keccak.py +9 -5
- {kryptools-1.1 → kryptools-1.3}/kryptools/la.py +107 -2
- {kryptools-1.1 → kryptools-1.3}/kryptools/lat.py +2 -1
- {kryptools-1.1 → kryptools-1.3}/kryptools/poly.py +78 -8
- {kryptools-1.1 → kryptools-1.3}/kryptools/sha1.py +16 -12
- {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/PKG-INFO +5 -5
- {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/SOURCES.txt +2 -0
- {kryptools-1.1 → kryptools-1.3}/pyproject.toml +1 -1
- {kryptools-1.1 → kryptools-1.3}/tests/test_GF2.py +5 -1
- {kryptools-1.1 → kryptools-1.3}/tests/test_Zmod.py +2 -0
- kryptools-1.3/tests/test_code.py +32 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_sha1.py +1 -1
- kryptools-1.1/kryptools/GF2.py +0 -281
- {kryptools-1.1 → kryptools-1.3}/LICENSE +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/blockcipher.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/ec.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/factor.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/intfuncs.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/nt.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools/primes.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.1 → kryptools-1.3}/setup.cfg +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_aes.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_des.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_dlog.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_ec.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_factor.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_factor_qs.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_intfuncs.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_keccak.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_la.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_lat.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_nt.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/tests/test_poly.py +0 -0
- {kryptools-1.1 → kryptools-1.3}/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.3
|
|
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
|
|
@@ -39,20 +39,20 @@ The tools contained are:
|
|
|
39
39
|
* elliptic curves (Weierstrass form), group operations, order, discrete logarithms
|
|
40
40
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
41
41
|
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
42
|
+
* linear codes: Hamming distance, left standard form, parity check matrix
|
|
42
43
|
|
|
43
44
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
44
45
|
* Poly: a class for polynomials (division, modulo)
|
|
45
46
|
* Zmod: a class for the ring of integers modulo an integer
|
|
46
47
|
* GF2: a class for Galois fields GF(2^n)
|
|
48
|
+
* Goppa: a class for encoding and decoding Goppa codes
|
|
47
49
|
|
|
48
50
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
49
51
|
* AESCipher: individual AES operations
|
|
50
52
|
* DESCipher: individual DES operations
|
|
51
53
|
|
|
52
|
-
* SHA1
|
|
53
|
-
* Keccak individual sponge operations
|
|
54
|
-
* SHA3
|
|
55
|
-
* SHAKE
|
|
54
|
+
* SHA1: custom initial state, padding function
|
|
55
|
+
* Keccak: individual sponge operations, SHA3, SHAKE
|
|
56
56
|
|
|
57
57
|
Documentation can be found in the jupyter notebook
|
|
58
58
|
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -21,20 +21,20 @@ The tools contained are:
|
|
|
21
21
|
* elliptic curves (Weierstrass form), group operations, order, discrete logarithms
|
|
22
22
|
* linear algebra: Hermite normal form, Gram-Schmidt
|
|
23
23
|
* lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
|
|
24
|
+
* linear codes: Hamming distance, left standard form, parity check matrix
|
|
24
25
|
|
|
25
26
|
* Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
|
|
26
27
|
* Poly: a class for polynomials (division, modulo)
|
|
27
28
|
* Zmod: a class for the ring of integers modulo an integer
|
|
28
29
|
* GF2: a class for Galois fields GF(2^n)
|
|
30
|
+
* Goppa: a class for encoding and decoding Goppa codes
|
|
29
31
|
|
|
30
32
|
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
31
33
|
* AESCipher: individual AES operations
|
|
32
34
|
* DESCipher: individual DES operations
|
|
33
35
|
|
|
34
|
-
* SHA1
|
|
35
|
-
* Keccak individual sponge operations
|
|
36
|
-
* SHA3
|
|
37
|
-
* SHAKE
|
|
36
|
+
* SHA1: custom initial state, padding function
|
|
37
|
+
* Keccak: individual sponge operations, SHA3, SHAKE
|
|
38
38
|
|
|
39
39
|
Documentation can be found in the jupyter notebook
|
|
40
40
|
(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
|
|
|
@@ -16,7 +17,7 @@ class Zmod:
|
|
|
16
17
|
To define a finite Galois field modulo the prime 5 use
|
|
17
18
|
>>> Z_5=Zmod(5)
|
|
18
19
|
|
|
19
|
-
To declare 3 as an element of our Galois
|
|
20
|
+
To declare 3 as an element of our Galois field use
|
|
20
21
|
>>> Z_5(3)
|
|
21
22
|
3
|
|
22
23
|
|
|
@@ -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:
|
|
@@ -204,13 +210,17 @@ class ZmodPoint:
|
|
|
204
210
|
def __abs__(self) -> int:
|
|
205
211
|
return abs(self.sharp())
|
|
206
212
|
|
|
213
|
+
def bits(self) -> list:
|
|
214
|
+
"Convert to a list of bits."
|
|
215
|
+
return [int(d) for d in str(format(self.x, "0" + str(int.bit_length(self.ring.n - 1)) + "b"))]
|
|
216
|
+
|
|
207
217
|
def sharp(self):
|
|
208
218
|
"Returns a symmetric (w.r.t. 0) representative."
|
|
209
219
|
tmp = (self.ring.n - 1) // 2
|
|
210
220
|
return (self.x + tmp) % self.ring.n - tmp
|
|
211
221
|
|
|
212
222
|
def order(self) -> int:
|
|
213
|
-
"
|
|
223
|
+
"Compute the order of the point in the group Z_n^*."
|
|
214
224
|
if self.x == 0 or gcd(self.x, self.ring.n) != 1:
|
|
215
225
|
raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
|
|
216
226
|
order = self.ring.order() # use euler_phi(n) as our current guess
|
|
@@ -224,5 +234,5 @@ class ZmodPoint:
|
|
|
224
234
|
return order
|
|
225
235
|
|
|
226
236
|
def is_generator(self):
|
|
227
|
-
"
|
|
237
|
+
"Test if the point is a generator of the group Z_n^*."
|
|
228
238
|
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.3"
|
|
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
|
|
@@ -19,6 +19,7 @@ from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round
|
|
|
19
19
|
from .poly import Poly
|
|
20
20
|
from .Zmod import Zmod
|
|
21
21
|
from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
|
|
22
|
+
from .code import left_standard_form, gen2pchk, hamming_dist, Goppa
|
|
22
23
|
from .blockcipher import BlockCipher, bytexor
|
|
23
24
|
from .aes import AESKeySchedule, AESCipher, AESBlockCipher
|
|
24
25
|
from .des import list2int, int2list, permute, invert_permutation, rotate_left, SDESKeySchedule, SDESCipher, DESKeySchedule, DESCipher, DESBlockCipher
|
|
@@ -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]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linear codes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .Zmod import Zmod
|
|
6
|
+
from .GF2 import GF2
|
|
7
|
+
from .poly import Poly
|
|
8
|
+
from .la import Matrix, eye, zeros
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def poly2bits(x: Poly) -> list:
|
|
12
|
+
"Convert a polynomial in GF(2^n)[x] to a list of binary digits."
|
|
13
|
+
out = []
|
|
14
|
+
for c in x.coeff:
|
|
15
|
+
out += c.bits()
|
|
16
|
+
return out
|
|
17
|
+
|
|
18
|
+
def left_standard_form(M: Matrix) -> (Matrix, Matrix):
|
|
19
|
+
"Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
|
|
20
|
+
M = M.rref()
|
|
21
|
+
for i in range(M.rows-1,-1,-1):
|
|
22
|
+
if not M[i,:]:
|
|
23
|
+
M.delete_rows(i) # remove zero rows
|
|
24
|
+
last = min(M.rows, M.cols)
|
|
25
|
+
P = eye(M.cols)
|
|
26
|
+
for i in range(last):
|
|
27
|
+
if not M[i,i]:
|
|
28
|
+
j = i + 1
|
|
29
|
+
while j <= last and not M[i, j]:
|
|
30
|
+
j += 1
|
|
31
|
+
if j <= last:
|
|
32
|
+
P[:,i], P[:,j] = P[:,j], P[:,i]
|
|
33
|
+
M[:,i], M[:,j] = M[:,j], M[:,i]
|
|
34
|
+
return M, P
|
|
35
|
+
|
|
36
|
+
def gen2pchk(G: Matrix) -> Matrix:
|
|
37
|
+
"Compute the parity check matrix from a given generator matrix (and vice versa)."
|
|
38
|
+
G, P = left_standard_form(G)
|
|
39
|
+
d = G.cols - G.rows
|
|
40
|
+
assert d > 0
|
|
41
|
+
zero = 0 * G[0]
|
|
42
|
+
H = zeros(d, G.cols, zero = zero)
|
|
43
|
+
for i in range(d):
|
|
44
|
+
H[i,G.rows+i] = zero**0
|
|
45
|
+
tmp = - G[:,-d:].transpose()
|
|
46
|
+
for i in range(G.cols - d):
|
|
47
|
+
H[:, i] = tmp[:, i]
|
|
48
|
+
return H * P.transpose()
|
|
49
|
+
|
|
50
|
+
def hamming_dist(a: iter, b: iter):
|
|
51
|
+
"Hamming distance of two iterables."
|
|
52
|
+
if len(a) != len(b):
|
|
53
|
+
raise ValueError("Both iteralbles must have equal length.")
|
|
54
|
+
return sum(aa != bb for aa, bb in zip(a, b))
|
|
55
|
+
|
|
56
|
+
class Goppa():
|
|
57
|
+
"""
|
|
58
|
+
Binary Goppa code.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
|
|
62
|
+
>>> gf = GF2(4) # base field GF(2^n)
|
|
63
|
+
>>> g = Poly([2, 0, 0, 1], ring = gf) # Polynomial for the code
|
|
64
|
+
>>> alpha = list(gf) # list of points from the base field (must not contain zeros of g)
|
|
65
|
+
>>> goppa = Goppa(gf, g, alpha)
|
|
66
|
+
|
|
67
|
+
To encode a list of bits (the length must be equal to the dimension k of the code)
|
|
68
|
+
>>> goppa.encode([1, 0, 1, 1])
|
|
69
|
+
[0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
|
|
70
|
+
|
|
71
|
+
Decoding still works if there are atmost deg(g) errors:
|
|
72
|
+
>>> goppa.decode([0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0])
|
|
73
|
+
[1, 0, 1, 1]
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, gf: GF2, g: Poly, alpha: list["GF2Point"]):
|
|
78
|
+
assert all(map(g, alpha))
|
|
79
|
+
self.gf = gf
|
|
80
|
+
self.g = g
|
|
81
|
+
self.alpha = alpha
|
|
82
|
+
# Compute the control matrix
|
|
83
|
+
self.Z_2 = Zmod(2)
|
|
84
|
+
|
|
85
|
+
tmp =[]
|
|
86
|
+
for x in alpha:
|
|
87
|
+
tmp.append(Poly([-x, 1], ring = gf, modulus = g).inv().bits())
|
|
88
|
+
self.H = Matrix(tmp, ring=self.Z_2).transpose()
|
|
89
|
+
# Compute the generator matrix
|
|
90
|
+
self.G = gen2pchk(self.H)
|
|
91
|
+
|
|
92
|
+
def encode(self, x: list[int]):
|
|
93
|
+
"Encode a given list of bits."
|
|
94
|
+
x = Matrix(x, ring = self.Z_2)
|
|
95
|
+
y = self.G.transpose() * x
|
|
96
|
+
y.map(int)
|
|
97
|
+
return list(y)
|
|
98
|
+
|
|
99
|
+
def decode(self, y: list[int]):
|
|
100
|
+
"Decode a given list of bits."
|
|
101
|
+
y = Matrix(y, ring = self.Z_2)
|
|
102
|
+
if self.H * y: # no code word
|
|
103
|
+
s = self.gf(0) # syndrom polynomial
|
|
104
|
+
for i, x in enumerate(self.alpha):
|
|
105
|
+
s += int(y[i]) * Poly([-x, 1], ring = self.gf, modulus = self.g).inv()
|
|
106
|
+
v = (s.inv() - Poly([0, 1], ring = self.gf, modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
|
|
107
|
+
# determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
|
|
108
|
+
# find a and b by running EEA until b has the desired degree
|
|
109
|
+
v.modulus = None
|
|
110
|
+
r0, r1 = self.g, v
|
|
111
|
+
y0, y1 = Poly([self.gf(0)]), Poly([self.gf(1)])
|
|
112
|
+
while r1 and 2* r1.degree() > self.g.degree():
|
|
113
|
+
q, r = r0.divmod(r1)
|
|
114
|
+
r0, r1 = r1, r
|
|
115
|
+
y0, y1 = y1, y0 - q * y1
|
|
116
|
+
a, b = r1, y1
|
|
117
|
+
sigma = a**2 + b**2 * Poly([0,1], ring = self.gf)
|
|
118
|
+
# correct the error
|
|
119
|
+
for x in self.gf:
|
|
120
|
+
if not sigma(x):
|
|
121
|
+
y[int(x)] += 1
|
|
122
|
+
x = self.G.transpose().solve(y)
|
|
123
|
+
x.map(int)
|
|
124
|
+
return list(x)
|
|
@@ -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)
|