ccssh 1.0.0__py3-none-any.whl

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.
ccssh/crypto.py ADDED
@@ -0,0 +1,316 @@
1
+ """
2
+ Symmetric packet protection: AES-CTR + HMAC, key derivation, framing helpers.
3
+
4
+ Primitives come from `cryptography`; all protocol framing is ours.
5
+ Covers RFC 4253 section 6 (binary packet protocol) and section 7.2 (KDF),
6
+ plus OpenSSH *-etm modes (length sent in the clear, see packet.c).
7
+ """
8
+ from __future__ import annotations
9
+ import hashlib
10
+ import os
11
+ import struct
12
+
13
+ from cryptography.hazmat.primitives import hashes, hmac
14
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
15
+
16
+
17
+ def HASH_sha256(data: bytes) -> bytes:
18
+ return hashlib.sha256(data).digest()
19
+
20
+
21
+ def kdf(hash_fn, K_encoded: bytes, H: bytes, session_id: bytes, letter: bytes, out_len: int) -> bytes:
22
+ """
23
+ RFC 4253 section 7.2 key expansion.
24
+
25
+ K1 = HASH(K || H || X || session_id), Kn+1 = HASH(K || H || K1 || .. || Kn).
26
+ ``hash_fn`` must be the *key exchange* hash (e.g. SHA-256 for
27
+ curve25519-sha256), never derived from the cipher/MAC choice.
28
+ """
29
+ if len(letter) != 1:
30
+ raise ValueError("letter must be a single byte")
31
+ if not 0 < out_len <= 1024:
32
+ raise ValueError("kdf out_len out of range")
33
+ out = hash_fn(K_encoded + H + letter + session_id)
34
+ while len(out) < out_len:
35
+ out += hash_fn(K_encoded + H + out)
36
+ return out[:out_len]
37
+
38
+
39
+ CIPHER_SPECS = {
40
+ "aes128-ctr": {"key_len": 16, "iv_len": 16, "block": 16},
41
+ "aes192-ctr": {"key_len": 24, "iv_len": 16, "block": 16},
42
+ "aes256-ctr": {"key_len": 32, "iv_len": 16, "block": 16},
43
+ }
44
+ """
45
+ Legacy chained-IV CBC (RFC 4253 section 6): disabled by default, see
46
+ algs.LEGACY_CIPHERS. Same framing as CTR; only the crypt operation differs.
47
+ """
48
+ CBC_SPECS = {
49
+ "aes128-cbc": {"prim": "aes", "key_len": 16, "iv_len": 16, "block": 16},
50
+ "aes192-cbc": {"prim": "aes", "key_len": 24, "iv_len": 16, "block": 16},
51
+ "aes256-cbc": {"prim": "aes", "key_len": 32, "iv_len": 16, "block": 16},
52
+ "3des-cbc": {"prim": "3des", "key_len": 24, "iv_len": 8, "block": 8},
53
+ }
54
+ MAC_SPECS = {
55
+ "hmac-sha2-256": {"key_len": 32, "out_len": 32, "etm": False},
56
+ "hmac-sha2-512": {"key_len": 64, "out_len": 64, "etm": False},
57
+ "hmac-sha1": {"key_len": 20, "out_len": 20, "etm": False},
58
+ "hmac-sha2-256-etm@openssh.com": {"key_len": 32, "out_len": 32, "etm": True},
59
+ "hmac-sha2-512-etm@openssh.com": {"key_len": 64, "out_len": 64, "etm": True},
60
+ "hmac-sha1-etm@openssh.com": {"key_len": 20, "out_len": 20, "etm": True},
61
+ "hmac-sha1-96": {"key_len": 20, "out_len": 12, "etm": False},
62
+ "hmac-sha1-96-etm@openssh.com": {"key_len": 20, "out_len": 12, "etm": True},
63
+ "hmac-md5": {"key_len": 16, "out_len": 16, "etm": False},
64
+ "hmac-md5-etm@openssh.com": {"key_len": 16, "out_len": 16, "etm": True},
65
+ "hmac-md5-96": {"key_len": 16, "out_len": 12, "etm": False},
66
+ "hmac-md5-96-etm@openssh.com": {"key_len": 16, "out_len": 12, "etm": True},
67
+ "umac-64@openssh.com": {"key_len": 16, "out_len": 8, "etm": False},
68
+ "umac-128@openssh.com": {"key_len": 16, "out_len": 16, "etm": False},
69
+ "umac-64-etm@openssh.com": {"key_len": 16, "out_len": 8, "etm": True},
70
+ "umac-128-etm@openssh.com": {"key_len": 16, "out_len": 16, "etm": True},
71
+ }
72
+ _HASH_BY_MAC = {
73
+ "hmac-sha2-256": hashlib.sha256,
74
+ "hmac-sha2-256-etm@openssh.com": hashlib.sha256,
75
+ "hmac-sha2-512": hashlib.sha512,
76
+ "hmac-sha2-512-etm@openssh.com": hashlib.sha512,
77
+ "hmac-sha1": hashlib.sha1,
78
+ "hmac-sha1-etm@openssh.com": hashlib.sha1,
79
+ "hmac-sha1-96": hashlib.sha1,
80
+ "hmac-sha1-96-etm@openssh.com": hashlib.sha1,
81
+ "hmac-md5": hashlib.md5,
82
+ "hmac-md5-etm@openssh.com": hashlib.md5,
83
+ "hmac-md5-96": hashlib.md5,
84
+ "hmac-md5-96-etm@openssh.com": hashlib.md5,
85
+ }
86
+
87
+
88
+ def _kdf_hash(cipher: str, mac: str, hashfn):
89
+ """
90
+ Resolve the KDF hash: explicit KEX hash wins, else MAC-implied (legacy).
91
+
92
+ Production callers always pass the KEX hash (RFC 4253 section 7.2);
93
+ the fallback exists for the test stub and ad-hoc callers, which only
94
+ use SHA-256 KEX methods there.
95
+ """
96
+ if hashfn is not None:
97
+ return hashfn
98
+ digestmod = _HASH_BY_MAC.get(mac, hashlib.sha256)
99
+ return lambda d: digestmod(d).digest()
100
+
101
+
102
+ def derive_keys(K_encoded: bytes, H: bytes, session_id: bytes, cipher: str, mac: str, hashfn=None) -> dict[str, bytes]:
103
+ """Derive the six direction keys (A..F). Prefer passing the KEX hash."""
104
+ try:
105
+ cs = CIPHER_SPECS[cipher]
106
+ except KeyError:
107
+ try:
108
+ cs = CBC_SPECS[cipher]
109
+ except KeyError as e:
110
+ raise ValueError(f"unsupported cipher {cipher}") from e
111
+ try:
112
+ ms = MAC_SPECS[mac]
113
+ except KeyError as e:
114
+ raise ValueError(f"unsupported mac {mac}") from e
115
+ hf = _kdf_hash(cipher, mac, hashfn)
116
+ return {
117
+ "iv_c2s": kdf(hf, K_encoded, H, session_id, b"A", cs["iv_len"]),
118
+ "iv_s2c": kdf(hf, K_encoded, H, session_id, b"B", cs["iv_len"]),
119
+ "enc_c2s": kdf(hf, K_encoded, H, session_id, b"C", cs["key_len"]),
120
+ "enc_s2c": kdf(hf, K_encoded, H, session_id, b"D", cs["key_len"]),
121
+ "mac_c2s": kdf(hf, K_encoded, H, session_id, b"E", ms["key_len"]),
122
+ "mac_s2c": kdf(hf, K_encoded, H, session_id, b"F", ms["key_len"]),
123
+ }
124
+
125
+
126
+ try:
127
+ from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
128
+ except ImportError: # older cryptography: pre-move location (warns, still works)
129
+ from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
130
+
131
+ _HASH_CLASSES = {
132
+ "hmac-sha2-256": hashes.SHA256,
133
+ "hmac-sha2-256-etm@openssh.com": hashes.SHA256,
134
+ "hmac-sha2-512": hashes.SHA512,
135
+ "hmac-sha2-512-etm@openssh.com": hashes.SHA512,
136
+ "hmac-sha1": hashes.SHA1,
137
+ "hmac-sha1-etm@openssh.com": hashes.SHA1,
138
+ "hmac-sha1-96": hashes.SHA1,
139
+ "hmac-sha1-96-etm@openssh.com": hashes.SHA1,
140
+ "hmac-md5": hashes.MD5,
141
+ "hmac-md5-etm@openssh.com": hashes.MD5,
142
+ "hmac-md5-96": hashes.MD5,
143
+ "hmac-md5-96-etm@openssh.com": hashes.MD5,
144
+ }
145
+
146
+
147
+ class SshMac:
148
+ """
149
+ Packet MAC shared by the CTR and CBC direction contexts: HMAC-*
150
+ (plain and -etm, full and -96 truncated) or UMAC-64/128
151
+ (draft-miller-secsh-umac-01, seq as uint64 nonce).
152
+ """
153
+
154
+ def __init__(self, mac: str, mac_key: bytes):
155
+ try:
156
+ ms = MAC_SPECS[mac]
157
+ except KeyError as e:
158
+ raise ValueError(f"unsupported mac: {mac}") from e
159
+ if len(mac_key) != ms["key_len"]:
160
+ raise ValueError(f"bad mac key length for {mac}")
161
+ self.mac, self.mac_len, self.etm = mac, ms["out_len"], ms["etm"]
162
+ self._mac_key = mac_key
163
+ self._umac = None
164
+ self._hcls = None
165
+ if mac.startswith("umac-"):
166
+ from ccssh import umac as _umac_mod
167
+ self._umac = _umac_mod.UMacCtx(mac_key, ms["out_len"])
168
+ else:
169
+ try:
170
+ self._hcls = _HASH_CLASSES[mac]
171
+ except KeyError as e:
172
+ raise ValueError(f"unsupported mac: {mac}") from e
173
+
174
+ def mac_of(self, seq: int, data: bytes) -> bytes:
175
+ if not 0 <= seq < 2**32:
176
+ raise ValueError("sequence number out of range")
177
+ if self._umac is not None:
178
+ return self._umac.tag(seq, data)
179
+ h = hmac.HMAC(self._mac_key, self._hcls())
180
+ h.update(struct.pack(">I", seq) + data)
181
+ return h.finalize()[:self.mac_len]
182
+
183
+
184
+ class AesCtrHmacCtx:
185
+ """
186
+ One direction of AES-CTR + MAC. Stateful CTR via a held encryptor.
187
+
188
+ ``etm`` switches the MAC input from plaintext to ciphertext; the caller
189
+ (transport) owns framing for both cases.
190
+ """
191
+
192
+ def __init__(self, cipher: str, mac: str, enc_key: bytes, iv: bytes, mac_key: bytes):
193
+ try:
194
+ cs = CIPHER_SPECS[cipher]
195
+ except KeyError as e:
196
+ raise ValueError(f"unsupported cipher/mac: {cipher}/{mac}") from e
197
+ if len(enc_key) != cs["key_len"] or len(iv) != cs["iv_len"]:
198
+ raise ValueError(f"bad enc/iv lengths for {cipher}")
199
+ self.cipher, self.mac = cipher, mac
200
+ self._mac = SshMac(mac, mac_key)
201
+ self.etm = self._mac.etm
202
+ self.mac_len = self._mac.mac_len
203
+ self.block_size = cs["block"]
204
+ enc = Cipher(algorithms.AES(enc_key), modes.CTR(iv)).encryptor()
205
+ dec = Cipher(algorithms.AES(enc_key), modes.CTR(iv)).decryptor()
206
+ self._enc, self._dec = enc, dec
207
+
208
+ def mac_of(self, seq: int, data: bytes) -> bytes:
209
+ return self._mac.mac_of(seq, data)
210
+
211
+ def encrypt_stream(self, data: bytes) -> bytes:
212
+ return self._enc.update(data)
213
+
214
+ def decrypt_stream(self, data: bytes) -> bytes:
215
+ return self._dec.update(data)
216
+
217
+
218
+ class CbcHmacCtx:
219
+ """
220
+ One direction of chained-IV CBC + MAC (legacy: aes*-cbc, 3des-cbc).
221
+
222
+ SSH CBC chains the IV across packets (RFC 4253 section 6), so the
223
+ encryptor/decryptor are held like the CTR ones. Disabled by default
224
+ (see algs.LEGACY_CIPHERS); force explicitly to interoperate.
225
+ """
226
+
227
+ def __init__(self, cipher: str, mac: str, enc_key: bytes, iv: bytes, mac_key: bytes):
228
+ try:
229
+ cs = CBC_SPECS[cipher]
230
+ except KeyError as e:
231
+ raise ValueError(f"unsupported cipher/mac: {cipher}/{mac}") from e
232
+ if len(enc_key) != cs["key_len"] or len(iv) != cs["iv_len"]:
233
+ raise ValueError(f"bad enc/iv lengths for {cipher}")
234
+ self.cipher, self.mac = cipher, mac
235
+ self._mac = SshMac(mac, mac_key)
236
+ self.etm = self._mac.etm
237
+ self.mac_len = self._mac.mac_len
238
+ self.block_size = cs["block"]
239
+ if cs["prim"] == "3des":
240
+ enc = Cipher(TripleDES(enc_key), modes.CBC(iv)).encryptor()
241
+ dec = Cipher(TripleDES(enc_key), modes.CBC(iv)).decryptor()
242
+ else:
243
+ enc = Cipher(algorithms.AES(enc_key), modes.CBC(iv)).encryptor()
244
+ dec = Cipher(algorithms.AES(enc_key), modes.CBC(iv)).decryptor()
245
+ self._enc, self._dec = enc, dec
246
+
247
+ def mac_of(self, seq: int, data: bytes) -> bytes:
248
+ return self._mac.mac_of(seq, data)
249
+
250
+ def encrypt_stream(self, data: bytes) -> bytes:
251
+ if len(data) % self.block_size != 0:
252
+ raise ValueError("CBC encrypt length must be a block multiple")
253
+ return self._enc.update(data)
254
+
255
+ def decrypt_stream(self, data: bytes) -> bytes:
256
+ if len(data) % self.block_size != 0:
257
+ raise ValueError("CBC decrypt length must be a block multiple")
258
+ return self._dec.update(data)
259
+
260
+
261
+ def pad_packet(payload_len_plus1: int, block_size: int) -> int:
262
+ """
263
+ Padding length so that (4 + 1 + payload + pad) is a block multiple.
264
+
265
+ ``payload_len_plus1`` is 1 (padding-length byte) + len(payload).
266
+ For ciphers where packet_length itself is encrypted (CBC/CTR/non-ETM,
267
+ and plaintext before NEWKEYS). Minimum padding is 4 bytes.
268
+ """
269
+ pad = block_size - ((4 + payload_len_plus1) % block_size)
270
+ if pad < 4:
271
+ pad += block_size
272
+ if pad > 255:
273
+ raise ValueError("padding exceeds 255 bytes")
274
+ return pad
275
+
276
+
277
+ def pad_len_aad(payload_len: int, block_size: int) -> int:
278
+ """
279
+ Padding length so that (1 + payload + pad) is a block multiple.
280
+
281
+ For modes where packet_length travels unencrypted (ETM, AES-GCM with
282
+ block 16, chacha20-poly1305 with block 8). OpenSSH drops packets with
283
+ wrong alignment ("padding error").
284
+ """
285
+ pad = block_size - ((1 + payload_len) % block_size)
286
+ if pad < 4:
287
+ pad += block_size
288
+ if pad > 255:
289
+ raise ValueError("padding exceeds 255 bytes")
290
+ return pad
291
+
292
+
293
+ def build_unencrypted_block(payload: bytes, block_size: int) -> bytes:
294
+ """Assemble packet_length || padding_length || payload || padding."""
295
+ # SSH packet_length (payload+padding+1) must fit MAX_PACKET=35000;
296
+ # SFTP data responses legitimately carry ~32KB + framing overhead,
297
+ # so cap the final packet length, not the raw payload.
298
+ pad_len = pad_packet(1 + len(payload), block_size)
299
+ pkt_len = 1 + len(payload) + pad_len
300
+ if pkt_len > 35000:
301
+ raise ValueError(f"packet too large (packet_length={pkt_len})")
302
+ return struct.pack(">I", pkt_len) + bytes([pad_len]) + payload + os.urandom(pad_len)
303
+
304
+
305
+ def build_aad_block(payload: bytes, block_size: int) -> tuple[bytes, bytes]:
306
+ """
307
+ Assemble (length_field, body) for length-in-clear modes.
308
+
309
+ Returns the 4-byte big-endian packet_length and the
310
+ padding_length || payload || padding body.
311
+ """
312
+ pad_len = pad_len_aad(len(payload), block_size)
313
+ body = bytes([pad_len]) + payload + os.urandom(pad_len)
314
+ if len(body) > 35000:
315
+ raise ValueError(f"packet too large (packet_length={len(body)})")
316
+ return struct.pack(">I", len(body)), body
ccssh/dh_groups.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ RFC 3526 MODP primes, extracted mechanically from rfc3526.txt (no hand copying).
3
+
4
+ Plus the Oakley Group 2 1024-bit prime (RFC 2409 section 6.2) for the
5
+ legacy diffie-hellman-group1-sha1 method: weak by modern standards,
6
+ disabled by default (see algs.LEGACY_KEX), force explicitly to use.
7
+ Its bit length is asserted like the rest; live interop against a real
8
+ sshd proves the digits.
9
+ """
10
+ P14_2048_HEX = (
11
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
12
+ "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
13
+ "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
14
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
15
+ "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
16
+ "9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
17
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
18
+ "3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"
19
+ )
20
+ P14_2048 = int(P14_2048_HEX, 16)
21
+ if P14_2048.bit_length() != 2048:
22
+ raise ValueError("group14 prime corrupted")
23
+ P1_1024_HEX = (
24
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
25
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
26
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
27
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
28
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
29
+ "FFFFFFFFFFFFFFFF"
30
+ )
31
+ P1_1024 = int(P1_1024_HEX, 16)
32
+ if P1_1024.bit_length() != 1024:
33
+ raise ValueError("group1 prime corrupted")
34
+ P16_4096_HEX = (
35
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
36
+ "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
37
+ "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
38
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
39
+ "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
40
+ "9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
41
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
42
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
43
+ "A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
44
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
45
+ "D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
46
+ "08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
47
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8"
48
+ "DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
49
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
50
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF"
51
+ )
52
+ P16_4096 = int(P16_4096_HEX, 16)
53
+ if P16_4096.bit_length() != 4096:
54
+ raise ValueError("group16 prime corrupted")
55
+ P18_8192_HEX = (
56
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
57
+ "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
58
+ "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
59
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
60
+ "98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
61
+ "9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
62
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
63
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
64
+ "A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
65
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
66
+ "D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
67
+ "08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
68
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8"
69
+ "DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
70
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
71
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026"
72
+ "C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE"
73
+ "B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B"
74
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC"
75
+ "F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E"
76
+ "59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
77
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76"
78
+ "F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468"
79
+ "043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4"
80
+ "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED"
81
+ "2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D"
82
+ "E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B"
83
+ "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6"
84
+ "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D"
85
+ "F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92"
86
+ "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA"
87
+ "9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF"
88
+ )
89
+ P18_8192 = int(P18_8192_HEX, 16)
90
+ if P18_8192.bit_length() != 8192:
91
+ raise ValueError("group18 prime corrupted")
92
+ G = 2