]> jfr.im git - yt-dlp.git/blame - youtube_dl/aes.py
Add intlist_to_bytes to utils.py
[yt-dlp.git] / youtube_dl / aes.py
CommitLineData
f3bcebb1 1__all__ = ['aes_encrypt', 'key_expansion', 'aes_ctr_decrypt', 'aes_decrypt_text']
2
3import base64
4from math import ceil
5
48ea9cea
PH
6from .utils import bytes_to_intlist
7
f3bcebb1 8BLOCK_SIZE_BYTES = 16
9
10def aes_ctr_decrypt(data, key, counter):
11 """
12 Decrypt with aes in counter mode
13
14 @param {int[]} data cipher
15 @param {int[]} key 16/24/32-Byte cipher key
16 @param {instance} counter Instance whose next_value function (@returns {int[]} 16-Byte block)
17 returns the next counter block
18 @returns {int[]} decrypted data
19 """
20 expanded_key = key_expansion(key)
48ea9cea 21 block_count = int(ceil(float(len(data)) // BLOCK_SIZE_BYTES))
f3bcebb1 22
23 decrypted_data=[]
24 for i in range(block_count):
25 counter_block = counter.next_value()
26 block = data[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES]
27 block += [0]*(BLOCK_SIZE_BYTES - len(block))
28
29 cipher_counter_block = aes_encrypt(counter_block, expanded_key)
30 decrypted_data += xor(block, cipher_counter_block)
31 decrypted_data = decrypted_data[:len(data)]
32
33 return decrypted_data
34
35def key_expansion(data):
36 """
37 Generate key schedule
38
39 @param {int[]} data 16/24/32-Byte cipher key
40 @returns {int[]} 176/208/240-Byte expanded key
41 """
42 data = data[:] # copy
43 rcon_iteration = 1
44 key_size_bytes = len(data)
48ea9cea 45 expanded_key_size_bytes = (key_size_bytes // 4 + 7) * BLOCK_SIZE_BYTES
f3bcebb1 46
47 while len(data) < expanded_key_size_bytes:
48 temp = data[-4:]
49 temp = key_schedule_core(temp, rcon_iteration)
50 rcon_iteration += 1
51 data += xor(temp, data[-key_size_bytes : 4-key_size_bytes])
52
53 for _ in range(3):
54 temp = data[-4:]
55 data += xor(temp, data[-key_size_bytes : 4-key_size_bytes])
56
57 if key_size_bytes == 32:
58 temp = data[-4:]
59 temp = sub_bytes(temp)
60 data += xor(temp, data[-key_size_bytes : 4-key_size_bytes])
61
62 for _ in range(3 if key_size_bytes == 32 else 2 if key_size_bytes == 24 else 0):
63 temp = data[-4:]
64 data += xor(temp, data[-key_size_bytes : 4-key_size_bytes])
65 data = data[:expanded_key_size_bytes]
66
67 return data
68
69def aes_encrypt(data, expanded_key):
70 """
71 Encrypt one block with aes
72
73 @param {int[]} data 16-Byte state
74 @param {int[]} expanded_key 176/208/240-Byte expanded key
75 @returns {int[]} 16-Byte cipher
76 """
48ea9cea 77 rounds = len(expanded_key) // BLOCK_SIZE_BYTES - 1
f3bcebb1 78
79 data = xor(data, expanded_key[:BLOCK_SIZE_BYTES])
80 for i in range(1, rounds+1):
81 data = sub_bytes(data)
82 data = shift_rows(data)
83 if i != rounds:
84 data = mix_columns(data)
85 data = xor(data, expanded_key[i*BLOCK_SIZE_BYTES : (i+1)*BLOCK_SIZE_BYTES])
86
87 return data
88
89def aes_decrypt_text(data, password, key_size_bytes):
90 """
91 Decrypt text
92 - The first 8 Bytes of decoded 'data' are the 8 high Bytes of the counter
93 - The cipher key is retrieved by encrypting the first 16 Byte of 'password'
94 with the first 'key_size_bytes' Bytes from 'password' (if necessary filled with 0's)
95 - Mode of operation is 'counter'
96
97 @param {str} data Base64 encoded string
98 @param {str,unicode} password Password (will be encoded with utf-8)
99 @param {int} key_size_bytes Possible values: 16 for 128-Bit, 24 for 192-Bit or 32 for 256-Bit
100 @returns {str} Decrypted data
101 """
102 NONCE_LENGTH_BYTES = 8
103
48ea9cea
PH
104 data = bytes_to_intlist(base64.b64decode(data))
105 password = bytes_to_intlist(password.encode('utf-8'))
f3bcebb1 106
107 key = password[:key_size_bytes] + [0]*(key_size_bytes - len(password))
48ea9cea 108 key = aes_encrypt(key[:BLOCK_SIZE_BYTES], key_expansion(key)) * (key_size_bytes // BLOCK_SIZE_BYTES)
f3bcebb1 109
110 nonce = data[:NONCE_LENGTH_BYTES]
111 cipher = data[NONCE_LENGTH_BYTES:]
112
113 class Counter:
114 __value = nonce + [0]*(BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES)
115 def next_value(self):
116 temp = self.__value
117 self.__value = inc(self.__value)
118 return temp
119
120 decrypted_data = aes_ctr_decrypt(cipher, key, Counter())
121 plaintext = ''.join(map(lambda x: chr(x), decrypted_data))
122
123 return plaintext
124
125RCON = (0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36)
126SBOX = (0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
127 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
128 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
129 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
130 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
131 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
132 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
133 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
134 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
135 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
136 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
137 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
138 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
139 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
140 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
141 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16)
142MIX_COLUMN_MATRIX = ((2,3,1,1),
143 (1,2,3,1),
144 (1,1,2,3),
145 (3,1,1,2))
146
147def sub_bytes(data):
48ea9cea 148 return [SBOX[x] for x in data]
f3bcebb1 149
150def rotate(data):
151 return data[1:] + [data[0]]
152
153def key_schedule_core(data, rcon_iteration):
154 data = rotate(data)
155 data = sub_bytes(data)
156 data[0] = data[0] ^ RCON[rcon_iteration]
157
158 return data
159
160def xor(data1, data2):
48ea9cea 161 return [x^y for x, y in zip(data1, data2)]
f3bcebb1 162
163def mix_column(data):
164 data_mixed = []
165 for row in range(4):
166 mixed = 0
167 for column in range(4):
168 addend = data[column]
169 if MIX_COLUMN_MATRIX[row][column] in (2,3):
170 addend <<= 1
171 if addend > 0xff:
172 addend &= 0xff
173 addend ^= 0x1b
174 if MIX_COLUMN_MATRIX[row][column] == 3:
175 addend ^= data[column]
176 mixed ^= addend & 0xff
177 data_mixed.append(mixed)
178 return data_mixed
179
180def mix_columns(data):
181 data_mixed = []
182 for i in range(4):
183 column = data[i*4 : (i+1)*4]
184 data_mixed += mix_column(column)
185 return data_mixed
186
187def shift_rows(data):
188 data_shifted = []
189 for column in range(4):
190 for row in range(4):
191 data_shifted.append( data[((column + row) & 0b11) * 4 + row] )
192 return data_shifted
193
194def inc(data):
195 data = data[:] # copy
196 for i in range(len(data)-1,-1,-1):
197 if data[i] == 255:
198 data[i] = 0
199 else:
200 data[i] = data[i] + 1
201 break
202 return data