diff options
author | Niels Möller <nisse@lysator.liu.se> | 2013-04-15 16:02:23 +0200 |
---|---|---|
committer | Niels Möller <nisse@lysator.liu.se> | 2013-04-15 16:02:23 +0200 |
commit | bf5649cfbe32597a854e573c811dc07d04e4fee6 (patch) | |
tree | 89c4c3da061f3f3f1b0733c634e793cbfc82e9b7 /misc | |
parent | 8a4c4bb80716572457fae72dbe3bc6c3af7edc66 (diff) | |
download | nettle-bf5649cfbe32597a854e573c811dc07d04e4fee6.tar.gz |
umac reference code, for generation of test vectors.
Diffstat (limited to 'misc')
-rw-r--r-- | misc/umac/Makefile | 4 | ||||
-rwxr-xr-x | misc/umac/mkvectors | 67 | ||||
-rw-r--r-- | misc/umac/repeat.py | 19 | ||||
-rw-r--r-- | misc/umac/rijndael.py | 376 | ||||
-rw-r--r-- | misc/umac/umac.py | 152 |
5 files changed, 618 insertions, 0 deletions
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) |