From bf5649cfbe32597a854e573c811dc07d04e4fee6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20M=C3=B6ller?= <nisse@lysator.liu.se>
Date: Mon, 15 Apr 2013 16:02:23 +0200
Subject: [PATCH] umac reference code, for generation of test vectors.

---
 misc/umac/Makefile    |   4 +
 misc/umac/mkvectors   |  67 ++++++++
 misc/umac/repeat.py   |  19 +++
 misc/umac/rijndael.py | 376 ++++++++++++++++++++++++++++++++++++++++++
 misc/umac/umac.py     | 152 +++++++++++++++++
 5 files changed, 618 insertions(+)
 create mode 100644 misc/umac/Makefile
 create mode 100755 misc/umac/mkvectors
 create mode 100644 misc/umac/repeat.py
 create mode 100644 misc/umac/rijndael.py
 create mode 100644 misc/umac/umac.py

diff --git a/misc/umac/Makefile b/misc/umac/Makefile
new file mode 100644
index 00000000..8a932c2d
--- /dev/null
+++ b/misc/umac/Makefile
@@ -0,0 +1,4 @@
+all: vectors.out
+
+vectors.out: mkvectors umac.py rijndael.py
+	./mkvectors > vectors.out
diff --git a/misc/umac/mkvectors b/misc/umac/mkvectors
new file mode 100755
index 00000000..dc211ab2
--- /dev/null
+++ b/misc/umac/mkvectors
@@ -0,0 +1,67 @@
+#! /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
diff --git a/misc/umac/repeat.py b/misc/umac/repeat.py
new file mode 100644
index 00000000..0bcf52ef
--- /dev/null
+++ b/misc/umac/repeat.py
@@ -0,0 +1,19 @@
+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])
+
+
diff --git a/misc/umac/rijndael.py b/misc/umac/rijndael.py
new file mode 100644
index 00000000..fa025c6f
--- /dev/null
+++ b/misc/umac/rijndael.py
@@ -0,0 +1,376 @@
+"""
+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)
diff --git a/misc/umac/umac.py b/misc/umac/umac.py
new file mode 100644
index 00000000..a9383f13
--- /dev/null
+++ b/misc/umac/umac.py
@@ -0,0 +1,152 @@
+# -*- 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)
+		# Generate pad
+		mask = [None, 3, 1, 0, 0]
+		nlen = len(nonce)
+		old = ord(nonce[nlen-1])
+		idx = old & mask[self.iters]
+		pt = '%s%s%s' % (nonce[0:nlen-1], chr(old - idx), '\x00' * (16-nlen))
+		pad = struct.unpack('>4I', self.pdfCipher.encrypt(pt))
+		result = [ hex(self.L3Out[i] ^ pad[self.iters*idx+i]).rstrip("L").lstrip("0x").zfill(8) for i in xrange(self.iters) ]
+		self.L1Out = [ list() for i in xrange(self.iters) ] # A sequence of empty lists
+		self.L3Out = list()
+		return result
+
+u = umac('abcdefghijklmnop', taglen)
+
+last_block = sys.stdin.read(1024)
+if len(last_block) == 1024:
+	while True:
+		block = sys.stdin.read(1024)
+		if len(block) == 0:
+			break;
+		u.umacUpdate(last_block)
+		last_block = block;
+
+tag = u.umacFinal(last_block, 8*len(last_block), nonce)
+
+print ''.join(tag)
-- 
GitLab