├── requirements.txt ├── .gitignore ├── resources ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── banner.png ├── aes128_de.gif └── aes128_en.gif ├── LICENCE ├── index.py ├── utils ├── converter.py └── constant.py ├── aes128.py ├── aes192.py ├── aes256.py ├── aes512.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.23.4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv-aes 2 | __pycache__ 3 | *.pyc -------------------------------------------------------------------------------- /resources/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/1.png -------------------------------------------------------------------------------- /resources/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/2.png -------------------------------------------------------------------------------- /resources/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/3.png -------------------------------------------------------------------------------- /resources/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/4.png -------------------------------------------------------------------------------- /resources/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/banner.png -------------------------------------------------------------------------------- /resources/aes128_de.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/aes128_de.gif -------------------------------------------------------------------------------- /resources/aes128_en.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandeep-shaw10/py-aes/HEAD/resources/aes128_en.gif -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Sandeep Shaw 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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. 8 | 9 | -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | from aes128 import AES as AES_128 2 | from aes192 import AES as AES_192 3 | from aes256 import AES as AES_256 4 | from aes512 import AES as AES_512 5 | 6 | 7 | msg = 'Checking AES Encryption & Decryption on python' 8 | encode = '__all__' 9 | 10 | 11 | print('AES 128') 12 | key = 'Thats my Kung Fu' # 16 character / 128 bits 13 | encrypt_128 = AES_128() 14 | x = encrypt_128.encrypt(key, msg, encode) 15 | y = encrypt_128.decrypt(key, x['hex']) 16 | print(x) 17 | print(y, end='\n\n') 18 | 19 | 20 | print('AES 192') 21 | key = 'Thats my Kung Fu Panda !' # 24 character / 192 bits 22 | encrypt_192 = AES_192() 23 | x = encrypt_192.encrypt(key, msg, encode) 24 | y = encrypt_192.decrypt(key, x['hex']) 25 | print(x) 26 | print(y, end='\n\n') 27 | 28 | 29 | print('AES 256') 30 | key = 'Thats my Kung Fu Panda ! Style12' # 32 character / 256 bits 31 | encrypt_256 = AES_256() 32 | x = encrypt_256.encrypt(key, msg, encode) 33 | y = encrypt_256.decrypt(key, x['hex']) 34 | print(x) 35 | print(y, end='\n\n') 36 | 37 | 38 | print('AES 512') 39 | key = 'Thats my Kung Fu Panda ! Style12Thats my Kung Fu Panda ! Style12' # 64 character / 512 bits 40 | encrypt_512 = AES_512() 41 | x = encrypt_512.encrypt(key, msg, encode) 42 | y = encrypt_512.decrypt(key, x['hex']) 43 | print(x) 44 | print(y, end='\n\n') -------------------------------------------------------------------------------- /utils/converter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from utils.constant import s_box, roundConstant, encryptMDS, mc2, mc3 3 | from utils.constant import invs_box, decryptMDS, mc9, mc11, mc13, mc14 4 | # aes512 5 | from utils.constant import roundConstant_8, encryptMDS_8, decryptMDS_8 6 | 7 | 8 | # Key to Matrix 9 | def keyToHexArray(key, row=4, col=4): 10 | arr = [] 11 | for i in key: 12 | arr.append(ord(i)) 13 | arr = np.array(arr) 14 | arr = arr.reshape(row,col) # 4*4 matrix 15 | return arr 16 | 17 | 18 | # Apply left shift / RotWord 19 | def arrayShift(arr, shift=-1): 20 | return np.roll(arr, shift) 21 | 22 | 23 | # S-box on 1D Array 24 | def arraySbox(arr): 25 | for i in range(0, len(arr)): 26 | lsb = arr[i] & 0b00001111 27 | msb = (arr[i] & 0b11110000) >> 4 28 | arr[i] = s_box[msb, lsb] 29 | return arr 30 | 31 | 32 | # Inverse S-box on 1D Array 33 | def arrayInvSbox(arr): 34 | for i in range(0, len(arr)): 35 | lsb = arr[i] & 0b00001111 36 | msb = (arr[i] & 0b11110000) >> 4 37 | arr[i] = invs_box[msb, lsb] 38 | return arr 39 | 40 | 41 | # XOR Operation on [arr1, arr2] or [arr1,arr2,rcon(i)] 42 | def xorArray(arr1, arr2, order=4, rcon=-1): 43 | xor_arr = [] 44 | if(arr1.shape == arr2.shape and (rcon >= -1 and rcon <= 9)): 45 | if(rcon == -1): 46 | for i in range(len(arr1)): 47 | val = arr1[i] ^ arr2[i] 48 | xor_arr.append(val) 49 | else: 50 | rcon_arr = roundConstant[rcon] 51 | if(order == 8): 52 | rcon_arr = roundConstant_8[rcon] 53 | for i in range(len(arr1)): 54 | val = arr1[i] ^ arr2[i] ^ rcon_arr[i] 55 | xor_arr.append(val) 56 | xor_arr = np.array(xor_arr) 57 | return xor_arr 58 | else: 59 | print('Array must be same dimension numpy OR Rcon: roundconstant must be 0-10') 60 | print(arr1, arr2, rcon) 61 | return False 62 | 63 | 64 | # Xor 2 2D array 65 | def addRoundKey(arr1, arr2): 66 | return np.bitwise_xor(arr1, arr2) 67 | 68 | 69 | # Substitution-box on 2D Array 70 | def subBytes(arr, inverse=False): 71 | for i in arr: 72 | if(not inverse): 73 | arraySbox(i) 74 | else: 75 | arrayInvSbox(i) 76 | return arr 77 | 78 | 79 | # Shift Row on 2D Array 80 | def shiftRow(arr, left=True, order=4): 81 | shifted_arr = [] 82 | for i in range(0, order): 83 | if(left): 84 | x = arrayShift(arr[:,i],-1*i) #Left circular shift: Encryption 85 | else: 86 | x = arrayShift(arr[:,i],i) #Right circular shift: Decryption 87 | shifted_arr.append(x) 88 | shifted_arr = np.array(shifted_arr) # Accurate 89 | return np.transpose(shifted_arr) 90 | 91 | 92 | # Mix Column 93 | def mixColumn(arr, order=4): 94 | arr = np.transpose(arr) 95 | mix_arr = np.zeros((order, order), dtype=int) 96 | encryptMDS_arr = encryptMDS 97 | if(order == 8): 98 | encryptMDS_arr = encryptMDS_8 99 | for i in range(0, order): 100 | for j in range(0, order): 101 | for k in range(0, order): 102 | if(encryptMDS_arr[i][k] == 1): 103 | mix_arr[i][j] ^= arr[k][j] 104 | lsb = arr[k][j] & 0b00001111 105 | msb = (arr[k][j] & 0b11110000) >> 4 106 | if(encryptMDS_arr[i][k] == 2): 107 | mix_arr[i][j] ^= mc2[msb,lsb] 108 | if(encryptMDS_arr[i][k] == 3): 109 | mix_arr[i][j] ^= mc3[msb,lsb] 110 | return np.transpose(mix_arr) 111 | 112 | 113 | # Inverse Mix Column 114 | def inverseMixColumn(arr, order=4): 115 | decryptMDS_arr = decryptMDS 116 | if(order == 8): 117 | decryptMDS_arr = decryptMDS_8 118 | arr = np.transpose(arr) 119 | mix_arr = np.zeros((order, order), dtype=int) 120 | for i in range(0, order): 121 | for j in range(0, order): 122 | for k in range(0, order): 123 | if(decryptMDS_arr[i][k] == 1): 124 | mix_arr[i][j] ^= arr[k][j] 125 | lsb = arr[k][j] & 0b00001111 126 | msb = (arr[k][j] & 0b11110000) >> 4 127 | if(decryptMDS_arr[i][k] == 9): 128 | mix_arr[i][j] ^= mc9[msb,lsb] 129 | if(decryptMDS_arr[i][k] == 11): 130 | mix_arr[i][j] ^= mc11[msb,lsb] 131 | if(decryptMDS_arr[i][k] == 13): 132 | mix_arr[i][j] ^= mc13[msb,lsb] 133 | if(decryptMDS_arr[i][k] == 14): 134 | mix_arr[i][j] ^= mc14[msb,lsb] 135 | return np.transpose(mix_arr) 136 | 137 | 138 | 139 | # Decryption: ecrypted hex to matrix 140 | ''' 141 | 4*4: 16 box => 128 bits => 32 in hex (representation) 142 | 8*8: 64 box => 512 bits => 128 in hex (representation) 143 | ''' 144 | def hexToMatrix(data, order=4): 145 | hexbit = order*order*2 146 | if(len(data) == hexbit): 147 | val = [data[i:i+2] for i in range(0, len(data), 2)] 148 | val = [int(x,16) for x in val] 149 | arr = np.array(val) 150 | arr = arr.reshape(order,order) # 4*4 matrix 151 | return arr 152 | else: 153 | print('length of encrypted data should be 32') -------------------------------------------------------------------------------- /aes128.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from base64 import b64encode, b64decode 3 | from utils.converter import keyToHexArray, arrayShift, arraySbox, xorArray, addRoundKey, subBytes, shiftRow, mixColumn 4 | from utils.converter import hexToMatrix, inverseMixColumn 5 | 6 | 7 | class AES: 8 | 9 | def __init__(self): 10 | self.ROUND = 10 11 | self.ORDER = 4 12 | self.ROUNDKEY = [] 13 | 14 | # Key Scheduling 15 | def __keySchedule(self, KEY): 16 | hexKey = keyToHexArray(KEY) 17 | self.ROUNDKEY.append(hexKey) 18 | for i in range(0, self.ROUND): 19 | prev_arr = self.ROUNDKEY[-1] 20 | last_col = prev_arr[self.ORDER-1] 21 | shift_col = arrayShift(last_col) 22 | sbox_col = arraySbox(shift_col) 23 | col_1 = xorArray(prev_arr[0], sbox_col, i) 24 | col_2 = xorArray(col_1, prev_arr[1]) 25 | col_3 = xorArray(col_2, prev_arr[2]) 26 | col_4 = xorArray(col_3, prev_arr[3]) 27 | new_arr = np.array([col_1, col_2, col_3, col_4]) 28 | self.ROUNDKEY.append(new_arr) 29 | 30 | # Encryption Process 31 | def __encryptProcess(self, TEXT): 32 | hexData = keyToHexArray(TEXT) 33 | cipher_arr = addRoundKey(hexData, self.ROUNDKEY[0]) 34 | for i in range(1, self.ROUND+1): 35 | arr = cipher_arr 36 | arr = subBytes(arr) 37 | arr = shiftRow(arr) 38 | if(i != self.ROUND): 39 | arr = mixColumn(arr) 40 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 41 | cipher_arr = arr 42 | return cipher_arr 43 | 44 | # Encryption Add Padding 45 | def __addPadding(self, data): 46 | bytes = 16 47 | bits_arr = [] 48 | while(True): 49 | if(len(data) > bytes): 50 | bits_arr.append(data[:bytes]) 51 | data = data[bytes:] 52 | else: 53 | space = bytes-len(data) 54 | bits_arr.append(data + chr(space)*space) 55 | break 56 | return bits_arr 57 | 58 | # Decryption Process 59 | def __decryptProcess(self, CIPHER_HEX): 60 | hexData = hexToMatrix(CIPHER_HEX) 61 | plain_arr = addRoundKey(hexData, self.ROUNDKEY[-1]) 62 | for i in range(self.ROUND-1, -1, -1): 63 | arr = plain_arr 64 | arr = shiftRow(arr, left=False) 65 | arr = subBytes(arr, inverse=True) 66 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 67 | if(i != 0): 68 | arr = inverseMixColumn(arr) 69 | plain_arr = arr 70 | return plain_arr 71 | 72 | # Decryption Delete Padding 73 | def __delPadding(self, data): 74 | verify = data[-1] 75 | if(verify >= 1 and verify <= 15): 76 | pad = data[16-verify:] 77 | sameCount = pad.count(verify) 78 | if(sameCount == verify): 79 | return data[:16-verify] 80 | return data 81 | return data 82 | 83 | #Encryption 84 | def encrypt(self, KEY, TEXT, type='hex'): 85 | text_arr = self.__addPadding(TEXT) 86 | self.__keySchedule(KEY) 87 | hex_ecrypt='' 88 | for i in text_arr: 89 | cipher_matrix = self.__encryptProcess(i) 90 | cipher_text = list(np.array(cipher_matrix).reshape(-1,)) 91 | for j in cipher_text: 92 | hex_ecrypt+=f'{j:02x}' 93 | self.ROUNDKEY = [] 94 | #conversion 95 | if(type == 'b64'): 96 | return b64encode(bytes.fromhex(hex_ecrypt)).decode() 97 | if(type == '0b'): 98 | return f'{int(hex_ecrypt, 16):0>b}' 99 | if(type == '__all__'): 100 | return { 101 | 'hex': hex_ecrypt, 102 | 'b64': b64encode(bytes.fromhex(hex_ecrypt)).decode(), 103 | '0b': bin(int(hex_ecrypt, 16))[2:].zfill(len(hex_ecrypt) * 4) 104 | } 105 | return hex_ecrypt 106 | 107 | # Decryption 108 | def decrypt(self, KEY, CIPHER, type='hex'): 109 | if type in ['hex', '0b', 'b64']: 110 | self.__keySchedule(KEY) 111 | data = '' 112 | 113 | if(type == 'b64'): 114 | CIPHER = b64decode(CIPHER).hex() 115 | 116 | if(type == '0b'): 117 | CIPHER = hex(int(CIPHER, 2)).replace('0x','') 118 | 119 | if(len(CIPHER) % 32 == 0 and len(CIPHER) > 0): 120 | examine = CIPHER 121 | while(len(examine) != 0): 122 | plain_matrix = self.__decryptProcess(examine[:32]) 123 | plain_arr = list(np.array(plain_matrix).reshape(-1,)) 124 | plain_arr = self.__delPadding(plain_arr) 125 | for j in plain_arr: 126 | data+=chr(j) 127 | if(len(examine)==32): 128 | examine='' 129 | else: 130 | examine=examine[32:] 131 | self.ROUNDKEY = [] 132 | return data 133 | 134 | else: 135 | raise Exception(f"Hex: {CIPHER}, should be non-empty multiple of 32bits") 136 | 137 | else: 138 | raise Exception(f"type := ['hex', '0b', 'b64'] but got '{type}'") 139 | 140 | 141 | if(__name__ == '__main__'): 142 | 143 | aes128 = AES() 144 | 145 | key = 'Thats my Kung Fu' 146 | msg = 'Checking AES 128 on Python' 147 | encode = '__all__' # hex, b64 => base64, 0b => binary 148 | 149 | x = aes128.encrypt(key, msg, encode) 150 | print(x) 151 | 152 | # decode from binary 153 | y = aes128.decrypt(key, x['0b'], '0b') 154 | print(y) 155 | 156 | # decode from base64 157 | y = aes128.decrypt(key, x['b64'], 'b64') 158 | print(y) 159 | 160 | # decode from hex (default) 161 | y = aes128.decrypt(key, x['hex']) 162 | print(y) -------------------------------------------------------------------------------- /aes192.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from base64 import b64encode, b64decode 3 | from utils.converter import keyToHexArray, arrayShift, arraySbox, xorArray, addRoundKey, subBytes, shiftRow, mixColumn 4 | from utils.converter import hexToMatrix, inverseMixColumn 5 | 6 | 7 | class AES: 8 | 9 | def __init__(self): 10 | self.ROUND = 12 11 | self.ROUNDKEY = [] 12 | 13 | # Key Scheduling 14 | def __keySchedule(self, KEY): 15 | ROW, COL = 6, 4 16 | EXPANSION = 8 17 | hexKey = keyToHexArray(KEY, ROW, COL) 18 | self.ROUNDKEY.append(hexKey) 19 | for i in range(0, EXPANSION): 20 | prev_arr = self.ROUNDKEY[-1] 21 | last_col = prev_arr[ROW-1] 22 | shift_col = arrayShift(last_col) 23 | sbox_col = arraySbox(shift_col) 24 | col_1 = xorArray(prev_arr[0], sbox_col, i) 25 | col_2 = xorArray(col_1, prev_arr[1]) 26 | col_3 = xorArray(col_2, prev_arr[2]) 27 | col_4 = xorArray(col_3, prev_arr[3]) 28 | col_5 = xorArray(col_4, prev_arr[4]) 29 | col_6 = xorArray(col_5, prev_arr[5]) 30 | new_arr = np.array([col_1, col_2, col_3, col_4, col_5, col_6]) 31 | self.ROUNDKEY.append(new_arr) 32 | self.__convertRoundKey() 33 | 34 | # Convert 9 4*6 Matrix to 13 4*4 Matrix 35 | def __convertRoundKey(self): 36 | self.ROUNDKEY = np.concatenate(self.ROUNDKEY) 37 | temp = [] 38 | for i in range(self.ROUND+1): 39 | temp.append(self.ROUNDKEY[i*4:i*4+4]) 40 | self.ROUNDKEY = temp 41 | 42 | # Encryption Process 43 | def __encryptProcess(self, TEXT): 44 | hexData = keyToHexArray(TEXT) 45 | cipher_arr = addRoundKey(hexData, self.ROUNDKEY[0]) 46 | for i in range(1, self.ROUND+1): 47 | arr = cipher_arr 48 | arr = subBytes(arr) 49 | arr = shiftRow(arr) 50 | if(i != self.ROUND): 51 | arr = mixColumn(arr) 52 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 53 | cipher_arr = arr 54 | return cipher_arr 55 | 56 | # Encryption Add Padding 57 | def __addPadding(self, data): 58 | bytes = 16 59 | bits_arr = [] 60 | while(True): 61 | if(len(data) > bytes): 62 | bits_arr.append(data[:bytes]) 63 | data = data[bytes:] 64 | else: 65 | space = bytes-len(data) 66 | bits_arr.append(data + chr(space)*space) 67 | break 68 | return bits_arr 69 | 70 | # Decryption Process 71 | def __decryptProcess(self, CIPHER_HEX): 72 | hexData = hexToMatrix(CIPHER_HEX) 73 | plain_arr = addRoundKey(hexData, self.ROUNDKEY[-1]) 74 | for i in range(self.ROUND-1, -1, -1): 75 | arr = plain_arr 76 | arr = shiftRow(arr, left=False) 77 | arr = subBytes(arr, inverse=True) 78 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 79 | if(i != 0): 80 | arr = inverseMixColumn(arr) 81 | plain_arr = arr 82 | return plain_arr 83 | 84 | # Decryption Delete Padding 85 | def __delPadding(self, data): 86 | verify = data[-1] 87 | bytes = 16 88 | if(verify >= 1 and verify <= bytes-1): 89 | pad = data[bytes-verify:] 90 | sameCount = pad.count(verify) 91 | if(sameCount == verify): 92 | return data[:bytes-verify] 93 | return data 94 | return data 95 | 96 | #Encryption 97 | def encrypt(self, KEY, TEXT, type='hex'): 98 | text_arr = self.__addPadding(TEXT) 99 | self.__keySchedule(KEY) 100 | hex_ecrypt='' 101 | for i in text_arr: 102 | cipher_matrix = self.__encryptProcess(i) 103 | cipher_text = list(np.array(cipher_matrix).reshape(-1,)) 104 | for j in cipher_text: 105 | hex_ecrypt+=f'{j:02x}' 106 | self.ROUNDKEY = [] 107 | #conversion 108 | if(type == 'b64'): 109 | return b64encode(bytes.fromhex(hex_ecrypt)).decode() 110 | if(type == '0b'): 111 | return f'{int(hex_ecrypt, 16):0>b}' 112 | if(type == '__all__'): 113 | return { 114 | 'hex': hex_ecrypt, 115 | 'b64': b64encode(bytes.fromhex(hex_ecrypt)).decode(), 116 | '0b': bin(int(hex_ecrypt, 16))[2:].zfill(len(hex_ecrypt) * 4) 117 | } 118 | return hex_ecrypt 119 | 120 | # Decryption 121 | def decrypt(self, KEY, CIPHER, type='hex'): 122 | if type in ['hex', '0b', 'b64']: 123 | self.__keySchedule(KEY) 124 | data = '' 125 | 126 | if(type == 'b64'): 127 | CIPHER = b64decode(CIPHER).hex() 128 | 129 | if(type == '0b'): 130 | CIPHER = hex(int(CIPHER, 2)).replace('0x','') 131 | 132 | if(len(CIPHER) % 32 == 0 and len(CIPHER) > 0): 133 | examine = CIPHER 134 | while(len(examine) != 0): 135 | plain_matrix = self.__decryptProcess(examine[:32]) 136 | plain_arr = list(np.array(plain_matrix).reshape(-1,)) 137 | plain_arr = self.__delPadding(plain_arr) 138 | for j in plain_arr: 139 | data+=chr(j) 140 | if(len(examine)==32): 141 | examine='' 142 | else: 143 | examine=examine[32:] 144 | self.ROUNDKEY = [] 145 | return data 146 | 147 | else: 148 | raise Exception(f"Hex: {CIPHER}, should be non-empty multiple of 32bits") 149 | 150 | else: 151 | raise Exception(f"type := ['hex', '0b', 'b64'] but got '{type}'") 152 | 153 | 154 | if(__name__ == '__main__'): 155 | 156 | aes192 = AES() 157 | 158 | key = 'Thats my Kung Fu12345678' 159 | msg = 'Checking AES 192 on Python' 160 | encode = '__all__' # hex, b64 => base64, 0b => binary 161 | 162 | x = aes192.encrypt(key, msg, encode) 163 | print(x) 164 | 165 | # decode from binary 166 | y = aes192.decrypt(key, x['0b'], '0b') 167 | print(y) 168 | 169 | # decode from base64 170 | y = aes192.decrypt(key, x['b64'], 'b64') 171 | print(y) 172 | 173 | # decode from hex (default) 174 | y = aes192.decrypt(key, x['hex']) 175 | print(y) -------------------------------------------------------------------------------- /aes256.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from base64 import b64encode, b64decode 3 | from utils.converter import keyToHexArray, arrayShift, arraySbox, xorArray, addRoundKey, subBytes, shiftRow, mixColumn 4 | from utils.converter import hexToMatrix, inverseMixColumn 5 | 6 | 7 | class AES: 8 | 9 | def __init__(self): 10 | self.ROUND = 14 11 | self.ROUNDKEY = [] 12 | 13 | # Key Scheduling 14 | def __keySchedule(self, KEY): 15 | ROW, COL = 8, 4 16 | EXPANSION = 7 17 | hexKey = keyToHexArray(KEY, ROW, COL) 18 | self.ROUNDKEY.append(hexKey) 19 | for i in range(0, EXPANSION): 20 | prev_arr = self.ROUNDKEY[-1] 21 | last_col = prev_arr[ROW-1] 22 | shift_col = arrayShift(last_col) 23 | sbox_col = arraySbox(shift_col) 24 | col_1 = xorArray(prev_arr[0], sbox_col, i) 25 | col_2 = xorArray(col_1, prev_arr[1]) 26 | col_3 = xorArray(col_2, prev_arr[2]) 27 | col_4 = xorArray(col_3, prev_arr[3]) 28 | # additional non-linear transformation after the fourth column 29 | # https://crypto.stackexchange.com/questions/20/what-are-the-practical-differences-between-256-bit-192-bit-and-128-bit-aes-enc#answer-1527 30 | col_5 = xorArray(arraySbox(np.copy(col_4)), prev_arr[4]) 31 | col_6 = xorArray(col_5, prev_arr[5]) 32 | col_7 = xorArray(col_6, prev_arr[6]) 33 | col_8 = xorArray(col_7, prev_arr[7]) 34 | new_arr = np.array([col_1, col_2, col_3, col_4, col_5, col_6, col_7, col_8]) 35 | self.ROUNDKEY.append(new_arr) 36 | self.__convertRoundKey() 37 | 38 | # Convert 8 4*8 Matrix to 15 4*4 Matrix 39 | def __convertRoundKey(self): 40 | self.ROUNDKEY = np.concatenate(self.ROUNDKEY) 41 | temp = [] 42 | for i in range(self.ROUND+1): 43 | temp.append(self.ROUNDKEY[i*4:i*4+4]) 44 | self.ROUNDKEY = temp 45 | 46 | # Encryption Process 47 | def __encryptProcess(self, TEXT): 48 | hexData = keyToHexArray(TEXT) 49 | cipher_arr = addRoundKey(hexData, self.ROUNDKEY[0]) 50 | for i in range(1, self.ROUND+1): 51 | arr = cipher_arr 52 | arr = subBytes(arr) 53 | arr = shiftRow(arr) 54 | if(i != self.ROUND): 55 | arr = mixColumn(arr) 56 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 57 | cipher_arr = arr 58 | return cipher_arr 59 | 60 | # Encryption Add Padding 61 | def __addPadding(self, data): 62 | bytes = 16 63 | bits_arr = [] 64 | while(True): 65 | if(len(data) > bytes): 66 | bits_arr.append(data[:bytes]) 67 | data = data[bytes:] 68 | else: 69 | space = bytes-len(data) 70 | bits_arr.append(data + chr(space)*space) 71 | break 72 | return bits_arr 73 | 74 | # Decryption Process 75 | def __decryptProcess(self, CIPHER_HEX): 76 | hexData = hexToMatrix(CIPHER_HEX) 77 | plain_arr = addRoundKey(hexData, self.ROUNDKEY[-1]) 78 | for i in range(self.ROUND-1, -1, -1): 79 | arr = plain_arr 80 | arr = shiftRow(arr, left=False) 81 | arr = subBytes(arr, inverse=True) 82 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 83 | if(i != 0): 84 | arr = inverseMixColumn(arr) 85 | plain_arr = arr 86 | return plain_arr 87 | 88 | # Decryption Delete Padding 89 | def __delPadding(self, data): 90 | verify = data[-1] 91 | bytes = 16 92 | if(verify >= 1 and verify <= bytes-1): 93 | pad = data[bytes-verify:] 94 | sameCount = pad.count(verify) 95 | if(sameCount == verify): 96 | return data[:bytes-verify] 97 | return data 98 | return data 99 | 100 | #Encryption 101 | def encrypt(self, KEY, TEXT, type='hex'): 102 | text_arr = self.__addPadding(TEXT) 103 | self.__keySchedule(KEY) 104 | hex_ecrypt='' 105 | for i in text_arr: 106 | cipher_matrix = self.__encryptProcess(i) 107 | cipher_text = list(np.array(cipher_matrix).reshape(-1,)) 108 | for j in cipher_text: 109 | hex_ecrypt+=f'{j:02x}' 110 | self.ROUNDKEY = [] 111 | #conversion 112 | if(type == 'b64'): 113 | return b64encode(bytes.fromhex(hex_ecrypt)).decode() 114 | if(type == '0b'): 115 | return f'{int(hex_ecrypt, 16):0>b}' 116 | if(type == '__all__'): 117 | return { 118 | 'hex': hex_ecrypt, 119 | 'b64': b64encode(bytes.fromhex(hex_ecrypt)).decode(), 120 | '0b': bin(int(hex_ecrypt, 16))[2:].zfill(len(hex_ecrypt) * 4) 121 | } 122 | return hex_ecrypt 123 | 124 | # Decryption 125 | def decrypt(self, KEY, CIPHER, type='hex'): 126 | if type in ['hex', '0b', 'b64']: 127 | self.__keySchedule(KEY) 128 | data = '' 129 | 130 | if(type == 'b64'): 131 | CIPHER = b64decode(CIPHER).hex() 132 | 133 | if(type == '0b'): 134 | CIPHER = hex(int(CIPHER, 2)).replace('0x','') 135 | 136 | if(len(CIPHER) % 32 == 0 and len(CIPHER) > 0): 137 | examine = CIPHER 138 | while(len(examine) != 0): 139 | plain_matrix = self.__decryptProcess(examine[:32]) 140 | plain_arr = list(np.array(plain_matrix).reshape(-1,)) 141 | plain_arr = self.__delPadding(plain_arr) 142 | for j in plain_arr: 143 | data+=chr(j) 144 | if(len(examine)==32): 145 | examine='' 146 | else: 147 | examine=examine[32:] 148 | self.ROUNDKEY = [] 149 | return data 150 | 151 | else: 152 | raise Exception(f"Hex: {CIPHER}, should be non-empty multiple of 32bits") 153 | 154 | else: 155 | raise Exception(f"type := ['hex', '0b', 'b64'] but got '{type}'") 156 | 157 | 158 | if(__name__ == '__main__'): 159 | 160 | aes256 = AES() 161 | 162 | key = 'Thats my Kung Fu1234567876543210' 163 | msg = 'Checking AES 256 on Python' 164 | encode = '__all__' # hex, b64 => base64, 0b => binary 165 | 166 | x = aes256.encrypt(key, msg, encode) 167 | print(x) 168 | 169 | # decode from binary 170 | y = aes256.decrypt(key, x['0b'], '0b') 171 | print(y) 172 | 173 | # decode from base64 174 | y = aes256.decrypt(key, x['b64'], 'b64') 175 | print(y) 176 | 177 | # decode from hex (default) 178 | y = aes256.decrypt(key, x['hex']) 179 | print(y) -------------------------------------------------------------------------------- /aes512.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from base64 import b64encode, b64decode 3 | from utils.converter import keyToHexArray, arrayShift, arraySbox, xorArray, addRoundKey, subBytes, shiftRow, mixColumn 4 | from utils.converter import hexToMatrix, inverseMixColumn 5 | 6 | 7 | class AES: 8 | 9 | def __init__(self): 10 | self.ROUND = 10 11 | self.ORDER = 8 12 | self.ROUNDKEY = [] 13 | 14 | # Key Scheduling 15 | def __keySchedule(self, KEY): 16 | ROW, COL = 8, 8 17 | hexKey = keyToHexArray(KEY, ROW, COL) 18 | self.ROUNDKEY.append(hexKey) 19 | #print(hexKey) 20 | for i in range(0, self.ROUND): 21 | prev_arr = self.ROUNDKEY[-1] 22 | last_col = prev_arr[ROW-1] 23 | shift_col = arrayShift(last_col) 24 | sbox_col = arraySbox(shift_col) 25 | col_1 = xorArray(prev_arr[0], sbox_col, self.ORDER, i) 26 | col_2 = xorArray(col_1, prev_arr[1], self.ORDER) 27 | col_3 = xorArray(col_2, prev_arr[2], self.ORDER) 28 | col_4 = xorArray(col_3, prev_arr[3]) 29 | # additional non-linear transformation after the fourth column 30 | # https://crypto.stackexchange.com/questions/20/what-are-the-practical-differences-between-256-bit-192-bit-and-128-bit-aes-enc#answer-1527 31 | col_5 = xorArray(arraySbox(np.copy(col_4)), prev_arr[4], self.ORDER) 32 | col_6 = xorArray(col_5, prev_arr[5], self.ORDER) 33 | col_7 = xorArray(col_6, prev_arr[6], self.ORDER) 34 | col_8 = xorArray(col_7, prev_arr[7], self.ORDER) 35 | new_arr = np.array([col_1, col_2, col_3, col_4, col_5, col_6, col_7, col_8]) 36 | self.ROUNDKEY.append(new_arr) 37 | self.__convertRoundKey() 38 | 39 | # Convert 8 4*8 Matrix to 15 4*4 Matrix 40 | def __convertRoundKey(self): 41 | self.ROUNDKEY = np.concatenate(self.ROUNDKEY) 42 | temp = [] 43 | for i in range(self.ROUND+1): 44 | temp.append(self.ROUNDKEY[i*8:i*8+8]) 45 | self.ROUNDKEY = temp 46 | 47 | # Encryption Process 48 | def __encryptProcess(self, TEXT): 49 | #print(TEXT) 50 | hexData = keyToHexArray(TEXT, self.ORDER, self.ORDER) 51 | #print(hexData) 52 | cipher_arr = addRoundKey(hexData, self.ROUNDKEY[0]) 53 | for i in range(1, self.ROUND+1): 54 | arr = cipher_arr 55 | arr = subBytes(arr) 56 | arr = shiftRow(arr, left=True, order=self.ORDER) 57 | if(i != self.ROUND): 58 | arr = mixColumn(arr, order=self.ORDER) 59 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 60 | cipher_arr = arr 61 | return cipher_arr 62 | 63 | # Encryption Add Padding 64 | def __addPadding(self, data): 65 | bytes = self.ORDER**2 66 | bits_arr = [] 67 | while(True): 68 | if(len(data) > bytes): 69 | bits_arr.append(data[:bytes]) 70 | data = data[bytes:] 71 | else: 72 | space = bytes-len(data) 73 | bits_arr.append(data + chr(space)*space) 74 | break 75 | return bits_arr 76 | 77 | # Decryption Process 78 | def __decryptProcess(self, CIPHER_HEX): 79 | hexData = hexToMatrix(CIPHER_HEX, self.ORDER) 80 | plain_arr = addRoundKey(hexData, self.ROUNDKEY[-1]) 81 | for i in range(self.ROUND-1, -1, -1): 82 | arr = plain_arr 83 | arr = shiftRow(arr, left=False, order=self.ORDER) 84 | arr = subBytes(arr, inverse=True) 85 | arr = addRoundKey(arr, self.ROUNDKEY[i]) 86 | if(i != 0): 87 | arr = inverseMixColumn(arr, order=self.ORDER) 88 | plain_arr = arr 89 | return plain_arr 90 | 91 | # Decryption Delete Padding 92 | def __delPadding(self, data): 93 | verify = data[-1] 94 | bytes = self.ORDER**2 95 | if(verify >= 1 and verify <= bytes-1): 96 | pad = data[bytes-verify:] 97 | sameCount = pad.count(verify) 98 | if(sameCount == verify): 99 | return data[:bytes-verify] 100 | return data 101 | return data 102 | 103 | #Encryption 104 | def encrypt(self, KEY, TEXT, type='hex'): 105 | text_arr = self.__addPadding(TEXT) 106 | self.__keySchedule(KEY) 107 | hex_ecrypt='' 108 | for i in text_arr: 109 | cipher_matrix = self.__encryptProcess(i) 110 | cipher_text = list(np.array(cipher_matrix).reshape(-1,)) 111 | for j in cipher_text: 112 | hex_ecrypt+=f'{j:02x}' 113 | self.ROUNDKEY = [] 114 | #conversion 115 | if(type == 'b64'): 116 | return b64encode(bytes.fromhex(hex_ecrypt)).decode() 117 | if(type == '0b'): 118 | return f'{int(hex_ecrypt, 16):0>b}' 119 | if(type == '__all__'): 120 | return { 121 | 'hex': hex_ecrypt, 122 | 'b64': b64encode(bytes.fromhex(hex_ecrypt)).decode(), 123 | '0b': bin(int(hex_ecrypt, 16))[2:].zfill(len(hex_ecrypt) * 4) 124 | } 125 | return hex_ecrypt 126 | 127 | # Decryption 128 | def decrypt(self, KEY, CIPHER, type='hex'): 129 | block = self.ORDER*self.ORDER*2 130 | if type in ['hex', '0b', 'b64']: 131 | self.__keySchedule(KEY) 132 | data = '' 133 | 134 | if(type == 'b64'): 135 | CIPHER = b64decode(CIPHER).hex() 136 | 137 | if(type == '0b'): 138 | CIPHER = hex(int(CIPHER, 2)).replace('0x','') 139 | 140 | if(len(CIPHER) % block == 0 and len(CIPHER) > 0): 141 | examine = CIPHER 142 | while(len(examine) != 0): 143 | plain_matrix = self.__decryptProcess(examine[:block]) 144 | plain_arr = list(np.array(plain_matrix).reshape(-1,)) 145 | plain_arr = self.__delPadding(plain_arr) 146 | for j in plain_arr: 147 | data+=chr(j) 148 | if(len(examine)==block): 149 | examine='' 150 | else: 151 | examine=examine[block:] 152 | self.ROUNDKEY = [] 153 | return data 154 | 155 | else: 156 | raise Exception(f"Hex: {CIPHER}, should be non-empty multiple of 32bits") 157 | 158 | else: 159 | raise Exception(f"type := ['hex', '0b', 'b64'] but got '{type}'") 160 | 161 | 162 | if(__name__ == '__main__'): 163 | 164 | aes512 = AES() 165 | 166 | key = 'Thats my Kung Fu1234567876543210Thats my Kung Fu1234567876543210' 167 | msg = 'Checking AES 512 on Python' 168 | encode = '__all__' # hex, b64 => base64, 0b => binary 169 | 170 | x = aes512.encrypt(key, msg, encode) 171 | print(x) 172 | 173 | # # decode from binary 174 | y = aes512.decrypt('Thats my Kung Fu1234567876543210Thats my Kung Fu1234567876543210', x['0b'], '0b') 175 | print(y) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |