450 lines
11 KiB
Python
450 lines
11 KiB
Python
"""
|
|
A pure python (slow) implementation of rijndael with a decent interface
|
|
|
|
To include -
|
|
|
|
from rijndael import rijndael
|
|
|
|
To do a key setup -
|
|
|
|
r = rijndael(key, block_size = 16)
|
|
|
|
key must be a string of length 16, 24, or 32
|
|
blocksize must be 16, 24, or 32. Default is 16
|
|
|
|
To use -
|
|
|
|
ciphertext = r.encrypt(plaintext)
|
|
plaintext = r.decrypt(ciphertext)
|
|
|
|
If any strings are of the wrong length a ValueError is thrown
|
|
"""
|
|
|
|
# ported from the Java reference code by Bram Cohen, April 2001
|
|
# this code is public domain, unless someone makes
|
|
# an intellectual property claim against the reference
|
|
# code, in which case it can be made public domain by
|
|
# deleting all the comments and renaming all the variables
|
|
|
|
import copy
|
|
import base64
|
|
|
|
shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]],
|
|
[[0, 0], [1, 5], [2, 4], [3, 3]],
|
|
[[0, 0], [1, 7], [3, 5], [4, 4]]]
|
|
|
|
# [keysize][block_size]
|
|
num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}}
|
|
|
|
A = [[1, 1, 1, 1, 1, 0, 0, 0],
|
|
[0, 1, 1, 1, 1, 1, 0, 0],
|
|
[0, 0, 1, 1, 1, 1, 1, 0],
|
|
[0, 0, 0, 1, 1, 1, 1, 1],
|
|
[1, 0, 0, 0, 1, 1, 1, 1],
|
|
[1, 1, 0, 0, 0, 1, 1, 1],
|
|
[1, 1, 1, 0, 0, 0, 1, 1],
|
|
[1, 1, 1, 1, 0, 0, 0, 1]]
|
|
|
|
# produce log and alog tables, needed for multiplying in the
|
|
# field GF(2^m) (generator = 3)
|
|
alog = [1]
|
|
for i in range(255):
|
|
j = (alog[-1] << 1) ^ alog[-1]
|
|
if j & 0x100 != 0:
|
|
j ^= 0x11B
|
|
alog.append(j)
|
|
|
|
log = [0] * 256
|
|
for i in range(1, 255):
|
|
log[alog[i]] = i
|
|
|
|
# multiply two elements of GF(2^m)
|
|
def mul(a, b):
|
|
if a == 0 or b == 0:
|
|
return 0
|
|
return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255]
|
|
|
|
# substitution box based on F^{-1}(x)
|
|
box = [[0] * 8 for i in range(256)]
|
|
box[1][7] = 1
|
|
for i in range(2, 256):
|
|
j = alog[255 - log[i]]
|
|
for t in range(8):
|
|
box[i][t] = (j >> (7 - t)) & 0x01
|
|
|
|
B = [0, 1, 1, 0, 0, 0, 1, 1]
|
|
|
|
# affine transform: box[i] <- B + A*box[i]
|
|
cox = [[0] * 8 for i in range(256)]
|
|
for i in range(256):
|
|
for t in range(8):
|
|
cox[i][t] = B[t]
|
|
for j in range(8):
|
|
cox[i][t] ^= A[t][j] * box[i][j]
|
|
|
|
# S-boxes and inverse S-boxes
|
|
S = [0] * 256
|
|
Si = [0] * 256
|
|
for i in range(256):
|
|
S[i] = cox[i][0] << 7
|
|
for t in range(1, 8):
|
|
S[i] ^= cox[i][t] << (7-t)
|
|
Si[S[i] & 0xFF] = i
|
|
|
|
# T-boxes
|
|
G = [[2, 1, 1, 3],
|
|
[3, 2, 1, 1],
|
|
[1, 3, 2, 1],
|
|
[1, 1, 3, 2]]
|
|
|
|
AA = [[0] * 8 for i in range(4)]
|
|
|
|
for i in range(4):
|
|
for j in range(4):
|
|
AA[i][j] = G[i][j]
|
|
AA[i][i+4] = 1
|
|
|
|
for i in range(4):
|
|
pivot = AA[i][i]
|
|
if pivot == 0:
|
|
t = i + 1
|
|
while AA[t][i] == 0 and t < 4:
|
|
t += 1
|
|
assert t != 4, 'G matrix must be invertible'
|
|
for j in range(8):
|
|
AA[i][j], AA[t][j] = AA[t][j], AA[i][j]
|
|
pivot = AA[i][i]
|
|
for j in range(8):
|
|
if AA[i][j] != 0:
|
|
AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]
|
|
for t in range(4):
|
|
if i != t:
|
|
for j in range(i+1, 8):
|
|
AA[t][j] ^= mul(AA[i][j], AA[t][i])
|
|
AA[t][i] = 0
|
|
|
|
iG = [[0] * 4 for i in range(4)]
|
|
|
|
for i in range(4):
|
|
for j in range(4):
|
|
iG[i][j] = AA[i][j + 4]
|
|
|
|
def mul4(a, bs):
|
|
if a == 0:
|
|
return 0
|
|
r = 0
|
|
for b in bs:
|
|
r <<= 8
|
|
if b != 0:
|
|
r |= mul(a, b)
|
|
return r
|
|
|
|
T1 = []
|
|
T2 = []
|
|
T3 = []
|
|
T4 = []
|
|
T5 = []
|
|
T6 = []
|
|
T7 = []
|
|
T8 = []
|
|
U1 = []
|
|
U2 = []
|
|
U3 = []
|
|
U4 = []
|
|
|
|
for t in range(256):
|
|
s = S[t]
|
|
T1.append(mul4(s, G[0]))
|
|
T2.append(mul4(s, G[1]))
|
|
T3.append(mul4(s, G[2]))
|
|
T4.append(mul4(s, G[3]))
|
|
|
|
s = Si[t]
|
|
T5.append(mul4(s, iG[0]))
|
|
T6.append(mul4(s, iG[1]))
|
|
T7.append(mul4(s, iG[2]))
|
|
T8.append(mul4(s, iG[3]))
|
|
|
|
U1.append(mul4(t, iG[0]))
|
|
U2.append(mul4(t, iG[1]))
|
|
U3.append(mul4(t, iG[2]))
|
|
U4.append(mul4(t, iG[3]))
|
|
|
|
# round constants
|
|
rcon = [1]
|
|
r = 1
|
|
for t in range(1, 30):
|
|
r = mul(2, r)
|
|
rcon.append(r)
|
|
|
|
del A
|
|
del AA
|
|
del pivot
|
|
del B
|
|
del G
|
|
del box
|
|
del log
|
|
del alog
|
|
del i
|
|
del j
|
|
del r
|
|
del s
|
|
del t
|
|
del mul
|
|
del mul4
|
|
del cox
|
|
del iG
|
|
|
|
class rijndael:
|
|
def __init__(self, key, block_size = 16):
|
|
if block_size != 16 and block_size != 24 and block_size != 32:
|
|
raise ValueError('Invalid block size: ' + str(block_size))
|
|
if len(key) != 16 and len(key) != 24 and len(key) != 32:
|
|
raise ValueError('Invalid key size: ' + str(len(key)))
|
|
self.block_size = block_size
|
|
|
|
ROUNDS = num_rounds[len(key)][block_size]
|
|
BC = block_size // 4
|
|
# encryption round keys
|
|
Ke = [[0] * BC for i in range(ROUNDS + 1)]
|
|
# decryption round keys
|
|
Kd = [[0] * BC for i in range(ROUNDS + 1)]
|
|
ROUND_KEY_COUNT = (ROUNDS + 1) * BC
|
|
KC = len(key) // 4
|
|
|
|
# copy user material bytes into temporary ints
|
|
tk = []
|
|
for i in range(0, KC):
|
|
tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) |
|
|
(ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3]))
|
|
|
|
# copy values into round key arrays
|
|
t = 0
|
|
j = 0
|
|
while j < KC and t < ROUND_KEY_COUNT:
|
|
Ke[t // BC][t % BC] = tk[j]
|
|
Kd[ROUNDS - (t // BC)][t % BC] = tk[j]
|
|
j += 1
|
|
t += 1
|
|
tt = 0
|
|
rconpointer = 0
|
|
while t < ROUND_KEY_COUNT:
|
|
# extrapolate using phi (the round key evolution function)
|
|
tt = tk[KC - 1]
|
|
tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \
|
|
(S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \
|
|
(S[ tt & 0xFF] & 0xFF) << 8 ^ \
|
|
(S[(tt >> 24) & 0xFF] & 0xFF) ^ \
|
|
(rcon[rconpointer] & 0xFF) << 24
|
|
rconpointer += 1
|
|
if KC != 8:
|
|
for i in range(1, KC):
|
|
tk[i] ^= tk[i-1]
|
|
else:
|
|
for i in range(1, KC // 2):
|
|
tk[i] ^= tk[i-1]
|
|
tt = tk[KC // 2 - 1]
|
|
tk[KC // 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \
|
|
(S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \
|
|
(S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \
|
|
(S[(tt >> 24) & 0xFF] & 0xFF) << 24
|
|
for i in range(KC // 2 + 1, KC):
|
|
tk[i] ^= tk[i-1]
|
|
# copy values into round key arrays
|
|
j = 0
|
|
while j < KC and t < ROUND_KEY_COUNT:
|
|
Ke[t // BC][t % BC] = tk[j]
|
|
Kd[ROUNDS - (t // BC)][t % BC] = tk[j]
|
|
j += 1
|
|
t += 1
|
|
# inverse MixColumn where needed
|
|
for r in range(1, ROUNDS):
|
|
for j in range(BC):
|
|
tt = Kd[r][j]
|
|
Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \
|
|
U2[(tt >> 16) & 0xFF] ^ \
|
|
U3[(tt >> 8) & 0xFF] ^ \
|
|
U4[ tt & 0xFF]
|
|
self.Ke = Ke
|
|
self.Kd = Kd
|
|
|
|
def encrypt(self, plaintext):
|
|
if len(plaintext) != self.block_size:
|
|
raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext)))
|
|
Ke = self.Ke
|
|
|
|
BC = self.block_size // 4
|
|
ROUNDS = len(Ke) - 1
|
|
if BC == 4:
|
|
SC = 0
|
|
elif BC == 6:
|
|
SC = 1
|
|
else:
|
|
SC = 2
|
|
s1 = shifts[SC][1][0]
|
|
s2 = shifts[SC][2][0]
|
|
s3 = shifts[SC][3][0]
|
|
a = [0] * BC
|
|
# temporary work array
|
|
t = []
|
|
# plaintext to ints + key
|
|
for i in range(BC):
|
|
t.append((ord(plaintext[i * 4 ]) << 24 |
|
|
ord(plaintext[i * 4 + 1]) << 16 |
|
|
ord(plaintext[i * 4 + 2]) << 8 |
|
|
ord(plaintext[i * 4 + 3]) ) ^ Ke[0][i])
|
|
# apply round transforms
|
|
for r in range(1, ROUNDS):
|
|
for i in range(BC):
|
|
a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^
|
|
T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^
|
|
T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^
|
|
T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i]
|
|
t = copy.copy(a)
|
|
# last round is special
|
|
result = []
|
|
for i in range(BC):
|
|
tt = Ke[ROUNDS][i]
|
|
result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
|
result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
|
result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
|
result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF)
|
|
return ''.join(map(chr, result))
|
|
|
|
def decrypt(self, ciphertext):
|
|
if len(ciphertext) != self.block_size:
|
|
raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(ciphertext)))
|
|
Kd = self.Kd
|
|
|
|
BC = self.block_size // 4
|
|
ROUNDS = len(Kd) - 1
|
|
if BC == 4:
|
|
SC = 0
|
|
elif BC == 6:
|
|
SC = 1
|
|
else:
|
|
SC = 2
|
|
s1 = shifts[SC][1][1]
|
|
s2 = shifts[SC][2][1]
|
|
s3 = shifts[SC][3][1]
|
|
a = [0] * BC
|
|
# temporary work array
|
|
t = [0] * BC
|
|
# ciphertext to ints + key
|
|
for i in range(BC):
|
|
t[i] = (ord(ciphertext[i * 4 ]) << 24 |
|
|
ord(ciphertext[i * 4 + 1]) << 16 |
|
|
ord(ciphertext[i * 4 + 2]) << 8 |
|
|
ord(ciphertext[i * 4 + 3]) ) ^ Kd[0][i]
|
|
# apply round transforms
|
|
for r in range(1, ROUNDS):
|
|
for i in range(BC):
|
|
a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^
|
|
T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^
|
|
T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^
|
|
T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i]
|
|
t = copy.copy(a)
|
|
# last round is special
|
|
result = []
|
|
for i in range(BC):
|
|
tt = Kd[ROUNDS][i]
|
|
result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
|
|
result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
|
|
result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
|
|
result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF)
|
|
return ''.join(map(chr, result))
|
|
|
|
def encrypt(key, block):
|
|
return rijndael(key, len(block)).encrypt(block)
|
|
|
|
def decrypt(key, block):
|
|
return rijndael(key, len(block)).decrypt(block)
|
|
|
|
|
|
class zeropad:
|
|
def __init__(self, block_size):
|
|
assert 0 < block_size < 256
|
|
self.block_size = block_size
|
|
|
|
def pad(self, pt):
|
|
ptlen = len(pt)
|
|
padsize = self.block_size - ((ptlen + self.block_size - 1) % self.block_size + 1)
|
|
return pt + "\0" * padsize
|
|
|
|
def unpad(self, ppt):
|
|
assert len(ppt) % self.block_size == 0
|
|
offset = len(ppt)
|
|
if offset == 0:
|
|
return ''
|
|
end = offset - self.block_size + 1
|
|
while offset > end:
|
|
offset -= 1
|
|
if ppt[offset] != "\0":
|
|
return ppt[:offset + 1]
|
|
assert False
|
|
|
|
class cbc:
|
|
def __init__(self, padding, cipher, iv):
|
|
assert padding.block_size == cipher.block_size
|
|
assert len(iv) == cipher.block_size
|
|
self.padding = padding
|
|
self.cipher = cipher
|
|
self.iv = iv
|
|
|
|
def encrypt(self, pt):
|
|
ppt = self.padding.pad(pt)
|
|
offset = 0
|
|
ct = ''
|
|
v = self.iv
|
|
while offset < len(ppt):
|
|
block = ppt[offset:offset + self.cipher.block_size]
|
|
block = self.xorblock(block, v)
|
|
block = self.cipher.encrypt(block)
|
|
ct += block
|
|
offset += self.cipher.block_size
|
|
v = block
|
|
return ct
|
|
|
|
def decrypt(self, ct):
|
|
assert len(ct) % self.cipher.block_size == 0
|
|
ppt = ''
|
|
offset = 0
|
|
v = self.iv
|
|
while offset < len(ct):
|
|
block = ct[offset:offset + self.cipher.block_size]
|
|
decrypted = self.cipher.decrypt(block)
|
|
ppt += self.xorblock(decrypted, v)
|
|
offset += self.cipher.block_size
|
|
v = block
|
|
pt = self.padding.unpad(ppt)
|
|
return pt
|
|
|
|
def xorblock(self, b1, b2):
|
|
# sorry, not very Pythonesk
|
|
i = 0
|
|
r = ''
|
|
while i < self.cipher.block_size:
|
|
r += chr(ord(b1[i]) ^ ord(b2[i]))
|
|
i += 1
|
|
return r
|
|
|
|
|
|
|
|
def decryptRinjdael(key, iv, data, areBase64 = False):
|
|
"""
|
|
Where the magic happens
|
|
|
|
key -- AES key (string)
|
|
IV -- IV thing (string)
|
|
data -- data to decrypt (string)
|
|
areBase64 -- if True, iv and data are passed in base64
|
|
"""
|
|
if areBase64:
|
|
iv = base64.b64decode(iv).decode("latin_1")
|
|
data = base64.b64decode(data).decode("latin_1")
|
|
|
|
r = rijndael(key, 32)
|
|
p = zeropad(32)
|
|
c = cbc(p, r, iv)
|
|
return str(c.decrypt(data))
|