kryptools 0.9.14__tar.gz → 1.0__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-0.9.14 → kryptools-1.0}/PKG-INFO +6 -2
- {kryptools-0.9.14 → kryptools-1.0}/README.md +4 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/GF2.py +26 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/__init__.py +5 -2
- kryptools-1.0/kryptools/aes.py +177 -0
- kryptools-1.0/kryptools/blockcipher.py +331 -0
- kryptools-1.0/kryptools/des.py +338 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp.py +1 -1
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_bsgs.py +1 -1
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_ecm.py +1 -1
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_pm1.py +17 -16
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_qs.py +7 -7
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_rho.py +3 -3
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/la.py +15 -14
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/nt.py +13 -2
- {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/PKG-INFO +6 -2
- {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/SOURCES.txt +6 -0
- {kryptools-0.9.14 → kryptools-1.0}/pyproject.toml +1 -1
- kryptools-1.0/tests/test_aes.py +162 -0
- kryptools-1.0/tests/test_des.py +42 -0
- kryptools-1.0/tests/test_factor_pm1.py +8 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor_qs.py +0 -1
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_nt.py +4 -3
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_primes.py +17 -1
- {kryptools-0.9.14 → kryptools-1.0}/LICENSE +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/Zmod.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/conway_polynomials.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_ic.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_qs.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_rho.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/ec.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_dix.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_fmt.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/intfuncs.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/lat.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/poly.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools/primes.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/setup.cfg +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_GF2.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_Zmod.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_dlog.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_ec.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor_fmt.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_intfuncs.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_la.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_lat.py +0 -0
- {kryptools-0.9.14 → kryptools-1.0}/tests/test_poly.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 0
|
|
3
|
+
Version: 1.0
|
|
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
|
|
@@ -44,5 +44,9 @@ The tools contained are:
|
|
|
44
44
|
* Zmod: a class for the ring of integers modulo an integer
|
|
45
45
|
* GF2: a class for Galois fields GF(2^n)
|
|
46
46
|
|
|
47
|
+
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
48
|
+
* AESCipher: individual AES operations
|
|
49
|
+
* DESCipher: individual DES operations
|
|
50
|
+
|
|
47
51
|
Documentation can be found in the jupyter notebook
|
|
48
52
|
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -27,5 +27,9 @@ The tools contained are:
|
|
|
27
27
|
* Zmod: a class for the ring of integers modulo an integer
|
|
28
28
|
* GF2: a class for Galois fields GF(2^n)
|
|
29
29
|
|
|
30
|
+
* BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
|
|
31
|
+
* AESCipher: individual AES operations
|
|
32
|
+
* DESCipher: individual DES operations
|
|
33
|
+
|
|
30
34
|
Documentation can be found in the jupyter notebook
|
|
31
35
|
(mostly done, but might not contain everything: use the force, read the source).
|
|
@@ -241,6 +241,32 @@ class GF2_aes(GF_2):
|
|
|
241
241
|
aes_sbox = list(map(GF2_aes, aes_sbox))
|
|
242
242
|
aes_sbox_inv = list(map(GF2_aes, aes_sbox_inv))
|
|
243
243
|
|
|
244
|
+
#
|
|
245
|
+
# Mini-AES
|
|
246
|
+
#
|
|
247
|
+
|
|
248
|
+
# sbox for Mini-AES
|
|
249
|
+
miniaes_sbox = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7]
|
|
250
|
+
|
|
251
|
+
# inverse sbox for MiniAES
|
|
252
|
+
miniaes_sbox_inv = [ 0 for i in range(16) ]
|
|
253
|
+
for i in range(16):
|
|
254
|
+
miniaes_sbox_inv[miniaes_sbox[i]] = i
|
|
255
|
+
|
|
256
|
+
class GF2_miniaes(GF_2):
|
|
257
|
+
"Represents a point in the Mini-AES Galois field GF(2^4)."
|
|
258
|
+
modulus: int = 0b10011 # x^4 + x + 1 = Mini-AES
|
|
259
|
+
power: int = 4 # n
|
|
260
|
+
order: int = 16 # 2**n
|
|
261
|
+
|
|
262
|
+
def sbox(self, inv: bool = False) -> "GF2_miniaes":
|
|
263
|
+
if inv:
|
|
264
|
+
return miniaes_sbox_inv[self.x]
|
|
265
|
+
return miniaes_sbox[self.x]
|
|
266
|
+
|
|
267
|
+
miniaes_sbox = list(map(GF2_miniaes, miniaes_sbox))
|
|
268
|
+
miniaes_sbox_inv = list(map(GF2_miniaes, miniaes_sbox_inv))
|
|
269
|
+
|
|
244
270
|
#
|
|
245
271
|
# GHASH
|
|
246
272
|
#
|
|
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
|
|
|
5
5
|
__author__ = "Gerald Teschl"
|
|
6
6
|
__copyright__ = "Copyright 2024, Gerald Teschl"
|
|
7
7
|
__license__ = "MIT License"
|
|
8
|
-
__version__ = "0.9.
|
|
8
|
+
__version__ = "0.9.15"
|
|
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
|
|
@@ -18,4 +18,7 @@ from .la import Matrix, zeros, eye
|
|
|
18
18
|
from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_round_bnd, babai_plane_cvp, babai_plane_bnd, lagrange_lr, lll, random_unimodular_matrix
|
|
19
19
|
from .poly import Poly
|
|
20
20
|
from .Zmod import Zmod
|
|
21
|
-
from .GF2 import GF2, GF2_aes, GF2_ghash
|
|
21
|
+
from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
|
|
22
|
+
from .blockcipher import BlockCipher, bytexor
|
|
23
|
+
from .aes import AESKeySchedule, AESCipher, AESBlockCipher
|
|
24
|
+
from .des import list2int, SDESKeySchedule, SDESCipher, DESKeySchedule, DESCipher, DESBlockCipher
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AES cipher
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .GF2 import GF2_aes
|
|
6
|
+
from .la import Matrix, zeros
|
|
7
|
+
from .blockcipher import BlockCipher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def rotate_left(l: list, n: int) -> list:
|
|
11
|
+
"Rotate a list cyclically n places to the left."
|
|
12
|
+
n %= len(l)
|
|
13
|
+
return l[n:] + l[:n]
|
|
14
|
+
|
|
15
|
+
aes_mix_matrix = Matrix([ rotate_left([2, 3, 1, 1], -n) for n in range(4) ], ring=GF2_aes)
|
|
16
|
+
aes_mix_matrix_inv = aes_mix_matrix.inv()
|
|
17
|
+
|
|
18
|
+
class AESCipher:
|
|
19
|
+
"AES state matrix"
|
|
20
|
+
def __init__(self, key: bytes = None):
|
|
21
|
+
self.keys = []
|
|
22
|
+
self.state = zeros(4, zero=GF2_aes(0))
|
|
23
|
+
self.set_key(key)
|
|
24
|
+
|
|
25
|
+
def __call__(self, s: bytes):
|
|
26
|
+
if isinstance(s, bytes|bytearray) and len(s) == 16:
|
|
27
|
+
self.state = zeros(4)
|
|
28
|
+
for l in range(16):
|
|
29
|
+
i, j = divmod(l, 4)
|
|
30
|
+
self.state[j, i] = GF2_aes(s[l])
|
|
31
|
+
elif isinstance(s, list|tuple) and len(s) == 4:
|
|
32
|
+
self.state = Matrix(s, ring=GF2_aes)
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError("Plaintext must be 16 bytes or 4x4 list of bytes.")
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return str(self.state)
|
|
39
|
+
|
|
40
|
+
def __bytes__(self):
|
|
41
|
+
tmp = bytearray()
|
|
42
|
+
for i in range(4):
|
|
43
|
+
for j in range(4):
|
|
44
|
+
tmp.append(int(self.state[j, i]))
|
|
45
|
+
return bytes(tmp)
|
|
46
|
+
|
|
47
|
+
def set_key(self, key: bytes):
|
|
48
|
+
"Set a new key."
|
|
49
|
+
if key is not None:
|
|
50
|
+
self.keys = AESKeySchedule(key).gen_keys()
|
|
51
|
+
|
|
52
|
+
def sub_bytes(self, inv: bool = False) -> None:
|
|
53
|
+
"Apply SubBytes to the state."
|
|
54
|
+
if inv:
|
|
55
|
+
self.state.map(lambda x: x.sbox(inv=True))
|
|
56
|
+
else:
|
|
57
|
+
self.state.map(lambda x: x.sbox())
|
|
58
|
+
|
|
59
|
+
def shift_rows(self, inv: bool = False) -> None:
|
|
60
|
+
"Apply ShiftRows to the state."
|
|
61
|
+
if inv:
|
|
62
|
+
inv = -1
|
|
63
|
+
else:
|
|
64
|
+
inv = 1
|
|
65
|
+
for i in range(self.state.rows):
|
|
66
|
+
self.state.matrix[i] = rotate_left(self.state.matrix[i], inv * i)
|
|
67
|
+
|
|
68
|
+
def mix_columns(self, inv = False) -> None:
|
|
69
|
+
"Apply MixColumns to the state."
|
|
70
|
+
if inv:
|
|
71
|
+
self.state.matrix = (aes_mix_matrix_inv * self.state).matrix
|
|
72
|
+
else:
|
|
73
|
+
self.state.matrix = (aes_mix_matrix * self.state).matrix
|
|
74
|
+
|
|
75
|
+
def add_key(self, key: "Matrix") -> None:
|
|
76
|
+
"Add the key to the state."
|
|
77
|
+
self.state += key
|
|
78
|
+
|
|
79
|
+
def round(self, k: "Matrix", last = False, inv = False) -> None:
|
|
80
|
+
"Apply one round to the state."
|
|
81
|
+
if inv:
|
|
82
|
+
self.add_key(k)
|
|
83
|
+
if not last:
|
|
84
|
+
self.mix_columns(inv = True)
|
|
85
|
+
self.shift_rows(inv = True)
|
|
86
|
+
self.sub_bytes(inv = True)
|
|
87
|
+
else:
|
|
88
|
+
self.sub_bytes()
|
|
89
|
+
self.shift_rows()
|
|
90
|
+
if not last:
|
|
91
|
+
self.mix_columns()
|
|
92
|
+
self.add_key(k)
|
|
93
|
+
|
|
94
|
+
def encrypt(self, x: bytes = None) -> bytes:
|
|
95
|
+
"Do a full encryption of a given block (or the current state)."
|
|
96
|
+
if x is not None:
|
|
97
|
+
self(x)
|
|
98
|
+
self.add_key(self.keys[0])
|
|
99
|
+
for k in self.keys[1:-1]:
|
|
100
|
+
self.round(k)
|
|
101
|
+
self.round(self.keys[-1], last = True)
|
|
102
|
+
return bytes(self)
|
|
103
|
+
|
|
104
|
+
def decrypt(self, x: bytes = None) -> bytes:
|
|
105
|
+
"Do a full decryption of a given block (or the current state)."
|
|
106
|
+
if x is not None:
|
|
107
|
+
self(x)
|
|
108
|
+
self.round(self.keys[-1], last = True, inv = True)
|
|
109
|
+
for k in reversed(self.keys[1:-1]):
|
|
110
|
+
self.round(k, inv = True)
|
|
111
|
+
self.add_key(self.keys[0])
|
|
112
|
+
return bytes(self)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class AESKeySchedule:
|
|
116
|
+
"AES key matrix"
|
|
117
|
+
def __init__(self, key: bytes):
|
|
118
|
+
assert len(key) == 16
|
|
119
|
+
self.round = 0
|
|
120
|
+
self.showtmp = False # print intermediate results
|
|
121
|
+
if isinstance(key, bytes|bytearray) and len(key) == 16:
|
|
122
|
+
self.key = zeros(4)
|
|
123
|
+
for l in range(16):
|
|
124
|
+
i, j = divmod(l, 4)
|
|
125
|
+
self.key[j, i] = GF2_aes(key[l])
|
|
126
|
+
elif isinstance(key, list|tuple) and len(key) == 4:
|
|
127
|
+
self.key = Matrix(key, ring=GF2_aes)
|
|
128
|
+
else:
|
|
129
|
+
raise NotImplementedError("Object must be 16 bytes or 4x4 list of bytes.")
|
|
130
|
+
|
|
131
|
+
def __repr__(self):
|
|
132
|
+
return str(self.key)
|
|
133
|
+
|
|
134
|
+
def __bytes__(self):
|
|
135
|
+
tmp = bytearray()
|
|
136
|
+
for i in range(4):
|
|
137
|
+
for j in range(4):
|
|
138
|
+
tmp.append(int(self.key[j, i]))
|
|
139
|
+
return bytes(tmp)
|
|
140
|
+
|
|
141
|
+
def next(self) -> None:
|
|
142
|
+
"Apply one round of the key shedule and return the next key."
|
|
143
|
+
self.round += 1
|
|
144
|
+
#RotWord
|
|
145
|
+
tmp = list(self.key[:,3])
|
|
146
|
+
if self.showtmp: print("Start :" + str(tmp))
|
|
147
|
+
tmp = rotate_left(tmp, 1)
|
|
148
|
+
if self.showtmp: print("RotWord :" + str(tmp))
|
|
149
|
+
#SubWord
|
|
150
|
+
tmp = list(map(lambda x: x.sbox(), tmp))
|
|
151
|
+
if self.showtmp: print("SubWord :" + str(tmp))
|
|
152
|
+
#AddRound constant
|
|
153
|
+
tmp[0] += GF2_aes(2)**(self.round - 1)
|
|
154
|
+
if self.showtmp: print("AddConst :" + str(tmp))
|
|
155
|
+
tmp = Matrix(tmp)
|
|
156
|
+
self.key[:,0] += tmp
|
|
157
|
+
self.key[:,1] += self.key[:,0]
|
|
158
|
+
self.key[:,2] += self.key[:,1]
|
|
159
|
+
self.key[:,3] += self.key[:,2]
|
|
160
|
+
|
|
161
|
+
def gen_keys(self) -> list:
|
|
162
|
+
"Generate all round keys."
|
|
163
|
+
if self.round:
|
|
164
|
+
raise ValueError('Some keys have already been generated!')
|
|
165
|
+
keys = [ GF2_aes(1) * self.key ] # we multiply with one to get a copy
|
|
166
|
+
for _ in range(10):
|
|
167
|
+
self.next()
|
|
168
|
+
keys.append(GF2_aes(1) * self.key)
|
|
169
|
+
return keys
|
|
170
|
+
|
|
171
|
+
class AESBlockCipher(BlockCipher):
|
|
172
|
+
blocksize = 16
|
|
173
|
+
def set_key(self, key: bytes):
|
|
174
|
+
if not isinstance(key, bytes) and len(key) != self.__class__.blocksize:
|
|
175
|
+
raise ValueError(f"Key must be {self.__class__.blocksize} bytes.")
|
|
176
|
+
self.key = key
|
|
177
|
+
self.cipher = AESCipher(key)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generic class for a block cipher.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from secrets import token_bytes as randbytes
|
|
6
|
+
|
|
7
|
+
def bytexor(a: bytes, b: bytes) -> bytes:
|
|
8
|
+
"Xor two byte strings."
|
|
9
|
+
return bytes([x ^ y for (x, y) in zip(a, b)])
|
|
10
|
+
|
|
11
|
+
class BlockCipher:
|
|
12
|
+
"Generic class for a block cipher."
|
|
13
|
+
|
|
14
|
+
blocksize: int = 1 # blocksize in bytes
|
|
15
|
+
|
|
16
|
+
def __init__(self, key: bytes|int = None, mode: str|None = None):
|
|
17
|
+
self.key = None # here we store the key (if any)
|
|
18
|
+
self.cipher = None # here we can store an instance of the cipher initialized with the key
|
|
19
|
+
self.mode = mode # here we store the default en/decryption mode
|
|
20
|
+
if key:
|
|
21
|
+
self.set_key(key)
|
|
22
|
+
|
|
23
|
+
def set_key(self, key: bytes|int):
|
|
24
|
+
"Store a new key and initialize the cipher with this key."
|
|
25
|
+
if key is not None:
|
|
26
|
+
if isinstance(key, bytes) and len(key) != self.__class__.blocksize:
|
|
27
|
+
raise ValueError(f"Key must be {self.__class__.blocksize} bytes.")
|
|
28
|
+
self.key = key
|
|
29
|
+
|
|
30
|
+
def encrypt_block(self, b: bytes) -> bytes:
|
|
31
|
+
"Decrypt one block."
|
|
32
|
+
return self.cipher.encrypt(b)
|
|
33
|
+
|
|
34
|
+
def decrypt_block(self, b: bytes) -> bytes:
|
|
35
|
+
"Encrypt one block."
|
|
36
|
+
return self.cipher.decrypt(b)
|
|
37
|
+
|
|
38
|
+
def blocksplit(self, text: bytes, padding: bool):
|
|
39
|
+
"Split a byte string according to the blocksize. Pad (PKCS#7) if requested."
|
|
40
|
+
if padding and self.__class__.blocksize > 1: # padding according to PKCS#7
|
|
41
|
+
pad = -len(text) % self.__class__.blocksize
|
|
42
|
+
if pad == 0:
|
|
43
|
+
pad = self.__class__.blocksize
|
|
44
|
+
text = bytearray(text)
|
|
45
|
+
text.extend([pad]*pad) # pad
|
|
46
|
+
text = bytes(text)
|
|
47
|
+
return [text[i:i+self.__class__.blocksize] for i in range(0, len(text), self.__class__.blocksize)]
|
|
48
|
+
|
|
49
|
+
def encrypt(self, text: bytes, key: bytes|int|None=None, mode=None):
|
|
50
|
+
"Encrypt using the given mode."
|
|
51
|
+
if mode is None:
|
|
52
|
+
mode = self.mode
|
|
53
|
+
try:
|
|
54
|
+
method = getattr(self, "encrypt_" + mode.lower())
|
|
55
|
+
except:
|
|
56
|
+
raise ValueError("Unsupported encryption mode: ",mode.lower())
|
|
57
|
+
if key:
|
|
58
|
+
self.set_key(key)
|
|
59
|
+
return method(text)
|
|
60
|
+
|
|
61
|
+
def decrypt(self, ctext: bytes, key: bytes|int|None=None, mode=None):
|
|
62
|
+
"Decrypt using the given mode."
|
|
63
|
+
if mode is None:
|
|
64
|
+
mode = self.mode
|
|
65
|
+
try:
|
|
66
|
+
method = getattr(self, "decrypt_" + mode)
|
|
67
|
+
except:
|
|
68
|
+
raise ValueError("Unsupported encryption mode: ",mode)
|
|
69
|
+
if key:
|
|
70
|
+
self.set_key(key)
|
|
71
|
+
return method(ctext)
|
|
72
|
+
|
|
73
|
+
def encrypt_ecb(self, text: bytes, padding: bool = True) -> bytes:
|
|
74
|
+
"Encrypt using ECB mode."
|
|
75
|
+
ctext = bytearray(b'')
|
|
76
|
+
for b in self.blocksplit(text, padding):
|
|
77
|
+
ctext.extend(self.encrypt_block(b))
|
|
78
|
+
return bytes(ctext)
|
|
79
|
+
|
|
80
|
+
def decrypt_ecb(self, ctext: bytes, padding: bool = True) -> bytes:
|
|
81
|
+
"Decrypt using ECB mode."
|
|
82
|
+
text = bytearray(b'')
|
|
83
|
+
for b in self.blocksplit(ctext, False):
|
|
84
|
+
text.extend(self.decrypt_block(b))
|
|
85
|
+
if padding and self.__class__.blocksize > 1:
|
|
86
|
+
return bytes(text[:-text[-1]]) # unpad
|
|
87
|
+
return bytes(text)
|
|
88
|
+
|
|
89
|
+
def encrypt_cbc(self, text: bytes, iv: bytes | None = None, padding: bool = True) -> bytes:
|
|
90
|
+
"Encrypt using CBC mode."
|
|
91
|
+
ctext = bytearray(b'')
|
|
92
|
+
if iv == None:
|
|
93
|
+
bb = randbytes(self.__class__.blocksize) # iv
|
|
94
|
+
else:
|
|
95
|
+
bb = iv
|
|
96
|
+
ctext.extend(bb)
|
|
97
|
+
for b in self.blocksplit(text, padding):
|
|
98
|
+
bb = self.encrypt_block(bytexor(bb, b))
|
|
99
|
+
ctext.extend(bb)
|
|
100
|
+
return bytes(ctext)
|
|
101
|
+
|
|
102
|
+
def decrypt_cbc(self, ctext: bytes, padding: bool = True) -> bytes:
|
|
103
|
+
"Decrypt using CBC mode."
|
|
104
|
+
text = bytearray(b'')
|
|
105
|
+
bb = ctext[:self.__class__.blocksize] # iv
|
|
106
|
+
ctext = ctext[self.__class__.blocksize:] # remove iv
|
|
107
|
+
for b in self.blocksplit(ctext, False):
|
|
108
|
+
text.extend(bytexor(bb, self.decrypt_block(b)))
|
|
109
|
+
bb = b
|
|
110
|
+
if padding and self.__class__.blocksize > 1:
|
|
111
|
+
return bytes(text[:-text[-1]]) # unpad
|
|
112
|
+
return bytes(text)
|
|
113
|
+
|
|
114
|
+
def encrypt_cfb(self, text: bytes, iv: bytes | None = None) -> bytes:
|
|
115
|
+
"Encrypt using CFB mode."
|
|
116
|
+
ctext = bytearray(b'')
|
|
117
|
+
if iv == None:
|
|
118
|
+
bb = randbytes(self.__class__.blocksize) # iv
|
|
119
|
+
else:
|
|
120
|
+
bb = iv
|
|
121
|
+
ctext.extend(bb) # add iv as first block of the cipher text
|
|
122
|
+
for b in self.blocksplit(text, False):
|
|
123
|
+
z = self.encrypt_block(bb)
|
|
124
|
+
bb = bytexor(b, z)
|
|
125
|
+
ctext.extend(bb)
|
|
126
|
+
return bytes(ctext)
|
|
127
|
+
|
|
128
|
+
def decrypt_cfb(self, ctext: bytes) -> bytes:
|
|
129
|
+
"Decrypt using CFB mode."
|
|
130
|
+
text = bytearray(b'')
|
|
131
|
+
bb = ctext[:self.__class__.blocksize] # iv
|
|
132
|
+
ctext = ctext[self.__class__.blocksize:] # remove iv
|
|
133
|
+
for b in self.blocksplit(ctext, False):
|
|
134
|
+
z = self.encrypt_block(bb)
|
|
135
|
+
text.extend(bytexor(b, z))
|
|
136
|
+
bb = b
|
|
137
|
+
return bytes(text)
|
|
138
|
+
|
|
139
|
+
def encrypt_ofb(self, text: bytes, iv: bytes | None = None) -> bytes:
|
|
140
|
+
"Encrypt using OFB mode."
|
|
141
|
+
ctext = bytearray(b'')
|
|
142
|
+
if iv == None:
|
|
143
|
+
z = randbytes(self.__class__.blocksize) # iv
|
|
144
|
+
else:
|
|
145
|
+
z = iv
|
|
146
|
+
ctext.extend(z) # add iv as first block of the cipher text
|
|
147
|
+
for b in self.blocksplit(text, False):
|
|
148
|
+
z = self.encrypt_block(z)
|
|
149
|
+
ctext.extend(bytexor(b, z))
|
|
150
|
+
return bytes(ctext)
|
|
151
|
+
|
|
152
|
+
def decrypt_ofb(self, ctext: bytes) -> bytes:
|
|
153
|
+
"Decrypt using OFB mode."
|
|
154
|
+
text = bytearray(b'')
|
|
155
|
+
z = ctext[:self.__class__.blocksize] # iv
|
|
156
|
+
ctext = ctext[self.__class__.blocksize:] # remove iv
|
|
157
|
+
for b in self.blocksplit(ctext, False):
|
|
158
|
+
z = self.encrypt_block(z)
|
|
159
|
+
text.extend(bytexor(b, z))
|
|
160
|
+
return bytes(text)
|
|
161
|
+
|
|
162
|
+
def adv_ctr(self, z: bytes, s: int) -> bytes:
|
|
163
|
+
"Advance the counter."
|
|
164
|
+
zz = bytearray(z)
|
|
165
|
+
for i in range(self.__class__.blocksize-1, self.__class__.blocksize-s-1, -1):
|
|
166
|
+
c = z[i]+1
|
|
167
|
+
if (c < 256):
|
|
168
|
+
zz[i] = c
|
|
169
|
+
break
|
|
170
|
+
else:
|
|
171
|
+
zz[i] = 0
|
|
172
|
+
return zz
|
|
173
|
+
|
|
174
|
+
def encrypt_ctr(self, text: bytes, iv: bytes | None = None) -> bytes:
|
|
175
|
+
"Encrypt using CTR mode."
|
|
176
|
+
ctext = bytearray(b'')
|
|
177
|
+
if iv == None:
|
|
178
|
+
ctr = randbytes(self.__class__.blocksize) # iv
|
|
179
|
+
else:
|
|
180
|
+
ctr = iv
|
|
181
|
+
ctext.extend(ctr) # add iv as first block of the cipher text
|
|
182
|
+
for b in self.blocksplit(text, False):
|
|
183
|
+
z = self.encrypt_block(ctr)
|
|
184
|
+
ctext.extend(bytexor(b, z))
|
|
185
|
+
ctr = self.adv_ctr(ctr, self.__class__.blocksize)
|
|
186
|
+
return bytes(ctext)
|
|
187
|
+
|
|
188
|
+
def decrypt_ctr(self, ctext: bytes) -> bytes:
|
|
189
|
+
"Decrypt using CTR mode."
|
|
190
|
+
text = bytearray(b'')
|
|
191
|
+
ctr = ctext[:self.__class__.blocksize] # iv
|
|
192
|
+
ctext = ctext[self.__class__.blocksize:] # remove iv
|
|
193
|
+
for b in self.blocksplit(ctext, False):
|
|
194
|
+
z = self.encrypt_block(ctr)
|
|
195
|
+
text.extend(bytexor(b, z))
|
|
196
|
+
ctr = self.adv_ctr(ctr, self.__class__.blocksize)
|
|
197
|
+
return bytes(text)
|
|
198
|
+
|
|
199
|
+
def mac_cbc(self, text: bytes, padding: bool = True) -> bytes:
|
|
200
|
+
"CBC-MAC of a bytestring."
|
|
201
|
+
bb = b'\x00' * self.__class__.blocksize
|
|
202
|
+
for b in self.blocksplit(text, padding = padding):
|
|
203
|
+
bb = self.encrypt_block(bytexor(bb, b))
|
|
204
|
+
return bytes(bb)
|
|
205
|
+
|
|
206
|
+
def mac_cmac(self, text: bytes) -> bytes:
|
|
207
|
+
"CMAC of a bytestring."
|
|
208
|
+
R_b = {16: 0b10000111, 8: 0b11011, 4: 0b10001101,
|
|
209
|
+
2: 0b101011, 1: 0b11011}[self.__class__.blocksize]
|
|
210
|
+
|
|
211
|
+
bb = b'\x00' * self.__class__.blocksize
|
|
212
|
+
# Compute key K1
|
|
213
|
+
key_extra = self.encrypt_block(bb)
|
|
214
|
+
key_extra = int.from_bytes(key_extra, byteorder='big')
|
|
215
|
+
msb = key_extra >> (self.__class__.blocksize * 8 - 1)
|
|
216
|
+
key_extra <<= 1
|
|
217
|
+
if msb:
|
|
218
|
+
key_extra ^= 1 << self.__class__.blocksize * 8
|
|
219
|
+
key_extra ^= R_b
|
|
220
|
+
|
|
221
|
+
pad = len(text) % self.__class__.blocksize
|
|
222
|
+
if pad or not len(text):
|
|
223
|
+
# Compute key K2
|
|
224
|
+
text += b'\x80' + b'\x00' * (self.__class__.blocksize - pad - 1)
|
|
225
|
+
msb = key_extra >> (self.__class__.blocksize * 8 - 1)
|
|
226
|
+
key_extra <<= 1
|
|
227
|
+
if msb:
|
|
228
|
+
key_extra ^= 1 << self.__class__.blocksize * 8
|
|
229
|
+
key_extra ^= R_b
|
|
230
|
+
key_extra = key_extra.to_bytes(self.__class__.blocksize, byteorder='big') # K1 or K2 depending on padding
|
|
231
|
+
last_block = bytexor(text[-self.__class__.blocksize:], key_extra) # Xor extra key to the last block
|
|
232
|
+
text = text[:-self.__class__.blocksize] + last_block
|
|
233
|
+
for i in range(0, len(text), self.__class__.blocksize):
|
|
234
|
+
bb = self.encrypt_block(bytexor(bb, text[i:i+self.__class__.blocksize]))
|
|
235
|
+
return bytes(bb)
|
|
236
|
+
|
|
237
|
+
def gf_mult(self, x: int, y: int) -> int:
|
|
238
|
+
"Muliplication in GF(2^128)."
|
|
239
|
+
# r = 0b11011 << 27 # 1 + x + x^3 + x^4 (+ x^32)
|
|
240
|
+
# r = 0b11100001 << 56 # 1 + x + x^2 + x^7 (+ x^64)
|
|
241
|
+
r = 0b11100001 << 120 # 1 + x + x^2 + x^7 (+ x^128)
|
|
242
|
+
z = 0
|
|
243
|
+
v = y
|
|
244
|
+
for d in bin(x)[2:].zfill(128):
|
|
245
|
+
if int(d):
|
|
246
|
+
z ^= v
|
|
247
|
+
if v % 2:
|
|
248
|
+
v >>= 1
|
|
249
|
+
v ^= r
|
|
250
|
+
else:
|
|
251
|
+
v >>= 1
|
|
252
|
+
return z
|
|
253
|
+
|
|
254
|
+
def ghash(self, text: bytes, h: int, init: int = 0) -> bytes:
|
|
255
|
+
"Compute GHASH of a given byte string."
|
|
256
|
+
assert self.__class__.blocksize == 16, "GCTR mode requires a blocksize of 128 bit."
|
|
257
|
+
pad = len(text) % self.__class__.blocksize
|
|
258
|
+
if pad > 0:
|
|
259
|
+
text += b"\x00" * (self.__class__.blocksize - pad)
|
|
260
|
+
y = init
|
|
261
|
+
for i in range(len(text) // self.__class__.blocksize):
|
|
262
|
+
x = int.from_bytes(
|
|
263
|
+
text[i * self.__class__.blocksize: (i + 1) * self.__class__.blocksize], byteorder="big")
|
|
264
|
+
y = self.gf_mult(h, x ^ y)
|
|
265
|
+
return y
|
|
266
|
+
|
|
267
|
+
def _aad_helper(self, len_text:int, aad: bytes | None):
|
|
268
|
+
"Compute the tag for the additional authenticated data."
|
|
269
|
+
zero = b'\x00' * self.__class__.blocksize
|
|
270
|
+
h = int.from_bytes(self.encrypt_block(zero), byteorder="big")
|
|
271
|
+
len_text *= 8
|
|
272
|
+
assert len_text.bit_length() <= 4 * self.__class__.blocksize, "Message too long!"
|
|
273
|
+
len_block = len_text.to_bytes((self.__class__.blocksize // 2), byteorder="big")
|
|
274
|
+
if aad is not None:
|
|
275
|
+
len_aad = len(aad) * 8
|
|
276
|
+
assert len_aad.bit_length() <= 4 * self.__class__.blocksize, "Additional authenticated data too long!"
|
|
277
|
+
len_block = len_aad.to_bytes(
|
|
278
|
+
(self.__class__.blocksize // 2), byteorder="big") + len_block
|
|
279
|
+
tag = self.ghash(aad, h)
|
|
280
|
+
else:
|
|
281
|
+
len_block = b'\x00' * (self.__class__.blocksize // 2) + len_block
|
|
282
|
+
tag = 0
|
|
283
|
+
return h, len_block, tag
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def encrypt_gcm(self, text: bytes, iv: bytes | None = None, aad: bytes | None = None) -> bytes:
|
|
287
|
+
"Encrypt using Galois CTR mode."
|
|
288
|
+
assert self.__class__.blocksize == 16, "GHASH requires a blocksize of 16 bytes."
|
|
289
|
+
ctext = bytearray(b'')
|
|
290
|
+
len_ctr = self.__class__.blocksize // 4
|
|
291
|
+
if iv == None:
|
|
292
|
+
iv = randbytes(self.__class__.blocksize - len_ctr)
|
|
293
|
+
else:
|
|
294
|
+
assert len(iv) + \
|
|
295
|
+
len_ctr == self.__class__.blocksize, "IV has inappropriate length!"
|
|
296
|
+
ctr = iv + (len_ctr - 1) * b'\x00' + b'\x01'
|
|
297
|
+
ctr0 = ctr
|
|
298
|
+
ctext.extend(iv)
|
|
299
|
+
h, len_block, tag = self._aad_helper(len(text), aad)
|
|
300
|
+
for b in self.blocksplit(text, 0):
|
|
301
|
+
ctr = self.adv_ctr(ctr, len_ctr)
|
|
302
|
+
z = self.encrypt_block(ctr)
|
|
303
|
+
ctext.extend(bytexor(b, z))
|
|
304
|
+
tag = self.ghash(ctext[self.__class__.blocksize - len_ctr:], h, init=tag)
|
|
305
|
+
tag = self.ghash(len_block, h, init=tag).to_bytes(
|
|
306
|
+
self.__class__.blocksize, byteorder="big")
|
|
307
|
+
z = self.encrypt_block(ctr0)
|
|
308
|
+
ctext.extend(bytexor(tag, z))
|
|
309
|
+
return bytes(ctext)
|
|
310
|
+
|
|
311
|
+
def decrypt_gcm(self, ctext: bytes, aad: bytes | None = None) -> bytes:
|
|
312
|
+
"Decrypt using Galois CTR mode."
|
|
313
|
+
assert self.__class__.blocksize == 16, "GHASH requires a blocksize of 16 bytes."
|
|
314
|
+
text = bytearray(b'')
|
|
315
|
+
len_ctr = self.__class__.blocksize // 4
|
|
316
|
+
iv = ctext[:self.__class__.blocksize - len_ctr] # iv
|
|
317
|
+
ctr = iv + (len_ctr - 1) * b'\x00' + b'\x01'
|
|
318
|
+
mac = ctext[-self.__class__.blocksize:] # mac
|
|
319
|
+
ctext = ctext[self.__class__.blocksize - len_ctr:-self.__class__.blocksize]
|
|
320
|
+
h, len_block, tag = self._aad_helper(len(ctext), aad)
|
|
321
|
+
tag = self.ghash(ctext, h, init=tag)
|
|
322
|
+
tag = self.ghash(len_block, h, init=tag).to_bytes(
|
|
323
|
+
self.__class__.blocksize, byteorder="big")
|
|
324
|
+
z = self.encrypt_block(ctr)
|
|
325
|
+
tag = bytexor(tag, z)
|
|
326
|
+
assert tag == mac, "Incorrect MAC!"
|
|
327
|
+
for b in self.blocksplit(ctext, 0):
|
|
328
|
+
ctr = self.adv_ctr(ctr, len_ctr)
|
|
329
|
+
z = self.encrypt_block(ctr)
|
|
330
|
+
text.extend(bytexor(b, z))
|
|
331
|
+
return bytes(text)
|