Commit bf5649cf authored by Niels Möller's avatar Niels Möller

umac reference code, for generation of test vectors.

parent 8a4c4bb8
all: vectors.out
vectors.out: mkvectors umac.py rijndael.py
./mkvectors > vectors.out
#! /bin/bash
set -e
vector () {
nonce="$1"
length="$2"
data="$3"
echo "nonce:" $nonce
echo "msg length:" $length
echo "data (repeated):" $data
for tag_len in 32 64 96 128 ; do
tag=`python repeat.py "$data" "$length" | python umac.py "$tag_len" "$nonce"`
echo "tag$tag_len:" $tag
done
echo
}
NONCE=bcdefghi
# RFC 4418 test vectors
vector $NONCE 0 ""
vector $NONCE 3 "a"
vector $NONCE 1024 "a"
vector $NONCE 32768 "a"
vector $NONCE 1048576 "a"
vector $NONCE 33554432 "a"
vector $NONCE 3 "abc"
vector $nonce 1500 "abc"
DATA=def
NONCE=bcdefghijklmnopq
vector ${NONCE:0:1} 0 $DATA
vector ${NONCE:0:2} 1 $DATA
vector ${NONCE:0:3} 2 $DATA
vector ${NONCE:0:4} 3 $DATA
vector ${NONCE:0:5} 4 $DATA
vector ${NONCE:0:6} 1020 $DATA
vector ${NONCE:0:7} 1021 $DATA
vector ${NONCE:0:8} 1022 $DATA
vector ${NONCE:0:9} 1023 $DATA
vector ${NONCE:0:10} 1024 $DATA
vector ${NONCE:0:11} 1025 $DATA
vector ${NONCE:0:12} 1026 $DATA
vector ${NONCE:0:13} 1027 $DATA
vector ${NONCE:0:14} 2046 $DATA
vector ${NONCE:0:15} 2047 $DATA
vector ${NONCE:0:16} 2048 $DATA
vector ${NONCE:0:1} 2049 $DATA
vector ${NONCE:0:2} 2050 $DATA
vector ${NONCE:0:3} 16777212 $DATA
vector ${NONCE:0:4} 16777213 $DATA
vector ${NONCE:0:5} 16777214 $DATA
vector ${NONCE:0:6} 16777215 $DATA
vector ${NONCE:0:7} 16777216 $DATA
vector ${NONCE:0:8} 16777217 $DATA
vector ${NONCE:0:9} 16778239 $DATA
vector ${NONCE:0:10} 16778240 $DATA
vector ${NONCE:0:11} 16778241 $DATA
vector ${NONCE:0:12} 16778242 $DATA
vector ${NONCE:0:13} 16778243 $DATA
vector ${NONCE:0:14} 16778244 $DATA
import sys
if len(sys.argv) < 3:
sys.stderr.write('Usage: repeat [string] [length]\n')
sys.exit(1)
s = sys.argv[1]
length = int(sys.argv[2])
if length > 0:
if length > 1024:
while len(s) < 1024:
s = s + s;
while length > len(s):
sys.stdout.write(s)
length -= len(s)
sys.stdout.write(s[:length])
"""
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 string
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(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 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)
# -*- coding: utf-8 -*-
# Reference implementation from
# http://fastcrypto.org/umac/2004/src/rijndael.py, hacked a bit by
# Nikos Mavrogiannopoulos and Niels Möller to accept command line
# arguments.
import rijndael
import struct
import fileinput
import sys
if len(sys.argv) < 3:
sys.stderr.write('Usage: umac [taglen] [nonce]\n')
sys.exit(1)
taglen = int(sys.argv[1])
nonce = sys.argv[2]
"""
*** Experimental Python code for verifying test-vectors.
*** Veriosn 0.01 (16 March 2006) - Hereby placed in public domain.
*** Use at your own risk. No warranties, implied or otherwise.
- Update only works on 1024 byte blocks. Call repeatedly for longer.
- Final only works on final blocks of length 1...8192 bits.
- Hash of empty-string is done via single call to final.
"""
# Constants
MP64 = 0x01ffffff01ffffffL # Polynomial key masks
MP128 = 0x01ffffff01ffffff01ffffff01ffffffL
M32 = 0xffffffffL # Bit masks
M64 = 0xffffffffffffffffL
P36 = 0xffffffffbL # Prime numbers
P64 = 0xffffffffffffffc5L
P128 = 0xffffffffffffffffffffffffffffff61L
T64 = 0xffffffff00000000L # Polynomial test values
T128 = 0xffffffff000000000000000000000000L
def nh(key,data,bitlength):
a = 0
for i in xrange(len(data)//8):
for j in xrange(4):
a += (((data[8*i+j ] + key[8*i+j ]) & M32) *
((data[8*i+j+4] + key[8*i+j+4]) & M32))
return (a+bitlength) & M64 # mod 2^^64
class umac:
def __init__(self, umacKey, tagLength = 64):
self.taglen = tagLength/8
self.iters = iters = max(1, min(4,tagLength//32))
# setup keys
def kdf(kdfCipher, index, numbytes):
ct = [ kdfCipher.encrypt('%s%s%s%s' % ('\x00' * 7, chr(index), '\x00' * 7, chr(i+1))) for i in xrange((numbytes+15)//16) ]
return (''.join(ct))[ : numbytes]
kdfCipher = rijndael.rijndael(umacKey)
self.pdfCipher = rijndael.rijndael(kdf(kdfCipher,0,len(umacKey)))
# L1Key is a sequence of tuples, each (32-bit * 256)
L1Key = kdf(kdfCipher, 1, 1024 + (iters - 1) * 16)
self.L1Key = [ struct.unpack('>256I', L1Key[16*i:16*i+1024]) for i in xrange(iters) ]
# L2Key is a sequence of tuples, each (64-bit, 128-bit)
L2Key = kdf(kdfCipher, 2, iters * 24)
L2Key = [ struct.unpack('>3Q', L2Key[24*i:24*(i+1)]) for i in xrange(iters) ]
self.L2Key = [ (L2Key[i][0] & MP64, ((L2Key[i][1] << 64) + L2Key[i][2]) & MP128) for i in xrange(iters) ]
# L3Key is a sequence of tuples, each ( [64-bit * 8], 32-bit )
tmp1 = kdf(kdfCipher, 3, iters * 64)
tmp1 = [ struct.unpack('>8Q', tmp1[64*i:64*(i+1)]) for i in xrange(iters) ]
tmp1 = [ map(lambda x : x % (2**36 - 5), i) for i in tmp1 ]
tmp2 = kdf(kdfCipher, 4, iters * 4)
tmp2 = struct.unpack('>%sI' % str(iters), tmp2)
self.L3Key = zip(tmp1, tmp2)
# Setup empty lists to accumulate L1Hash outputs
self.L1Out = [ list() for i in xrange(iters) ] # A sequence of empty lists
self.L3Out = list()
def uhashUpdate(self, inString):
data = struct.unpack('<256I', inString) # To big-endian, 256 * 32-bits
for i in xrange(self.iters):
self.L1Out[i].append(nh(self.L1Key[i], data, 8192))
def uhashFinal(self, inString, bitlength):
# Pad to 32-byte multiple and unpack to tuple of 32-bit values
if len(inString) == 0: toAppend = 32
else: toAppend = (32 - (len(inString) % 32)) % 32
data = '%s%s' % (inString, '\x00' * toAppend)
data = struct.unpack('<%sI' % str(len(data)//4), data)
# Do three-level hash, iter times
for i in xrange(self.iters):
# L1 Hash
self.L1Out[i].append(nh(self.L1Key[i], data, bitlength))
# L2 Hash
if len(self.L1Out[0]) == 1:
L2Out = self.L1Out[i][0]
else:
loPoly = self.L1Out[i][ : 2**14]
hiPoly = self.L1Out[i][2**14 : ]
for j in xrange(len(loPoly)-1,-1,-1):
if loPoly[j] >= T64:
loPoly[j] = [P64-1, loPoly[j] - 59]
L2Out = reduce(lambda x, y : (x * self.L2Key[i][0] + y) % P64, loPoly, 1)
if (len(hiPoly) > 0):
hiPoly.append(0x8000000000000000L)
if (len(hiPoly) % 2 == 1):
hiPoly.append(0)
hiPoly = [ (hiPoly[j] << 64) + hiPoly[j+1] for j in xrange(0,len(hiPoly),2) ]
hiPoly.insert(0,L2Out)
for j in xrange(len(hiPoly)-1,-1,-1):
if hiPoly[j] >= T128:
hiPoly[j] = [P128-1, hiPoly[j] - 159]
L2Out = reduce(lambda x, y : (x * self.L2Key[i][1] + y) % P128, hiPoly, 1)
#L3 Hash
a,res = L2Out,0;
for j in xrange(7,-1,-1):
res += (a & 0xffff) * self.L3Key[i][0][j]
a >>= 16
self.L3Out.append(((res % P36) & M32) ^ self.L3Key[i][1])
print "L1Out:", self.L1Out
print "L2Out:", L2Out
print "L3Out:", self.L3Out
def umacUpdate(self, inString):
self.uhashUpdate(inString)
def umacFinal(self, inString, bitlength, nonce):
self.uhashFinal(inString, bitlength)
</