├── .gitignore ├── README.md ├── main.py └── aes128.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | .idea/* 4 | 5 | .idea 6 | 7 | __pycache__/ 8 | 9 | __pycache__* 10 | 11 | *.pyc 12 | 13 | *.pyo 14 | 15 | venv/ 16 | 17 | venv/* 18 | 19 | ./venv 20 | 21 | *_test.* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AES-128 2 | ============== 3 | This tool provides encryption/decrytion according to AES(128) standart. The standart is based on symmetric Rijndael algorithm and regulates work with 128/192/256 bit long keys. 4 | My tool works only with 128 bit length key, ie your secret key should be less than 16 symbols. The algorithm has been recognized impregnable even with this key-length. 5 | [Link to the official document for details](http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf) 6 | 7 | 8 | ## How to use 9 | 10 | The tool is able to encrypt anything which consist of bytes, type of file doesn't matter. 11 | The first way is to put *aes128.py* in project directoty or somewhere in PYTHON PATH and import 12 | 13 | ```python 14 | import aes128 15 | 16 | cipher = aes128.encrypt(input_bytes, key) 17 | message = aes128.decrypt(cipher, key) 18 | ``` 19 | 20 | Input and output types is described in doc strings. I assume you won't use not the English alphabet for the secret key, because ```ord()``` of symbols should return less than 255, ie we can keep it using just 1 byte per symbol. 21 | 22 | The second way is to run *main.py* which provides a shy CLI-interface. Just run it and follow the instructions. 23 | 24 | ## Author 25 | 26 | If you want give me feedback - feel free to contact 27 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | 3 | import os 4 | import time 5 | 6 | import aes128 7 | 8 | print('Step 1:') 9 | while True: 10 | print('Press 1 for encription smth and 2 for decription') 11 | way = input() 12 | if way not in ['1', '2']: 13 | print('Action denied') 14 | continue 15 | else: 16 | break 17 | print() 18 | 19 | print('Step 2:') 20 | while True: 21 | print('Enter full name of file') 22 | input_path = os.path.abspath(input()) 23 | 24 | if os.path.isfile(input_path): 25 | break 26 | else: 27 | print('This is not a file') 28 | continue 29 | print() 30 | 31 | print('Step 3:') 32 | while True: 33 | print('Enter your Key for encription/decription. The Key must be less than 16 symbols. Please, don\'t forget it!') 34 | key = input() 35 | 36 | if len(key) > 16: 37 | print('Too long Key. Imagine another one') 38 | continue 39 | 40 | for symbol in key: 41 | if ord(symbol) > 0xff: 42 | print('That key won\'t work. Try another using only latin alphabet and numbers') 43 | continue 44 | 45 | break 46 | print('\r\nPlease, wait...') 47 | 48 | time_before = time.time() 49 | 50 | # Input data 51 | with open(input_path, 'rb') as f: 52 | data = f.read() 53 | 54 | if way == '1': 55 | crypted_data = [] 56 | temp = [] 57 | for byte in data: 58 | temp.append(byte) 59 | if len(temp) == 16: 60 | crypted_part = aes128.encrypt(temp, key) 61 | crypted_data.extend(crypted_part) 62 | del temp[:] 63 | else: 64 | #padding v1 65 | # crypted_data.extend(temp) 66 | 67 | # padding v2 68 | if 0 < len(temp) < 16: 69 | empty_spaces = 16 - len(temp) 70 | for i in range(empty_spaces - 1): 71 | temp.append(0) 72 | temp.append(1) 73 | crypted_part = aes128.encrypt(temp, key) 74 | crypted_data.extend(crypted_part) 75 | 76 | out_path = os.path.join(os.path.dirname(input_path) , 'crypted_' + os.path.basename(input_path)) 77 | 78 | # Ounput data 79 | with open(out_path, 'xb') as ff: 80 | ff.write(bytes(crypted_data)) 81 | 82 | else: # if way == '2' 83 | decrypted_data = [] 84 | temp = [] 85 | for byte in data: 86 | temp.append(byte) 87 | if len(temp) == 16: 88 | decrypted_part = aes128.decrypt(temp, key) 89 | decrypted_data.extend(decrypted_part) 90 | del temp[:] 91 | else: 92 | #padding v1 93 | # decrypted_data.extend(temp) 94 | 95 | # padding v2 96 | if 0 < len(temp) < 16: 97 | empty_spaces = 16 - len(temp) 98 | for i in range(empty_spaces - 1): 99 | temp.append(0) 100 | temp.append(1) 101 | decrypted_part = aes128.encrypt(temp, key) 102 | decrypted_data.extend(crypted_part) 103 | 104 | out_path = os.path.join(os.path.dirname(input_path) , 'decrypted_' + os.path.basename(input_path)) 105 | 106 | # Ounput data 107 | with open(out_path, 'xb') as ff: 108 | ff.write(bytes(decrypted_data)) 109 | 110 | time_after = time.time() 111 | 112 | print('New file here:', out_path, '--', time_after - time_before, ' seconds') 113 | print('If smth wrong check the key you entered') -------------------------------------------------------------------------------- /aes128.py: -------------------------------------------------------------------------------- 1 | """Tham module provides encrypting/decrypting according AES(128) standart. 2 | Based on Rijndael algorithm, AES uses 4 transformation for encrypting: SubSytes(), ShiftRows(), 3 | MixColumns() and AddRoundKey(). For decrypting it uses inverse functions of that fout. 4 | Detales you can read here: 5 | http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf 6 | or here: 7 | http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 8 | or here: 9 | http://www.cs.bc.edu/~straubin/cs381-05/blockciphers/rijndael_ingles2004.swf 10 | or somewhere else. 11 | 12 | Comments rather won't help if don't read documentation of the algorithm. 13 | 14 | """ 15 | 16 | nb = 4 # number of coloumn of State (for AES = 4) 17 | nr = 10 # number of rounds ib ciper cycle (if nb = 4 nr = 10) 18 | nk = 4 # the key length (in 32-bit words) 19 | 20 | # This dict will be used in SubBytes(). 21 | hex_symbols_to_int = {'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15} 22 | 23 | sbox = [ 24 | 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 25 | 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 26 | 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 27 | 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 28 | 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 29 | 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 30 | 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 31 | 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 32 | 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 33 | 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 34 | 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 35 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 36 | 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 37 | 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 38 | 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 39 | 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 40 | ] 41 | 42 | inv_sbox = [ 43 | 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 44 | 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 45 | 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 46 | 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 47 | 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 48 | 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 49 | 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 50 | 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 51 | 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 52 | 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 53 | 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 54 | 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 55 | 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 56 | 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 57 | 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 58 | 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d 59 | ] 60 | 61 | rcon = [[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36], 62 | [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 63 | [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], 64 | [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] 65 | ] 66 | 67 | 68 | def encrypt(input_bytes, key): 69 | """Function encrypts the input_bytes according to AES(128) algorithm using the key 70 | 71 | Args: 72 | input_bytes -- list of int less than 255, ie list of bytes. Length of input_bytes is constantly 16 73 | key -- a strig of plain text. Do not forget it! The same string is used in decryption 74 | 75 | Returns: 76 | List of int 77 | 78 | """ 79 | 80 | # let's prepare our enter data: State array and KeySchedule 81 | state = [[] for j in range(4)] 82 | for r in range(4): 83 | for c in range(nb): 84 | state[r].append(input_bytes[r + 4 * c]) 85 | 86 | key_schedule = key_expansion(key) 87 | 88 | state = add_round_key(state, key_schedule) 89 | 90 | for rnd in range(1, nr): 91 | state = sub_bytes(state) 92 | state = shift_rows(state) 93 | state = mix_columns(state) 94 | state = add_round_key(state, key_schedule, rnd) 95 | 96 | state = sub_bytes(state) 97 | state = shift_rows(state) 98 | state = add_round_key(state, key_schedule, rnd + 1) 99 | 100 | output = [None for i in range(4 * nb)] 101 | for r in range(4): 102 | for c in range(nb): 103 | output[r + 4 * c] = state[r][c] 104 | 105 | return output 106 | 107 | 108 | def decrypt(cipher, key): 109 | """Function decrypts the cipher according to AES(128) algorithm using the key 110 | 111 | Args: 112 | cipher -- list of int less than 255, ie list of bytes 113 | key -- a strig of plain text. Do not forget it! The same string is used in decryption 114 | 115 | Returns: 116 | List of int 117 | 118 | """ 119 | 120 | # let's prepare our algorithm enter data: State array and KeySchedule 121 | state = [[] for i in range(nb)] 122 | for r in range(4): 123 | for c in range(nb): 124 | state[r].append(cipher[r + 4 * c]) 125 | 126 | key_schedule = key_expansion(key) 127 | 128 | state = add_round_key(state, key_schedule, nr) 129 | 130 | rnd = nr - 1 131 | while rnd >= 1: 132 | state = shift_rows(state, inv=True) 133 | state = sub_bytes(state, inv=True) 134 | state = add_round_key(state, key_schedule, rnd) 135 | state = mix_columns(state, inv=True) 136 | 137 | rnd -= 1 138 | 139 | state = shift_rows(state, inv=True) 140 | state = sub_bytes(state, inv=True) 141 | state = add_round_key(state, key_schedule, rnd) 142 | 143 | output = [None for i in range(4 * nb)] 144 | for r in range(4): 145 | for c in range(nb): 146 | output[r + 4 * c] = state[r][c] 147 | 148 | return output 149 | 150 | 151 | def sub_bytes(state, inv=False): 152 | """That transformation replace every element from State on element from Sbox 153 | according the algorithm: in hexadecimal notation an element from State 154 | consist of two values: 0x. We take elem from crossing 155 | val1-row and val2-column in Sbox and put it instead of the element in State. 156 | If decryption-transformation is on (inv == True) it uses InvSbox instead Sbox. 157 | 158 | Args: 159 | inv -- If value == False means function is encryption-transformation. 160 | True - decryption-transformation 161 | 162 | """ 163 | 164 | if inv == False: # encrypt 165 | box = sbox 166 | else: # decrypt 167 | box = inv_sbox 168 | 169 | for i in range(len(state)): 170 | for j in range(len(state[i])): 171 | row = state[i][j] // 0x10 172 | col = state[i][j] % 0x10 173 | 174 | # Our Sbox is a flat array, not a bable. So, we use this trich to find elem: 175 | # And DO NOT change list sbox! if you want it to work 176 | box_elem = box[16 * row + col] 177 | state[i][j] = box_elem 178 | 179 | return state 180 | 181 | 182 | def shift_rows(state, inv=False): 183 | """That transformation shifts rows of State: the second rotate over 1 bytes, 184 | the third rotate over 2 bytes, the fourtg rotate over 3 bytes. The transformation doesn't 185 | touch the first row. When encrypting transformation uses left shift, in decription - right shift 186 | 187 | Args: 188 | inv: If value == False means function is encryption mode. True - decryption mode 189 | 190 | """ 191 | 192 | count = 1 193 | 194 | if inv == False: # encrypting 195 | for i in range(1, nb): 196 | state[i] = left_shift(state[i], count) 197 | count += 1 198 | else: # decryptionting 199 | for i in range(1, nb): 200 | state[i] = right_shift(state[i], count) 201 | count += 1 202 | 203 | return state 204 | 205 | 206 | def mix_columns(state, inv=False): 207 | """When encrypting transformation multiplyes every column of State with 208 | a fixed polinomial a(x) = {03}x**3 + {01}x**2 + {01}x + {02} in Galua field. 209 | When decrypting multiplies with a'(x) = {0b}x**3 + {0d}x**2 + {09}x + {0e} 210 | Detailed information in AES standart. 211 | 212 | Args: 213 | inv: If value == False means function is encryption mode. True - decryption mode 214 | 215 | """ 216 | 217 | for i in range(nb): 218 | 219 | if inv == False: # encryption 220 | s0 = mul_by_02(state[0][i]) ^ mul_by_03(state[1][i]) ^ state[2][i] ^ state[3][i] 221 | s1 = state[0][i] ^ mul_by_02(state[1][i]) ^ mul_by_03(state[2][i]) ^ state[3][i] 222 | s2 = state[0][i] ^ state[1][i] ^ mul_by_02(state[2][i]) ^ mul_by_03(state[3][i]) 223 | s3 = mul_by_03(state[0][i]) ^ state[1][i] ^ state[2][i] ^ mul_by_02(state[3][i]) 224 | else: # decryption 225 | s0 = mul_by_0e(state[0][i]) ^ mul_by_0b(state[1][i]) ^ mul_by_0d(state[2][i]) ^ mul_by_09(state[3][i]) 226 | s1 = mul_by_09(state[0][i]) ^ mul_by_0e(state[1][i]) ^ mul_by_0b(state[2][i]) ^ mul_by_0d(state[3][i]) 227 | s2 = mul_by_0d(state[0][i]) ^ mul_by_09(state[1][i]) ^ mul_by_0e(state[2][i]) ^ mul_by_0b(state[3][i]) 228 | s3 = mul_by_0b(state[0][i]) ^ mul_by_0d(state[1][i]) ^ mul_by_09(state[2][i]) ^ mul_by_0e(state[3][i]) 229 | 230 | state[0][i] = s0 231 | state[1][i] = s1 232 | state[2][i] = s2 233 | state[3][i] = s3 234 | 235 | return state 236 | 237 | 238 | def key_expansion(key): 239 | """It makes list of RoundKeys for function AddRoundKey. All details 240 | about algorithm is is in AES standart 241 | 242 | """ 243 | 244 | key_symbols = [ord(symbol) for symbol in key] 245 | 246 | # ChipherKey shoul contain 16 symbols to fill 4*4 table. If it's less 247 | # complement the key with "0x01" 248 | if len(key_symbols) < 4 * nk: 249 | for i in range(4 * nk - len(key_symbols)): 250 | key_symbols.append(0x01) 251 | 252 | # make ChipherKey(which is base of KeySchedule) 253 | key_schedule = [[] for i in range(4)] 254 | for r in range(4): 255 | for c in range(nk): 256 | key_schedule[r].append(key_symbols[r + 4 * c]) 257 | 258 | # Comtinue to fill KeySchedule 259 | for col in range(nk, nb * (nr + 1)): # col - column number 260 | if col % nk == 0: 261 | # take shifted (col - 1)th column... 262 | tmp = [key_schedule[row][col - 1] for row in range(1, 4)] 263 | tmp.append(key_schedule[0][col - 1]) 264 | 265 | # change its elements using Sbox-table like in SubBytes... 266 | for j in range(len(tmp)): 267 | sbox_row = tmp[j] // 0x10 268 | sbox_col = tmp[j] % 0x10 269 | sbox_elem = sbox[16 * sbox_row + sbox_col] 270 | tmp[j] = sbox_elem 271 | 272 | # and finally make XOR of 3 columns 273 | for row in range(4): 274 | s = (key_schedule[row][col - 4]) ^ (tmp[row]) ^ (rcon[row][int(col / nk - 1)]) 275 | key_schedule[row].append(s) 276 | 277 | else: 278 | # just make XOR of 2 columns 279 | for row in range(4): 280 | s = key_schedule[row][col - 4] ^ key_schedule[row][col - 1] 281 | key_schedule[row].append(s) 282 | 283 | return key_schedule 284 | 285 | 286 | def add_round_key(state, key_schedule, round=0): 287 | """That transformation combines State and KeySchedule together. Xor 288 | of State and RoundSchedule(part of KeySchedule). 289 | 290 | """ 291 | 292 | for col in range(nk): 293 | # nb*round is a shift which indicates start of a part of the KeySchedule 294 | s0 = state[0][col] ^ key_schedule[0][nb * round + col] 295 | s1 = state[1][col] ^ key_schedule[1][nb * round + col] 296 | s2 = state[2][col] ^ key_schedule[2][nb * round + col] 297 | s3 = state[3][col] ^ key_schedule[3][nb * round + col] 298 | 299 | state[0][col] = s0 300 | state[1][col] = s1 301 | state[2][col] = s2 302 | state[3][col] = s3 303 | 304 | return state 305 | 306 | 307 | # Small helpful functions block 308 | 309 | def left_shift(array, count): 310 | """Rotate the array over count times""" 311 | 312 | res = array[:] 313 | for i in range(count): 314 | temp = res[1:] 315 | temp.append(res[0]) 316 | res[:] = temp[:] 317 | 318 | return res 319 | 320 | 321 | def right_shift(array, count): 322 | """Rotate the array over count times""" 323 | 324 | res = array[:] 325 | for i in range(count): 326 | tmp = res[:-1] 327 | tmp.insert(0, res[-1]) 328 | res[:] = tmp[:] 329 | 330 | return res 331 | 332 | 333 | def mul_by_02(num): 334 | """The function multiplies by 2 in Galua space""" 335 | 336 | if num < 0x80: 337 | res = (num << 1) 338 | else: 339 | res = (num << 1) ^ 0x1b 340 | 341 | return res % 0x100 342 | 343 | 344 | def mul_by_03(num): 345 | """The function multiplies by 3 in Galua space 346 | example: 0x03*num = (0x02 + 0x01)num = num*0x02 + num 347 | Addition in Galua field is oparetion XOR 348 | 349 | """ 350 | return (mul_by_02(num) ^ num) 351 | 352 | 353 | def mul_by_09(num): 354 | # return mul_by_03(num)^mul_by_03(num)^mul_by_03(num) - works wrong, I don't know why 355 | return mul_by_02(mul_by_02(mul_by_02(num))) ^ num 356 | 357 | 358 | def mul_by_0b(num): 359 | # return mul_by_09(num)^mul_by_02(num) 360 | return mul_by_02(mul_by_02(mul_by_02(num))) ^ mul_by_02(num) ^ num 361 | 362 | 363 | def mul_by_0d(num): 364 | # return mul_by_0b(num)^mul_by_02(num) 365 | return mul_by_02(mul_by_02(mul_by_02(num))) ^ mul_by_02(mul_by_02(num)) ^ num 366 | 367 | 368 | def mul_by_0e(num): 369 | # return mul_by_0d(num)^num 370 | return mul_by_02(mul_by_02(mul_by_02(num))) ^ mul_by_02(mul_by_02(num)) ^ mul_by_02(num) 371 | 372 | # End of small helpful functions block 373 | --------------------------------------------------------------------------------