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.
Files changed (51) hide show
  1. {kryptools-0.9.14 → kryptools-1.0}/PKG-INFO +6 -2
  2. {kryptools-0.9.14 → kryptools-1.0}/README.md +4 -0
  3. {kryptools-0.9.14 → kryptools-1.0}/kryptools/GF2.py +26 -0
  4. {kryptools-0.9.14 → kryptools-1.0}/kryptools/__init__.py +5 -2
  5. kryptools-1.0/kryptools/aes.py +177 -0
  6. kryptools-1.0/kryptools/blockcipher.py +331 -0
  7. kryptools-1.0/kryptools/des.py +338 -0
  8. {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp.py +1 -1
  9. {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_bsgs.py +1 -1
  10. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_ecm.py +1 -1
  11. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_pm1.py +17 -16
  12. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_qs.py +7 -7
  13. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_rho.py +3 -3
  14. {kryptools-0.9.14 → kryptools-1.0}/kryptools/la.py +15 -14
  15. {kryptools-0.9.14 → kryptools-1.0}/kryptools/nt.py +13 -2
  16. {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/PKG-INFO +6 -2
  17. {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/SOURCES.txt +6 -0
  18. {kryptools-0.9.14 → kryptools-1.0}/pyproject.toml +1 -1
  19. kryptools-1.0/tests/test_aes.py +162 -0
  20. kryptools-1.0/tests/test_des.py +42 -0
  21. kryptools-1.0/tests/test_factor_pm1.py +8 -0
  22. {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor_qs.py +0 -1
  23. {kryptools-0.9.14 → kryptools-1.0}/tests/test_nt.py +4 -3
  24. {kryptools-0.9.14 → kryptools-1.0}/tests/test_primes.py +17 -1
  25. {kryptools-0.9.14 → kryptools-1.0}/LICENSE +0 -0
  26. {kryptools-0.9.14 → kryptools-1.0}/kryptools/Zmod.py +0 -0
  27. {kryptools-0.9.14 → kryptools-1.0}/kryptools/conway_polynomials.py +0 -0
  28. {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_ic.py +0 -0
  29. {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_qs.py +0 -0
  30. {kryptools-0.9.14 → kryptools-1.0}/kryptools/dlp_rho.py +0 -0
  31. {kryptools-0.9.14 → kryptools-1.0}/kryptools/ec.py +0 -0
  32. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor.py +0 -0
  33. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_dix.py +0 -0
  34. {kryptools-0.9.14 → kryptools-1.0}/kryptools/factor_fmt.py +0 -0
  35. {kryptools-0.9.14 → kryptools-1.0}/kryptools/intfuncs.py +0 -0
  36. {kryptools-0.9.14 → kryptools-1.0}/kryptools/lat.py +0 -0
  37. {kryptools-0.9.14 → kryptools-1.0}/kryptools/poly.py +0 -0
  38. {kryptools-0.9.14 → kryptools-1.0}/kryptools/primes.py +0 -0
  39. {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/dependency_links.txt +0 -0
  40. {kryptools-0.9.14 → kryptools-1.0}/kryptools.egg-info/top_level.txt +0 -0
  41. {kryptools-0.9.14 → kryptools-1.0}/setup.cfg +0 -0
  42. {kryptools-0.9.14 → kryptools-1.0}/tests/test_GF2.py +0 -0
  43. {kryptools-0.9.14 → kryptools-1.0}/tests/test_Zmod.py +0 -0
  44. {kryptools-0.9.14 → kryptools-1.0}/tests/test_dlog.py +0 -0
  45. {kryptools-0.9.14 → kryptools-1.0}/tests/test_ec.py +0 -0
  46. {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor.py +0 -0
  47. {kryptools-0.9.14 → kryptools-1.0}/tests/test_factor_fmt.py +0 -0
  48. {kryptools-0.9.14 → kryptools-1.0}/tests/test_intfuncs.py +0 -0
  49. {kryptools-0.9.14 → kryptools-1.0}/tests/test_la.py +0 -0
  50. {kryptools-0.9.14 → kryptools-1.0}/tests/test_lat.py +0 -0
  51. {kryptools-0.9.14 → kryptools-1.0}/tests/test_poly.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: kryptools
3
- Version: 0.9.14
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.14"
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)