"""␊ |
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, bram@gawth.com, 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␊ |
# Source: https://raw.githubusercontent.com/andris9/squirrelpay/master/banklink/tlslite/utils/rijndael.py␊ |
␊ |
import copy␊ |
import string␊ |
␊ |
␊ |
␊ |
#-----------------------␊ |
#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN␊ |
#2.4.....␊ |
import os␊ |
if os.name != "java":␊ |
import exceptions␊ |
if hasattr(exceptions, "FutureWarning"):␊ |
import warnings␊ |
warnings.filterwarnings("ignore", category=FutureWarning, append=1)␊ |
#-----------------------␊ |
␊ |
␊ |
␊ |
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 xrange(255):␊ |
j = (alog[-1] << 1) ^ alog[-1]␊ |
if j & 0x100 != 0:␊ |
j ^= 0x11B␊ |
alog.append(j)␊ |
␊ |
log = [0] * 256␊ |
for i in xrange(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 xrange(256)]␊ |
box[1][7] = 1␊ |
for i in xrange(2, 256):␊ |
j = alog[255 - log[i]]␊ |
for t in xrange(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 xrange(256)]␊ |
for i in xrange(256):␊ |
for t in xrange(8):␊ |
cox[i][t] = B[t]␊ |
for j in xrange(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 xrange(256):␊ |
S[i] = cox[i][0] << 7␊ |
for t in xrange(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 xrange(4)]␊ |
␊ |
for i in xrange(4):␊ |
for j in xrange(4):␊ |
AA[i][j] = G[i][j]␊ |
AA[i][i+4] = 1␊ |
␊ |
for i in xrange(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 xrange(8):␊ |
AA[i][j], AA[t][j] = AA[t][j], AA[i][j]␊ |
pivot = AA[i][i]␊ |
for j in xrange(8):␊ |
if AA[i][j] != 0:␊ |
AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255]␊ |
for t in xrange(4):␊ |
if i != t:␊ |
for j in xrange(i+1, 8):␊ |
AA[t][j] ^= mul(AA[i][j], AA[t][i])␊ |
AA[t][i] = 0␊ |
␊ |
iG = [[0] * 4 for i in xrange(4)]␊ |
␊ |
for i in xrange(4):␊ |
for j in xrange(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 = r | mul(a, b)␊ |
return r␊ |
␊ |
T1 = []␊ |
T2 = []␊ |
T3 = []␊ |
T4 = []␊ |
T5 = []␊ |
T6 = []␊ |
T7 = []␊ |
T8 = []␊ |
U1 = []␊ |
U2 = []␊ |
U3 = []␊ |
U4 = []␊ |
␊ |
for t in xrange(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 xrange(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 xrange(ROUNDS + 1)]␊ |
# decryption round keys␊ |
Kd = [[0] * BC for i in xrange(ROUNDS + 1)]␊ |
ROUND_KEY_COUNT = (ROUNDS + 1) * BC␊ |
KC = len(key) / 4␊ |
␊ |
# copy user material bytes into temporary ints␊ |
tk = []␊ |
for i in xrange(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 xrange(1, KC):␊ |
tk[i] ^= tk[i-1]␊ |
else:␊ |
for i in xrange(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 xrange(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 xrange(1, ROUNDS):␊ |
for j in xrange(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 xrange(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 xrange(1, ROUNDS):␊ |
for i in xrange(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 xrange(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 string.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(plaintext)))␊ |
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 xrange(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 xrange(1, ROUNDS):␊ |
for i in xrange(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 xrange(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 string.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)␊ |
␊ |
def test():␊ |
def t(kl, bl):␊ |
b = 'b' * bl␊ |
r = rijndael('a' * kl, bl)␊ |
assert r.decrypt(r.encrypt(b)) == b␊ |
t(16, 16)␊ |
t(16, 24)␊ |
t(16, 32)␊ |
t(24, 16)␊ |
t(24, 24)␊ |
t(24, 32)␊ |
t(32, 16)␊ |
t(32, 24)␊ |
t(32, 32)␊ |