From 463f640e25e60341ff52dc1e1f27678a7c319fbb Mon Sep 17 00:00:00 2001 From: Vinting <54710337+vintingb@users.noreply.github.com> Date: Mon, 23 Nov 2020 23:04:37 +0800 Subject: [PATCH 1/2] Add python3 support --- pyaes.py | 391 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 200 insertions(+), 191 deletions(-) diff --git a/pyaes.py b/pyaes.py index 5d20c48..ed258ae 100644 --- a/pyaes.py +++ b/pyaes.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """Simple AES cipher implementation in pure Python following PEP-272 API @@ -28,7 +27,7 @@ """ -#### +# # Copyright (c) 2010 Marti Raudsepp # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -48,7 +47,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -#### +# from array import array @@ -57,7 +56,7 @@ # http://www.python.org/dev/peps/pep-0272/ MODE_ECB = 1 MODE_CBC = 2 -#MODE_CTR = 6 +# MODE_CTR = 6 block_size = 16 # variable length key: 16, 24 or 32 bytes @@ -75,13 +74,104 @@ def new(key, mode, IV=None): raise NotImplementedError -#### AES cipher implementation +# AES cipher implementation +def mix_columns_inv(block): + """ + Similar to mix_columns above, but performed in inverse for decryption. + """ + # Cache global multiplication tables (see below) + mul_9 = gf_mul_by_9 + mul_11 = gf_mul_by_11 + mul_13 = gf_mul_by_13 + mul_14 = gf_mul_by_14 + + # Since we're dealing with a transposed matrix, columns are already + # sequential + 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] + block[col + 1] = mul_14[v1] ^ mul_9[v0] ^ mul_13[v3] ^ mul_11[v2] + block[col + 2] = mul_14[v2] ^ mul_9[v1] ^ mul_13[v0] ^ mul_11[v3] + block[col + 3] = mul_14[v3] ^ mul_9[v2] ^ mul_13[v1] ^ mul_11[v0] + + # print 'MixColumns :', block + + +def sub_bytes(block, sbox): + """ + SubBytes step, apply S-box to all bytes + + Depending on whether encrypting or decrypting, a different sbox array + is passed in. + """ + + for i in range(16): + block[i] = sbox[block[i]] + + # print 'SubBytes :', block + + +def shift_rows(b): + """ + ShiftRows step in AES. + + Shifts 2nd row to left by 1, 3rd row by 2, 4th row by 3 + + Since we're performing this on a transposed matrix, cells are numbered + from top to bottom first:: + + 0 4 8 12 -> 0 4 8 12 -- 1st row doesn't change + 1 5 9 13 -> 5 9 13 1 -- row shifted to left by 1 (wraps around) + 2 6 10 14 -> 10 14 2 6 -- shifted by 2 + 3 7 11 15 -> 15 3 7 11 -- shifted by 3 + """ + + b[1], b[5], b[9], b[13] = b[5], b[9], b[13], b[1] + b[2], b[6], b[10], b[14] = b[10], b[14], b[2], b[6] + b[3], b[7], b[11], b[15] = b[15], b[3], b[7], b[11] + + # print 'ShiftRows :', b + + +def shift_rows_inv(b): + """ + Similar to shift_rows above, but performed in inverse for decryption. + """ + b[5], b[9], b[13], b[1] = b[1], b[5], b[9], b[13] + b[10], b[14], b[2], b[6] = b[2], b[6], b[10], b[14] + b[15], b[3], b[7], b[11] = b[3], b[7], b[11], b[15] + + # print 'ShiftRows :', b + + +def mix_columns(block): + """MixColumns step. Mixes the values in each column""" + + # Cache global multiplication tables (see below) + mul_by_2 = gf_mul_by_2 + mul_by_3 = gf_mul_by_3 + + # Since we're dealing with a transposed matrix, columns are already + # sequential + 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] + block[col + 1] = mul_by_2[v1] ^ v0 ^ v3 ^ mul_by_3[v2] + block[col + 2] = mul_by_2[v2] ^ v1 ^ v0 ^ mul_by_3[v3] + block[col + 3] = mul_by_2[v3] ^ v2 ^ v1 ^ mul_by_3[v0] + + # print 'MixColumns :', block + + class AES(object): block_size = 16 def __init__(self, key): self.setkey(key) + # noinspection PyAttributeOutsideInit def setkey(self, key): """Sets the key and performs key expansion.""" @@ -99,6 +189,7 @@ def setkey(self, key): self.expand_key() + # noinspection PyAttributeOutsideInit def expand_key(self): """Performs AES key expansion on self.key and stores in self.exkey""" @@ -110,7 +201,8 @@ def expand_key(self): # http://en.wikipedia.org/wiki/Rijndael_key_schedule # The expanded key starts with the actual key itself - exkey = array('B', self.key) + exkey = array('B') + exkey.frombytes(self.key.encode()) # extra key expansion steps if self.key_size == 16: @@ -123,22 +215,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: + # 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 + # 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,116 +241,31 @@ 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) self.exkey = exkey - def add_round_key(self, block, round): + def add_round_key(self, block, r): """AddRoundKey step. This is where the key is mixed into plaintext""" - offset = round * 16 + offset = r * 16 exkey = self.exkey - for i in xrange(16): + for i in range(16): block[i] ^= exkey[offset + i] - #print 'AddRoundKey:', block - - def sub_bytes(self, block, sbox): - """ - SubBytes step, apply S-box to all bytes - - Depending on whether encrypting or decrypting, a different sbox array - is passed in. - """ - - for i in xrange(16): - block[i] = sbox[block[i]] - - #print 'SubBytes :', block - - def shift_rows(self, b): - """ - ShiftRows step in AES. - - Shifts 2nd row to left by 1, 3rd row by 2, 4th row by 3 - - Since we're performing this on a transposed matrix, cells are numbered - from top to bottom first:: - - 0 4 8 12 -> 0 4 8 12 -- 1st row doesn't change - 1 5 9 13 -> 5 9 13 1 -- row shifted to left by 1 (wraps around) - 2 6 10 14 -> 10 14 2 6 -- shifted by 2 - 3 7 11 15 -> 15 3 7 11 -- shifted by 3 - """ - - b[1], b[5], b[9], b[13] = b[5], b[9], b[13], b[1] - b[2], b[6], b[10], b[14] = b[10], b[14], b[2], b[6] - b[3], b[7], b[11], b[15] = b[15], b[3], b[7], b[11] - - #print 'ShiftRows :', b - - def shift_rows_inv(self, b): - """ - Similar to shift_rows above, but performed in inverse for decryption. - """ - b[5], b[9], b[13], b[1] = b[1], b[5], b[9], b[13] - b[10], b[14], b[2], b[6] = b[2], b[6], b[10], b[14] - b[15], b[3], b[7], b[11] = b[3], b[7], b[11], b[15] - - #print 'ShiftRows :', b - - def mix_columns(self, block): - """MixColumns step. Mixes the values in each column""" - - # Cache global multiplication tables (see below) - mul_by_2 = gf_mul_by_2 - mul_by_3 = gf_mul_by_3 - - # Since we're dealing with a transposed matrix, columns are already - # sequential - for col in xrange(0, 16, 4): - v0, v1, v2, v3 = block[col:col + 4] - - block[col] = mul_by_2[v0] ^ v3 ^ v2 ^ mul_by_3[v1] - block[col + 1] = mul_by_2[v1] ^ v0 ^ v3 ^ mul_by_3[v2] - block[col + 2] = mul_by_2[v2] ^ v1 ^ v0 ^ mul_by_3[v3] - block[col + 3] = mul_by_2[v3] ^ v2 ^ v1 ^ mul_by_3[v0] - - #print 'MixColumns :', block - - def mix_columns_inv(self, block): - """ - Similar to mix_columns above, but performed in inverse for decryption. - """ - # Cache global multiplication tables (see below) - mul_9 = gf_mul_by_9 - mul_11 = gf_mul_by_11 - mul_13 = gf_mul_by_13 - mul_14 = gf_mul_by_14 - - # Since we're dealing with a transposed matrix, columns are already - # sequential - for col in xrange(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] - block[col + 1] = mul_14[v1] ^ mul_9[v0] ^ mul_13[v3] ^ mul_11[v2] - block[col + 2] = mul_14[v2] ^ mul_9[v1] ^ mul_13[v0] ^ mul_11[v3] - block[col + 3] = mul_14[v3] ^ mul_9[v2] ^ mul_13[v1] ^ mul_11[v0] - - #print 'MixColumns :', block + # print 'AddRoundKey:', block def encrypt_block(self, block): """Encrypts a single block. This is the main AES function""" @@ -267,14 +274,14 @@ def encrypt_block(self, block): # mutable array, not returned self.add_round_key(block, 0) - for round in xrange(1, self.rounds): - self.sub_bytes(block, aes_sbox) - self.shift_rows(block) - self.mix_columns(block) - self.add_round_key(block, round) + for r in range(1, self.rounds): + sub_bytes(block, aes_sbox) + shift_rows(block) + mix_columns(block) + self.add_round_key(block, r) - self.sub_bytes(block, aes_sbox) - self.shift_rows(block) + sub_bytes(block, aes_sbox) + shift_rows(block) # no mix_columns step in the last round self.add_round_key(block, self.rounds) @@ -286,19 +293,19 @@ 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): - self.shift_rows_inv(block) - self.sub_bytes(block, aes_inv_sbox) - self.add_round_key(block, round) - self.mix_columns_inv(block) - - self.shift_rows_inv(block) - self.sub_bytes(block, aes_inv_sbox) + for r in range(self.rounds - 1, 0, -1): + shift_rows_inv(block) + sub_bytes(block, aes_inv_sbox) + self.add_round_key(block, r) + mix_columns_inv(block) + + shift_rows_inv(block) + sub_bytes(block, aes_inv_sbox) self.add_round_key(block, 0) # no mix_columns step in the last round -#### ECB mode implementation +# ECB mode implementation class ECBMode(object): """Electronic CodeBook (ECB) mode encryption. @@ -317,13 +324,12 @@ def ecb(self, data, block_func): if len(data) % self.block_size != 0: raise ValueError("Input length must be multiple of 16") - block_size = self.block_size data = array('B', data) - for offset in xrange(0, len(data), block_size): - block = data[offset:offset + block_size] + for offset in range(0, len(data), self.block_size): + block = data[offset:offset + self.block_size] block_func(block) - data[offset:offset + block_size] = block + data[offset:offset + self.block_size] = block return data.tostring() @@ -338,7 +344,7 @@ def decrypt(self, data): return self.ecb(data, self.cipher.decrypt_block) -#### CBC mode +# CBC mode class CBCMode(object): """ Cipher Block Chaining(CBC) mode encryption. This mode avoids content leaks. @@ -354,56 +360,56 @@ class CBCMode(object): def __init__(self, cipher, IV): self.cipher = cipher self.block_size = cipher.block_size - self.IV = array('B', IV) + self.IV = array('B') + self.IV.frombytes(IV.encode()) - def encrypt(self, data): + def encrypt(self, datas): """Encrypt data in CBC mode""" - block_size = self.block_size - if len(data) % block_size != 0: + if len(datas) % self.block_size != 0: raise ValueError("Plaintext length must be multiple of 16") - data = array('B', data) + data = array('B') + data.frombytes(datas.encode()) IV = self.IV - for offset in xrange(0, len(data), block_size): - block = data[offset:offset + block_size] + for offset in range(0, len(data), self.block_size): + block = data[offset:offset + self.block_size] # Perform CBC chaining - for i in xrange(block_size): + for i in range(self.block_size): block[i] ^= IV[i] self.cipher.encrypt_block(block) - data[offset:offset + block_size] = block + data[offset:offset + self.block_size] = block IV = block self.IV = IV - return data.tostring() + return data.tobytes() def decrypt(self, data): """Decrypt data in CBC mode""" - block_size = self.block_size if len(data) % block_size != 0: raise ValueError("Ciphertext length must be multiple of 16") data = array('B', data) IV = self.IV - for offset in xrange(0, len(data), block_size): - ctext = data[offset:offset + block_size] + for offset in range(0, len(data), self.block_size): + ctext = data[offset:offset + self.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(self.block_size): block[i] ^= IV[i] - data[offset:offset + block_size] = block + data[offset:offset + self.block_size] = block IV = ctext - #data[offset : offset+block_size] = block + # data[offset : offset+block_size] = block self.IV = IV return data.tostring() @@ -422,6 +428,7 @@ def galois_multiply(a, b): return p & 0xff + # Precompute the multiplication tables for encryption gf_mul_by_2 = array('B', [galois_multiply(x, 2) for x in range(256)]) gf_mul_by_3 = array('B', [galois_multiply(x, 3) for x in range(256)]) @@ -431,7 +438,7 @@ def galois_multiply(a, b): gf_mul_by_13 = array('B', [galois_multiply(x, 13) for x in range(256)]) gf_mul_by_14 = array('B', [galois_multiply(x, 14) for x in range(256)]) -#### +# # The S-box is a 256-element array, that maps a single byte value to another # byte value. Since it's designed to be reversible, each value occurs only once @@ -441,22 +448,23 @@ 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 +472,23 @@ 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 +498,20 @@ 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') ) From b280dd8d9fd59b07bde45f45326ce7e532d5c867 Mon Sep 17 00:00:00 2001 From: Vinting <54710337+vintingb@users.noreply.github.com> Date: Sun, 29 Nov 2020 13:23:40 +0800 Subject: [PATCH 2/2] Update pyaes.py add pad --- pyaes.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyaes.py b/pyaes.py index ed258ae..5c87e5e 100644 --- a/pyaes.py +++ b/pyaes.py @@ -165,6 +165,13 @@ def mix_columns(block): # print 'MixColumns :', block +def pad(text): + count = len(text.encode('utf-8')) + add = 16 - (count % 16) + entext = text + (chr(add) * add) + return entext + + class AES(object): block_size = 16 @@ -367,7 +374,8 @@ def encrypt(self, datas): """Encrypt data in CBC mode""" if len(datas) % self.block_size != 0: - raise ValueError("Plaintext length must be multiple of 16") + datas = pad(datas) + # raise ValueError("Plaintext length must be multiple of 16") data = array('B') data.frombytes(datas.encode())