From 6e414d27f8111451d931a3fe20e47b1e2ac74b4b Mon Sep 17 00:00:00 2001 From: MikeWang000000 Date: Sat, 29 Apr 2023 15:32:36 +0800 Subject: [PATCH] Migrate to Python 3 --- README.md | 114 ++++++++++++++++++------------------- bench.py | 18 +++--- pyaes.py | 167 ++++++++++++++++++++++++++++++------------------------ 3 files changed, 159 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index af66b50..901b6c9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ pyAES ===== -AES algorithm with pure python implementation. Small modify based on [https://bitbucket.org/intgr/pyaes/](https://bitbucket.org/intgr/pyaes/) to compatible with PEP-8. +AES algorithm with pure Python 3 implementation. Small modify based on [https://bitbucket.org/intgr/pyaes/](https://bitbucket.org/intgr/pyaes/) to compatible with PEP-8. Intro --- @@ -29,74 +29,72 @@ How to use? ``` Speed --- -Even though pyaes is an optimized Python implementation, Python itself is still slow. It should be capable of around 80 kB/s on modern hardware; __that's 1000x slower than pure C implementations__. +Even though pyAES is an optimized Python implementation, Python itself is still slow. It should be capable of around 300 kB/s on modern hardware; __that's 1000x slower than pure C implementations__. -This is a test in My Macbook Air(CPython and PyPy): +This is a test in My Macbook Air M1 (CPython and PyPy): ``` -$ python bench.py --all -AES-CBC-128 short encrypt: 2.3228 67004 cpb 43.7 kB/s -AES-CBC-128 short decrypt: 2.4256 69969 cpb 41.9 kB/s -AES-CBC-192 short encrypt: 2.7715 79947 cpb 36.6 kB/s -AES-CBC-192 short decrypt: 2.8919 83419 cpb 35.1 kB/s -AES-CBC-256 short encrypt: 3.2400 93462 cpb 31.3 kB/s -AES-CBC-256 short decrypt: 3.3679 97151 cpb 30.2 kB/s -AES-CBC-128 long encrypt: 2.3564 66700 cpb 43.9 kB/s -AES-CBC-128 long decrypt: 2.4683 69869 cpb 41.9 kB/s -AES-CBC-192 long encrypt: 2.8118 79591 cpb 36.8 kB/s -AES-CBC-192 long decrypt: 2.9336 83038 cpb 35.3 kB/s -AES-CBC-256 long encrypt: 3.3641 95226 cpb 30.8 kB/s -AES-CBC-256 long decrypt: 3.4369 97286 cpb 30.1 kB/s -AES-ECB-128 short encrypt: 2.3100 66634 cpb 44.0 kB/s -AES-ECB-128 short decrypt: 2.4134 69618 cpb 42.1 kB/s -AES-ECB-192 short encrypt: 2.8941 83483 cpb 35.1 kB/s -AES-ECB-192 short decrypt: 2.9094 83926 cpb 34.9 kB/s -AES-ECB-256 short encrypt: 3.2390 93432 cpb 31.4 kB/s -AES-ECB-256 short decrypt: 3.3983 98029 cpb 29.9 kB/s -AES-ECB-128 long encrypt: 2.4414 69107 cpb 42.4 kB/s -AES-ECB-128 long decrypt: 2.5723 72811 cpb 40.2 kB/s -AES-ECB-192 long encrypt: 2.9348 83073 cpb 35.3 kB/s -AES-ECB-192 long decrypt: 3.1270 88512 cpb 33.1 kB/s -AES-ECB-256 long encrypt: 3.7037 104839 cpb 27.9 kB/s -AES-ECB-256 long decrypt: 6.5956 186695 cpb 15.7 kB/s +$ python3 bench.py --all +AES-CBC-128 short encrypt: 0.3024 8723 cpb 335.9 kB/s +AES-CBC-128 short decrypt: 0.3330 9605 cpb 305.0 kB/s +AES-CBC-192 short encrypt: 0.3644 10512 cpb 278.7 kB/s +AES-CBC-192 short decrypt: 0.4015 11581 cpb 253.0 kB/s +AES-CBC-256 short encrypt: 0.4249 12256 cpb 239.0 kB/s +AES-CBC-256 short decrypt: 0.4644 13397 cpb 218.7 kB/s +AES-CBC-128 long encrypt: 0.3107 8796 cpb 333.1 kB/s +AES-CBC-128 long decrypt: 0.3385 9583 cpb 305.7 kB/s +AES-CBC-192 long encrypt: 0.3709 10498 cpb 279.1 kB/s +AES-CBC-192 long decrypt: 0.4061 11496 cpb 254.8 kB/s +AES-CBC-256 long encrypt: 0.4319 12224 cpb 239.7 kB/s +AES-CBC-256 long decrypt: 0.4746 13433 cpb 218.1 kB/s +AES-ECB-128 short encrypt: 0.2982 8601 cpb 340.6 kB/s +AES-ECB-128 short decrypt: 0.3252 9381 cpb 312.3 kB/s +AES-ECB-192 short encrypt: 0.3570 10299 cpb 284.5 kB/s +AES-ECB-192 short decrypt: 0.3940 11365 cpb 257.8 kB/s +AES-ECB-256 short encrypt: 0.4180 12058 cpb 243.0 kB/s +AES-ECB-256 short decrypt: 0.4570 13183 cpb 222.2 kB/s +AES-ECB-128 long encrypt: 0.3044 8617 cpb 340.0 kB/s +AES-ECB-128 long decrypt: 0.3334 9437 cpb 310.4 kB/s +AES-ECB-192 long encrypt: 0.3664 10373 cpb 282.4 kB/s +AES-ECB-192 long decrypt: 0.3980 11265 cpb 260.1 kB/s +AES-ECB-256 long encrypt: 0.4276 12103 cpb 242.1 kB/s +AES-ECB-256 long decrypt: 0.4663 13199 cpb 222.0 kB/s -$ pypy bench.py --all -AES-CBC-128 short encrypt: 0.3558 10263 cpb 285.5 kB/s -AES-CBC-128 short decrypt: 0.4569 13179 cpb 222.3 kB/s -AES-CBC-192 short encrypt: 0.2565 7400 cpb 395.9 kB/s -AES-CBC-192 short decrypt: 0.2916 8411 cpb 348.3 kB/s -AES-CBC-256 short encrypt: 0.2569 7410 cpb 395.4 kB/s -AES-CBC-256 short decrypt: 0.3692 10650 cpb 275.1 kB/s -AES-CBC-128 long encrypt: 0.2114 5985 cpb 489.5 kB/s -AES-CBC-128 long decrypt: 0.2103 5952 cpb 492.2 kB/s -AES-CBC-192 long encrypt: 0.2235 6325 cpb 463.2 kB/s -AES-CBC-192 long decrypt: 0.2317 6559 cpb 446.7 kB/s -AES-CBC-256 long encrypt: 0.2517 7125 cpb 411.2 kB/s -AES-CBC-256 long decrypt: 0.2549 7215 cpb 406.1 kB/s -AES-ECB-128 short encrypt: 0.3762 10852 cpb 270.0 kB/s -AES-ECB-128 short decrypt: 0.3265 9418 cpb 311.1 kB/s -AES-ECB-192 short encrypt: 0.2321 6695 cpb 437.6 kB/s -AES-ECB-192 short decrypt: 0.2962 8543 cpb 342.9 kB/s -AES-ECB-256 short encrypt: 0.2416 6970 cpb 420.3 kB/s -AES-ECB-256 short decrypt: 0.2562 7391 cpb 396.4 kB/s -AES-ECB-128 long encrypt: 0.1960 5547 cpb 528.2 kB/s -AES-ECB-128 long decrypt: 0.2123 6011 cpb 487.4 kB/s -AES-ECB-192 long encrypt: 0.2413 6829 cpb 429.0 kB/s -AES-ECB-192 long decrypt: 0.2380 6736 cpb 434.9 kB/s -AES-ECB-256 long encrypt: 0.2560 7245 cpb 404.4 kB/s -AES-ECB-256 long decrypt: 0.2638 7468 cpb 392.3 kB/s +$ pypy3 bench.py --all +AES-CBC-128 short encrypt: 0.0452 1305 cpb 2245.7 kB/s +AES-CBC-128 short decrypt: 0.0425 1225 cpb 2392.4 kB/s +AES-CBC-192 short encrypt: 0.0299 861 cpb 3401.1 kB/s +AES-CBC-192 short decrypt: 0.0340 981 cpb 2985.4 kB/s +AES-CBC-256 short encrypt: 0.0321 925 cpb 3168.8 kB/s +AES-CBC-256 short decrypt: 0.0343 989 cpb 2961.1 kB/s +AES-CBC-128 long encrypt: 0.0230 651 cpb 4501.6 kB/s +AES-CBC-128 long decrypt: 0.0238 675 cpb 4341.5 kB/s +AES-CBC-192 long encrypt: 0.0282 797 cpb 3674.2 kB/s +AES-CBC-192 long decrypt: 0.0282 799 cpb 3665.8 kB/s +AES-CBC-256 long encrypt: 0.0324 916 cpb 3196.8 kB/s +AES-CBC-256 long decrypt: 0.0329 932 cpb 3144.0 kB/s +AES-ECB-128 short encrypt: 0.0371 1070 cpb 2738.0 kB/s +AES-ECB-128 short decrypt: 0.0323 932 cpb 3143.7 kB/s +AES-ECB-192 short encrypt: 0.0278 802 cpb 3652.7 kB/s +AES-ECB-192 short decrypt: 0.0272 783 cpb 3740.5 kB/s +AES-ECB-256 short encrypt: 0.0321 925 cpb 3167.8 kB/s +AES-ECB-256 short decrypt: 0.0318 917 cpb 3195.9 kB/s +AES-ECB-128 long encrypt: 0.0227 643 cpb 4558.5 kB/s +AES-ECB-128 long decrypt: 0.0237 672 cpb 4362.8 kB/s +AES-ECB-192 long encrypt: 0.0270 764 cpb 3835.0 kB/s +AES-ECB-192 long decrypt: 0.0284 805 cpb 3638.4 kB/s +AES-ECB-256 long encrypt: 0.0317 898 cpb 3262.3 kB/s +AES-ECB-256 long decrypt: 0.0329 933 cpb 3141.3 kB/s $ openssl speed -evp aes-128-cbc aes-192-cbc aes-256-cbc -elapsed You have chosen to measure elapsed time instead of user CPU time. -To get the most accurate results, try to run this -program when this computer is idle. ......(pass some info) type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes -aes-192 cbc 67434.37k 69537.86k 69332.72k 69231.16k 70681.26k -aes-256 cbc 59265.99k 60532.54k 56754.30k 54190.84k 60041.34k -aes-128-cbc 65590.32k 68580.34k 62408.63k 77896.57k 80406.19k +aes-192 cbc 258761.09k 271434.35k 274407.33k 273140.34k 271047.29k +aes-256 cbc 232336.64k 237868.93k 234162.85k 238381.57k 239210.50k +aes-128-cbc 303575.80k 316688.83k 320358.54k 318650.80k 320910.45k ``` Why pyAES diff --git a/bench.py b/bench.py index 095f78d..cd16fda 100644 --- a/bench.py +++ b/bench.py @@ -25,9 +25,9 @@ cleartext = "This is a test. What could possibly go wrong? " * 50 + '\0' * 4 keys = ( - ('128', '3afca8488ce0d5136aba87953fbd986e'.decode('hex')), - ('192', '35f8b4f0edcee9999d6cc995409e1d506fa8e269c2b795e6'.decode('hex')), - ('256', 'e6cb51df8394cd2c78055c92a55fdb44202034fdabd765feeb12b7ad61554972'.decode('hex')), + ('128', bytes.fromhex('3afca8488ce0d5136aba87953fbd986e')), + ('192', bytes.fromhex('35f8b4f0edcee9999d6cc995409e1d506fa8e269c2b795e6')), + ('256', bytes.fromhex('e6cb51df8394cd2c78055c92a55fdb44202034fdabd765feeb12b7ad61554972')), ) texts = ( # 500 x 52 x 4 bytes = 104000 @@ -41,7 +41,7 @@ ) funcs = ('encrypt', 'decrypt') -iv = 'b9de523d1e1588bf46f9084cb796684f'.decode('hex') +iv = bytes.fromhex('b9de523d1e1588bf46f9084cb796684f') def benchmark(key, mode, runs, func, text): @@ -49,7 +49,7 @@ def benchmark(key, mode, runs, func, text): aes = pyaes.new(key, mode, IV=iv) f = getattr(aes, func) - for i in xrange(runs): + for i in range(runs): f(text) @@ -70,13 +70,13 @@ def alltests(): for keydesc, key in keys: for func in funcs: result = run(key, mode, runs, func, text) - print 'AES-%s-%s %5s %s: %s' % ( + print ('AES-%s-%s %5s %s: %s' % ( modedesc, keydesc, textdesc, func, result - ) + )) def quicktest(): @@ -89,13 +89,13 @@ def quicktest(): #runs *= 2 result = run(key, mode, runs, func, text) - print 'AES-%s-%s %5s %s: %s' % (modedesc, keydesc, textdesc, func, result) + print ('AES-%s-%s %5s %s: %s' % (modedesc, keydesc, textdesc, func, result)) # AES-CBC-256 long encrypt keydesc, key = keys[2] result = run(key, mode, runs, func, text) - print 'AES-%s-%s %5s %s: %s' % (modedesc, keydesc, textdesc, func, result) + print ('AES-%s-%s %5s %s: %s' % (modedesc, keydesc, textdesc, func, result)) if __name__ == '__main__': diff --git a/pyaes.py b/pyaes.py index 5d20c48..e2e331e 100644 --- a/pyaes.py +++ b/pyaes.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 """Simple AES cipher implementation in pure Python following PEP-272 API @@ -85,7 +84,11 @@ def __init__(self, key): def setkey(self, key): """Sets the key and performs key expansion.""" - self.key = key + if type(key) is str: + self.key = key.encode() + else: + self.key = key + self.key_size = len(key) if self.key_size == 16: @@ -123,22 +126,22 @@ def expand_key(self): # 4-byte temporary variable for key expansion word = exkey[-4:] # Each expansion cycle uses 'i' once for Rcon table lookup - for i in xrange(1, 11): + for i in range(1, 11): #### key schedule core: # left-rotate by 1 byte word = word[1:4] + word[0:1] # apply S-box to all bytes - for j in xrange(4): + for j in range(4): word[j] = aes_sbox[word[j]] # apply the Rcon table to the leftmost byte word[0] ^= aes_Rcon[i] #### end key schedule core - for z in xrange(4): - for j in xrange(4): + for z in range(4): + for j in range(4): # mix in bytes from the last subkey word[j] ^= exkey[-self.key_size + j] exkey.extend(word) @@ -149,15 +152,15 @@ def expand_key(self): # Special substitution step for 256-bit key if self.key_size == 32: - for j in xrange(4): + for j in range(4): # mix in bytes from the last subkey XORed with S-box of # current word bytes word[j] = aes_sbox[word[j]] ^ exkey[-self.key_size + j] exkey.extend(word) # Twice for 192-bit key, thrice for 256-bit key - for z in xrange(extra_cnt): - for j in xrange(4): + for z in range(extra_cnt): + for j in range(4): # mix in bytes from the last subkey word[j] ^= exkey[-self.key_size + j] exkey.extend(word) @@ -170,7 +173,7 @@ def add_round_key(self, block, round): offset = round * 16 exkey = self.exkey - for i in xrange(16): + for i in range(16): block[i] ^= exkey[offset + i] #print 'AddRoundKey:', block @@ -183,7 +186,7 @@ def sub_bytes(self, block, sbox): is passed in. """ - for i in xrange(16): + for i in range(16): block[i] = sbox[block[i]] #print 'SubBytes :', block @@ -228,7 +231,7 @@ def mix_columns(self, block): # Since we're dealing with a transposed matrix, columns are already # sequential - for col in xrange(0, 16, 4): + for col in range(0, 16, 4): v0, v1, v2, v3 = block[col:col + 4] block[col] = mul_by_2[v0] ^ v3 ^ v2 ^ mul_by_3[v1] @@ -250,7 +253,7 @@ def mix_columns_inv(self, block): # Since we're dealing with a transposed matrix, columns are already # sequential - for col in xrange(0, 16, 4): + for col in range(0, 16, 4): v0, v1, v2, v3 = block[col:col + 4] block[col] = mul_14[v0] ^ mul_9[v3] ^ mul_13[v2] ^ mul_11[v1] @@ -267,7 +270,7 @@ def encrypt_block(self, block): # mutable array, not returned self.add_round_key(block, 0) - for round in xrange(1, self.rounds): + for round in range(1, self.rounds): self.sub_bytes(block, aes_sbox) self.shift_rows(block) self.mix_columns(block) @@ -286,7 +289,7 @@ def decrypt_block(self, block): self.add_round_key(block, self.rounds) # count rounds down from (self.rounds) ... 1 - for round in xrange(self.rounds - 1, 0, -1): + for round in range(self.rounds - 1, 0, -1): self.shift_rows_inv(block) self.sub_bytes(block, aes_inv_sbox) self.add_round_key(block, round) @@ -316,16 +319,19 @@ def ecb(self, data, block_func): if len(data) % self.block_size != 0: raise ValueError("Input length must be multiple of 16") + + if type(data) is str: + data = data.encode() block_size = self.block_size data = array('B', data) - for offset in xrange(0, len(data), block_size): + for offset in range(0, len(data), block_size): block = data[offset:offset + block_size] block_func(block) data[offset:offset + block_size] = block - return data.tostring() + return data.tobytes() def encrypt(self, data): """Encrypt data in ECB mode""" @@ -352,6 +358,9 @@ class CBCMode(object): # Cipher-block_chaining_.28CBC.29 def __init__(self, cipher, IV): + if type(IV) is str: + IV = IV.encode() + self.cipher = cipher self.block_size = cipher.block_size self.IV = array('B', IV) @@ -362,15 +371,18 @@ def encrypt(self, data): block_size = self.block_size if len(data) % block_size != 0: raise ValueError("Plaintext length must be multiple of 16") + + if type(data) is str: + data = data.encode() data = array('B', data) IV = self.IV - for offset in xrange(0, len(data), block_size): + for offset in range(0, len(data), block_size): block = data[offset:offset + block_size] # Perform CBC chaining - for i in xrange(block_size): + for i in range(block_size): block[i] ^= IV[i] self.cipher.encrypt_block(block) @@ -378,7 +390,7 @@ def encrypt(self, data): IV = block self.IV = IV - return data.tostring() + return data.tobytes() def decrypt(self, data): """Decrypt data in CBC mode""" @@ -387,18 +399,21 @@ def decrypt(self, data): if len(data) % block_size != 0: raise ValueError("Ciphertext length must be multiple of 16") + if type(data) is str: + data = data.encode() + data = array('B', data) IV = self.IV - for offset in xrange(0, len(data), block_size): + for offset in range(0, len(data), block_size): ctext = data[offset:offset + block_size] block = ctext[:] self.cipher.decrypt_block(block) # Perform CBC chaining - #for i in xrange(block_size): + #for i in range(block_size): # data[offset + i] ^= IV[i] - for i in xrange(block_size): + for i in range(block_size): block[i] ^= IV[i] data[offset:offset + block_size] = block @@ -406,7 +421,7 @@ def decrypt(self, data): #data[offset : offset+block_size] = block self.IV = IV - return data.tostring() + return data.tobytes() def galois_multiply(a, b): @@ -441,22 +456,24 @@ def galois_multiply(a, b): aes_sbox = array( 'B', - '637c777bf26b6fc53001672bfed7ab76' - 'ca82c97dfa5947f0add4a2af9ca472c0' - 'b7fd9326363ff7cc34a5e5f171d83115' - '04c723c31896059a071280e2eb27b275' - '09832c1a1b6e5aa0523bd6b329e32f84' - '53d100ed20fcb15b6acbbe394a4c58cf' - 'd0efaafb434d338545f9027f503c9fa8' - '51a3408f929d38f5bcb6da2110fff3d2' - 'cd0c13ec5f974417c4a77e3d645d1973' - '60814fdc222a908846eeb814de5e0bdb' - 'e0323a0a4906245cc2d3ac629195e479' - 'e7c8376d8dd54ea96c56f4ea657aae08' - 'ba78252e1ca6b4c6e8dd741f4bbd8b8a' - '703eb5664803f60e613557b986c11d9e' - 'e1f8981169d98e949b1e87e9ce5528df' - '8ca1890dbfe6426841992d0fb054bb16'.decode('hex') + bytes.fromhex( + '637c777bf26b6fc53001672bfed7ab76' + 'ca82c97dfa5947f0add4a2af9ca472c0' + 'b7fd9326363ff7cc34a5e5f171d83115' + '04c723c31896059a071280e2eb27b275' + '09832c1a1b6e5aa0523bd6b329e32f84' + '53d100ed20fcb15b6acbbe394a4c58cf' + 'd0efaafb434d338545f9027f503c9fa8' + '51a3408f929d38f5bcb6da2110fff3d2' + 'cd0c13ec5f974417c4a77e3d645d1973' + '60814fdc222a908846eeb814de5e0bdb' + 'e0323a0a4906245cc2d3ac629195e479' + 'e7c8376d8dd54ea96c56f4ea657aae08' + 'ba78252e1ca6b4c6e8dd741f4bbd8b8a' + '703eb5664803f60e613557b986c11d9e' + 'e1f8981169d98e949b1e87e9ce5528df' + '8ca1890dbfe6426841992d0fb054bb16' + ) ) # This is the inverse of the above. In other words: @@ -464,22 +481,24 @@ def galois_multiply(a, b): aes_inv_sbox = array( 'B', - '52096ad53036a538bf40a39e81f3d7fb' - '7ce339829b2fff87348e4344c4dee9cb' - '547b9432a6c2233dee4c950b42fac34e' - '082ea16628d924b2765ba2496d8bd125' - '72f8f66486689816d4a45ccc5d65b692' - '6c704850fdedb9da5e154657a78d9d84' - '90d8ab008cbcd30af7e45805b8b34506' - 'd02c1e8fca3f0f02c1afbd0301138a6b' - '3a9111414f67dcea97f2cfcef0b4e673' - '96ac7422e7ad3585e2f937e81c75df6e' - '47f11a711d29c5896fb7620eaa18be1b' - 'fc563e4bc6d279209adbc0fe78cd5af4' - '1fdda8338807c731b11210592780ec5f' - '60517fa919b54a0d2de57a9f93c99cef' - 'a0e03b4dae2af5b0c8ebbb3c83539961' - '172b047eba77d626e169146355210c7d'.decode('hex') + bytes.fromhex( + '52096ad53036a538bf40a39e81f3d7fb' + '7ce339829b2fff87348e4344c4dee9cb' + '547b9432a6c2233dee4c950b42fac34e' + '082ea16628d924b2765ba2496d8bd125' + '72f8f66486689816d4a45ccc5d65b692' + '6c704850fdedb9da5e154657a78d9d84' + '90d8ab008cbcd30af7e45805b8b34506' + 'd02c1e8fca3f0f02c1afbd0301138a6b' + '3a9111414f67dcea97f2cfcef0b4e673' + '96ac7422e7ad3585e2f937e81c75df6e' + '47f11a711d29c5896fb7620eaa18be1b' + 'fc563e4bc6d279209adbc0fe78cd5af4' + '1fdda8338807c731b11210592780ec5f' + '60517fa919b54a0d2de57a9f93c99cef' + 'a0e03b4dae2af5b0c8ebbb3c83539961' + '172b047eba77d626e169146355210c7d' + ) ) # The Rcon table is used in AES's key schedule (key expansion) @@ -489,20 +508,22 @@ def galois_multiply(a, b): aes_Rcon = array( 'B', - '8d01020408102040801b366cd8ab4d9a' - '2f5ebc63c697356ad4b37dfaefc59139' - '72e4d3bd61c29f254a943366cc831d3a' - '74e8cb8d01020408102040801b366cd8' - 'ab4d9a2f5ebc63c697356ad4b37dfaef' - 'c5913972e4d3bd61c29f254a943366cc' - '831d3a74e8cb8d01020408102040801b' - '366cd8ab4d9a2f5ebc63c697356ad4b3' - '7dfaefc5913972e4d3bd61c29f254a94' - '3366cc831d3a74e8cb8d010204081020' - '40801b366cd8ab4d9a2f5ebc63c69735' - '6ad4b37dfaefc5913972e4d3bd61c29f' - '254a943366cc831d3a74e8cb8d010204' - '08102040801b366cd8ab4d9a2f5ebc63' - 'c697356ad4b37dfaefc5913972e4d3bd' - '61c29f254a943366cc831d3a74e8cb'.decode('hex') + bytes.fromhex( + '8d01020408102040801b366cd8ab4d9a' + '2f5ebc63c697356ad4b37dfaefc59139' + '72e4d3bd61c29f254a943366cc831d3a' + '74e8cb8d01020408102040801b366cd8' + 'ab4d9a2f5ebc63c697356ad4b37dfaef' + 'c5913972e4d3bd61c29f254a943366cc' + '831d3a74e8cb8d01020408102040801b' + '366cd8ab4d9a2f5ebc63c697356ad4b3' + '7dfaefc5913972e4d3bd61c29f254a94' + '3366cc831d3a74e8cb8d010204081020' + '40801b366cd8ab4d9a2f5ebc63c69735' + '6ad4b37dfaefc5913972e4d3bd61c29f' + '254a943366cc831d3a74e8cb8d010204' + '08102040801b366cd8ab4d9a2f5ebc63' + 'c697356ad4b37dfaefc5913972e4d3bd' + '61c29f254a943366cc831d3a74e8cb' + ) )