chiark / gitweb /
Initial version.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 29 Jun 2017 10:11:01 +0000 (11:11 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 29 Jun 2017 10:11:01 +0000 (11:11 +0100)
Makefile [new file with mode: 0644]
ocbgen [new file with mode: 0755]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..084eacb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,198 @@
+### -*-makefile-*-
+###
+### Script to construct OCB test vectors
+###
+### (c) 2017 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+all::
+
+misc128_K0              = 000102030405060708090a0b0c0d0e0f
+misc128_K1              = 0f0e0d0c0b0a09080706050403020100
+
+misc192_K0              = 000102030405060708090a0b0c0d0e0f1011121314151617
+misc192_K1              = 17161514131211100f0e0d0c0b0a09080706050403020100
+
+misc256_K0              = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
+misc256_K1              = 1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
+
+b64_N0                  = 554433221100
+b96_N0                  = 887766554433221100
+b128_N0                         = bbaa99887766554433221100
+b192_N0                         = 2110ffeeddccbbaa99887766554433221100
+b256_N0                         = 8776655443322110ffeeddccbbaa99887766554433221100
+
+BLKC                   += des3
+des3_PRETTY             = 3DES
+des3_NAME               = des3
+des3_BLKSZ              = 64
+des3_K0                         = 010102020404070708080b0b0d0d0e0e1010131315151616
+des3_K1                         = fefedcdcbaba98987676545432321010fedcba9876543210
+des3_KSZS               = 64 128 192
+
+BLKC                   += aes
+aes_PRETTY              = AES
+aes_NAME                = rijndael
+aes_BLKSZ               = 128
+aes_K0                  = $(misc128_K0)
+aes_K1                  = $(misc128_K1)
+aes_KSZS                = 128 192 256
+
+BLKC                   += rijndael192
+rijndael192_PRETTY      = Rijndael192
+rijndael192_NAME        = rijndael192
+rijndael192_BLKSZ       = 192
+rijndael192_K0          = $(misc192_K0)
+rijndael192_K1          = $(misc192_K1)
+rijndael192_KSZS        = $(aes_KSZS)
+
+BLKC                   += rijndael256
+rijndael256_PRETTY      = Rijndael256
+rijndael256_NAME        = rijndael256
+rijndael256_BLKSZ       = 256
+rijndael256_K0          = $(misc256_K0)
+rijndael256_K1          = $(misc256_K1)
+rijndael256_KSZS        = $(aes_KSZS)
+
+define def-lraes
+LRAES                  += $1
+BLKC                   += lraes$1
+lraes$1_PRETTY          = LRAES$1
+lraes$1_NAME            = lraes$1
+lraes$1_BLKSZ           = $1
+lraes$1_K0              = $(misc$2_K0)
+lraes$1_K1              = $(misc$2_K1)
+lraes$1_KSZS            = $(aes_KSZS)
+endef
+
+$(eval $(call def-lraes,64,128))
+$(eval $(call def-lraes,96,128))
+$(eval $(call def-lraes,128,128))
+$(eval $(call def-lraes,192,192))
+$(eval $(call def-lraes,256,256))
+
+BLKSZS                  = $(sort $(foreach c,$(BLKC),$($c_BLKSZ)))
+
+auto.mk: Makefile
+       set -e; exec >$@.new; \
+       echo '### -*-makefile-*- AUTOMATICALLY GENERATED: DO NOT EDIT'; \
+       $(foreach b,$(BLKSZS),b$b_N0=$(b$b_N0); ) \
+       \
+       for blksz in $(BLKSZS); do \
+         echo; \
+         hm="0,0"; \
+         for i in 1 2 3 4 5; do \
+           w=$$(( $$i*$$blksz/16 )); \
+           hm="$$hm $$w,$$w $$w,0 0,$$w"; \
+         done; \
+         echo "b$${blksz}_HMSZS = $$hm"; \
+         echo "b$${blksz}_HMSZV = $$w,$$w"; \
+         \
+         printf "b$${blksz}_TSZ0 = %d\n" $$(( $$blksz )); \
+         printf "b$${blksz}_TSZ1 = %d\n" $$(( $$blksz*3/4 )); \
+         printf "b$${blksz}_TSZ2 = %d\n" $$(( $$blksz/2 )); \
+         \
+         eval n0=\$$b$${blksz}_N0; \
+         N0=$$(echo $$n0 | tr a-f A-F); \
+         n1=$$(echo "16o 16i $$N0 D + p" | dc | tr A-F a-f); \
+         nsz=$$(( 4*($$(echo $$n0 | wc -c) - 1) )); \
+         echo "b$${blksz}_N1 = $$n1"; \
+         echo "b$${blksz}_NSZ0 = $$nsz"; \
+       done; \
+       \
+       mv $@.new $@
+include auto.mk
+CLEAN                  += auto.mk
+
+blkc                    = $(word 1,$(subst -, ,$(subst ., ,$1)))
+blkc-blksz              = $($(call blkc,$1)_BLKSZ)
+blkc-pretty             = $($(call blkc,$1)_PRETTY)
+blkc-name               = $($(call blkc,$1)_NAME)
+blkc-k0                         = $($(call blkc,$1)_K0)
+blkc-k1                         = $($(call blkc,$1)_K1)
+blkc-kszs               = $($(call blkc,$1)_KSZS)
+
+blkc-hmszs              = $(b$(call blkc-blksz,$1)_HMSZS)
+blkc-hmszv              = $(b$(call blkc-blksz,$1)_HMSZV)
+blkc-tsz0               = $(b$(call blkc-blksz,$1)_TSZ0)
+blkc-tsz1               = $(b$(call blkc-blksz,$1)_TSZ1)
+blkc-tsz2               = $(b$(call blkc-blksz,$1)_TSZ2)
+blkc-n0                         = $(b$(call blkc-blksz,$1)_N0)
+blkc-n1                         = $(b$(call blkc-blksz,$1)_N1)
+blkc-nsz0               = $(b$(call blkc-blksz,$1)_NSZ0)
+
+TARGETS                        += $(OCBKAT0)
+OCBKAT0                         = $(foreach b,$(BLKC), ocb3-$b-t$(b$($b_BLKSZ)_TSZ0)-n$(b$($b_BLKSZ)_NSZ0).kat)
+$(OCBKAT0): ocb3-%: ocbgen Makefile
+       ./ocbgen >$@.new ocb3 $(call blkc-name,$*) kat \
+               $(call blkc-k0,$*) $(call blkc-n0,$*)+ \
+               $$(( $(call blkc-tsz0,$*)/8 )) $(call blkc-hmszs,$*)
+                       mv $@.new $@
+
+TARGETS                        += $(OCBKAT1)
+OCBKAT1                         = $(foreach b,$(BLKC), ocb3-$b-t$(b$($b_BLKSZ)_TSZ1)-n$(b$($b_BLKSZ)_NSZ0).kat)
+$(OCBKAT1): ocb3-%: ocbgen Makefile
+       ./ocbgen >$@.new ocb3 $(call blkc-name,$*) kat \
+               $(call blkc-k1,$*) $(call blkc-n0,$*)+ \
+               $$(( $(call blkc-tsz1,$*)/8 )) $(call blkc-hmszs,$*)
+                       mv $@.new $@
+
+TARGETS                        += $(OCBVERBOSE)
+OCBVERBOSE              = $(foreach b,$(BLKC), ocb3-$b-t$(b$($b_BLKSZ)_TSZ0)-n$(b$($b_BLKSZ)_NSZ0).verbose)
+$(OCBVERBOSE): ocb3-%: ocbgen Makefile
+       ./ocbgen >$@.new -v ocb3 $(call blkc-name,$*) kat \
+               $(call blkc-k0,$*) $(call blkc-n1,$*) \
+               $$(( $(call blkc-tsz0,$*)/8 )) $(call blkc-hmszv,$*)
+                       mv $@.new $@
+
+TARGETS                        += $(OCBMCT)
+OCBMCT                  = $(foreach b,$(BLKC), ocb3-$b-n$(b$($b_BLKSZ)_NSZ0).mct)
+$(OCBMCT): ocb3-%: ocbgen Makefile
+       set -e; \
+       for t in \
+               $(call blkc-tsz0,$*) \
+               $(call blkc-tsz1,$*) \
+               $(call blkc-tsz2,$*); \
+       do \
+         for k in $(call blkc-kszs,$*); do \
+           printf "OCB3-%s-%d-TAGLEN%d: " \
+               $(call blkc-pretty,$*) $$k $$t; \
+           ./ocbgen ocb3 $(call blkc-name,$*) mct \
+               $$(( $$k/8 )) $$(( $(call blkc-nsz0,$*)/8 )) $$(( $$t/8 )); \
+         done; \
+       done >$@.new; \
+       mv $@.new $@
+
+lraes64_M0              = 0011223344556677
+lraes96_M0              = 00112233445566778899aabb
+lraes128_M0             = 00112233445566778899aabbccddeeff
+lraes192_M0             = 00112233445566778899aabbccddeeff0112233445566778
+lraes256_M0             = 00112233445566778899aabbccddeeff0112233445566778899aabbccddeeff0
+
+TARGETS                        += $(LRAESVERBOSE)
+LRAESVERBOSE            = $(foreach k,$(LRAES), lraes$k.verbose)
+$(LRAESVERBOSE): lraes%.verbose: ocbgen Makefile
+       ./ocbgen >$@.new ocb3 rijndael lraes $$(( $*/8 )) \
+               $(lraes$*_K0) $(lraes$*_M0) && \
+               mv $@.new $@
+
+all:: $(TARGETS)
+
+CLEAN                  += $(TARGETS)
+clean::; rm -f $(CLEAN)
diff --git a/ocbgen b/ocbgen
new file mode 100755 (executable)
index 0000000..acdd997
--- /dev/null
+++ b/ocbgen
@@ -0,0 +1,593 @@
+#! /usr/bin/python
+### -*-python-*-
+###
+### Generalization of OCB mode for other block sizes
+###
+### (c) 2017 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from sys import argv, stderr
+from struct import pack
+from itertools import izip
+import catacomb as C
+
+R = C.FibRand(0)
+
+###--------------------------------------------------------------------------
+### Utilities.
+
+def combs(things, k):
+  ii = range(k)
+  n = len(things)
+  while True:
+    yield [things[i] for i in ii]
+    for j in xrange(k):
+      if j == k - 1: lim = n
+      else: lim = ii[j + 1]
+      i = ii[j] + 1
+      if i < lim:
+        ii[j] = i
+        break
+      ii[j] = j
+    else:
+      return
+
+POLYMAP = {}
+
+def poly(nbits):
+  try: return POLYMAP[nbits]
+  except KeyError: pass
+  base = C.GF(0).setbit(nbits).setbit(0)
+  for k in xrange(1, nbits, 2):
+    for cc in combs(range(1, nbits), k):
+      p = base + sum(C.GF(0).setbit(c) for c in cc)
+      if p.irreduciblep(): POLYMAP[nbits] = p; return p
+  raise ValueError, nbits
+
+def prim(nbits):
+  ## No fancy way to do this: I'd need a much cleverer factoring algorithm
+  ## than I have in my pockets.
+  if nbits == 64: cc = [64, 4, 3, 1, 0]
+  elif nbits == 96: cc = [96, 10, 9, 6, 0]
+  elif nbits == 128: cc = [128, 7, 2, 1, 0]
+  elif nbits == 192: cc = [192, 15, 11, 5, 0]
+  elif nbits == 256: cc = [256, 10, 5, 2, 0]
+  else: raise ValueError, 'no field for %d bits' % nbits
+  p = C.GF(0)
+  for c in cc: p = p.setbit(c)
+  return p
+
+def Z(n):
+  return C.ByteString.zero(n)
+
+def mul_blk_gf(m, x, p): return ((C.GF.loadb(m)*x)%p).storeb((p.nbits + 6)/8)
+
+def with_lastp(it):
+  it = iter(it)
+  try: j = next(it)
+  except StopIteration: raise ValueError, 'empty iter'
+  lastp = False
+  while not lastp:
+    i = j
+    try: j = next(it)
+    except StopIteration: lastp = True
+    yield i, lastp
+
+def safehex(x):
+  if len(x): return hex(x)
+  else: return '""'
+
+def keylens(ksz):
+  sel = []
+  if isinstance(ksz, C.KeySZSet): kk = ksz.set
+  elif isinstance(ksz, C.KeySZRange): kk = range(ksz.min, ksz.max, ksz.mod)
+  elif isinstance(ksz, C.KeySZAny): kk = range(64); sel = [0]
+  kk = list(kk); kk = kk[:]
+  n = len(kk)
+  while n and len(sel) < 4:
+    i = R.range(n)
+    n -= 1
+    kk[i], kk[n] = kk[n], kk[i]
+    sel.append(kk[n])
+  return sel
+
+def pad0star(m, w):
+  n = len(m)
+  if not n: r = w
+  else: r = (-len(m))%w
+  if r: m += Z(r)
+  return C.ByteString(m)
+
+def pad10star(m, w):
+  r = w - len(m)%w
+  if r: m += '\x80' + Z(r - 1)
+  return C.ByteString(m)
+
+def ntz(i):
+  j = 0
+  while (i&1) == 0: i >>= 1; j += 1
+  return j
+
+def blocks(x, w):
+  v, i, n = [], 0, len(x)
+  while n - i > w:
+    v.append(C.ByteString(x[i:i + w]))
+    i += w
+  return v, C.ByteString(x[i:])
+
+EMPTY = C.bytes('')
+
+def blocks0(x, w):
+  v, tl = blocks(x, w)
+  if len(tl) == w: v.append(tl); tl = EMPTY
+  return v, tl
+
+###--------------------------------------------------------------------------
+### Luby--Rackoff large-block ciphers.
+
+class LubyRackoffCipher (type):
+  def __new__(cls, bc, blksz):
+    assert blksz%2 == 0
+    assert blksz <= 2*bc.blksz
+    name = '%s-lr[%d]' % (bc.name, 8*blksz)
+    me = type(name, (LubyRackoffBase,), {})
+    me.name = name
+    me.blksz = blksz
+    me.keysz = bc.keysz
+    me.bc = bc
+    return me
+
+class LubyRackoffBase (object):
+  NR = 4 # for strong-PRP security
+  def __init__(me, k):
+    if LRVERBOSE: print 'K = %s' % hex(k)
+    bc, blksz = me.__class__.bc, me.__class__.blksz
+    E = bc(k)
+    me.f = []
+    ksz = len(k)
+    i = C.MP(0)
+    for j in xrange(me.NR):
+      b = C.WriteBuffer()
+      while b.size < ksz:
+        x = E.encrypt(i.storeb(bc.blksz))
+        b.put(x)
+        if LRVERBOSE: print 'E(K; [%d]) = %s' % (i, hex(x))
+        i += 1
+      kj = C.ByteString(C.ByteString(b)[0:ksz])
+      if LRVERBOSE: print 'K_%d = %s' % (j, hex(kj))
+      me.f.append(bc(kj))
+  def encrypt(me, m):
+    bc, blksz = me.__class__.bc, me.__class__.blksz
+    assert len(m) == blksz
+    l, r = C.ByteString(m[:blksz/2]), C.ByteString(m[blksz/2:])
+    if LRVERBOSE: print 'L_0, R_0 = %s, %s' % (hex(l), hex(r))
+    for j in xrange(me.NR):
+      l0 = pad0star(l, bc.blksz)
+      t = me.f[j].encrypt(l0)
+      l, r = r ^ t[:blksz/2], l
+      if LRVERBOSE:
+        print 'E(K_%d; L_%d || 0^*) = %s' % (j, j, hex(t))
+        print 'L_%d, R_%d = %s, %s' % (j + 1, j + 1, hex(l), hex(r))
+    return C.ByteString(r + l)
+  def decrypt(me, c):
+    bc, blksz = me.__class__.bc, me.__class__.blksz
+    assert len(c) == blksz
+    l, r = C.ByteString(c[:blksz/2]), C.ByteString(c[blksz/2:])
+    for j in xrange(me.NR - 1, -1, -1):
+      l0 = pad0star(l, bc.blksz)
+      t = me.f[j].encrypt(l0)
+      if LRVERBOSE:
+        print 'L_%d, R_%d = %s, %s' % (j + 1, j + 1, hex(l), hex(r))
+        print 'E(K_%d; L_%d || 0^*) = %s' % (j + 1, j + 1, hex(t))
+      l, r = r ^ t[:blksz/2], l
+    if LRVERBOSE: print 'L_0, R_0 = %s, %s' % (hex(l), hex(r))
+    return C.ByteString(r + l)
+
+LRAES = {}
+for i in [8, 12, 16, 24, 32]:
+  LRAES['lraes%d' % (8*i)] = LubyRackoffCipher(C.rijndael, i)
+
+###--------------------------------------------------------------------------
+### PMAC.
+
+def ocb_masks(E):
+  blksz = E.__class__.blksz
+  p = poly(8*blksz)
+  x = C.GF(2); xinv = p.modinv(x)
+  z = Z(blksz)
+  L = E.encrypt(z)
+  Lxinv = mul_blk_gf(L, xinv, p)
+  Lgamma = 66*[L]
+  for i in xrange(1, len(Lgamma)):
+    Lgamma[i] = mul_blk_gf(Lgamma[i - 1], x, p)
+  return Lgamma, Lxinv
+
+def dump_ocb(E):
+  Lgamma, Lxinv = ocb_masks(E)
+  print 'L x^-1 = %s' % hex(Lxinv)
+  for i, lg in enumerate(Lgamma):
+    print 'L x^%d = %s' % (i, hex(lg))
+
+def pmac1(E, m):
+  blksz = E.__class__.blksz
+  Lgamma, Lxinv = ocb_masks(E)
+  a = o = Z(blksz)
+  i = 1
+  v, tl = blocks(m, blksz)
+  for x in v:
+    b = ntz(i)
+    o ^= Lgamma[b]
+    a ^= E.encrypt(x ^ o)
+    if VERBOSE:
+      print 'Z[%d]: %d -> %s' % (i - 1, b, hex(o))
+      print 'A[%d]: %s' % (i - 1, hex(a))
+    i += 1
+  if len(tl) == blksz: a ^= tl ^ Lxinv
+  else: a ^= pad10star(tl, blksz)
+  return E.encrypt(a)
+
+def pmac2(E, m):
+  blksz = E.__class__.blksz
+  p = prim(8*blksz)
+  L = E.encrypt(Z(blksz))
+  o = mul_blk_gf(L, 10, p)
+  a = Z(blksz)
+  v, tl = blocks(m, blksz)
+  for x in v:
+    a ^= E.encrypt(x ^ o)
+    o = mul_blk_gf(o, 2, p)
+  if len(tl) == blksz: a ^= tl ^ mul_blk_gf(o, 3, p)
+  else: a ^= pad10star(tl, blksz) ^ mul_blk_gf(o, 5, p)
+  return E.encrypt(a)
+
+def ocb3_masks(E):
+  Lgamma, _ = ocb_masks(E)
+  Lstar = Lgamma[0]
+  Ldollar = Lgamma[1]
+  return Lstar, Ldollar, Lgamma[2:]
+
+def dump_ocb3(E):
+  Lstar, Ldollar, Lgamma = ocb3_masks(E)
+  print 'L_*       : %s' % hex(Lstar)
+  print 'L_$       : %s' % hex(Ldollar)
+  for i, lg in enumerate(Lgamma[:4]):
+    print 'L_%-8d: %s' % (i, hex(lg))
+
+def pmac3(E, m):
+  blksz = E.__class__.blksz
+  Lstar, Ldollar, Lgamma = ocb3_masks(E)
+  a = o = Z(blksz)
+  i = 1
+  v, tl = blocks0(m, blksz)
+  for x in v:
+    b = ntz(i)
+    o ^= Lgamma[b]
+    a ^= E.encrypt(x ^ o)
+    if VERBOSE:
+      print 'Offset\'_%-2d: %s' % (i, hex(o))
+      print 'AuthSum_%-2d: %s' % (i, hex(a))
+    i += 1
+  if tl:
+    o ^= Lstar
+    a ^= E.encrypt(pad10star(tl, blksz) ^ o)
+    if VERBOSE:
+      print 'Offset\'_* : %s' % hex(o)
+      print 'AuthSum_* : %s' % hex(a)
+  return a
+
+def pmac1_pub(E, m):
+  if VERBOSE: dump_ocb(E)
+  return pmac1(E, m),
+
+def pmac2_pub(E, m):
+  return pmac2(E, m),
+
+def pmac3_pub(E, m):
+  return pmac3(E, m),
+
+def pmacgen(bc):
+  return [(0,), (1,),
+          (3*bc.blksz,),
+          (3*bc.blksz - 5,)]
+
+###--------------------------------------------------------------------------
+### OCB.
+
+## For OCB2, it's important for security that n = log_x (x + 1) is large in
+## the field representations of GF(2^w) used -- in fact, we need more, that
+## i n (mod 2^w - 1) is large for i in {4, -3, -2, -1, 1, 2, 3, 4}.  The
+## original paper lists the values for 64 and 128, but we support other block
+## sizes, so here's the result of the (rather large, in some cases)
+## computation.
+##
+## Block size           log_x (x + 1)
+##
+##       64             9686038906114705801
+##       96             63214690573408919568138788065
+##      128             338793687469689340204974836150077311399
+##      192             161110085006042185925119981866940491651092686475226538785
+##      256             22928580326165511958494515843249267194111962539778797914076675796261938307298
+
+def ocb1(E, n, h, m, tsz = None):
+  ## This is OCB1.PMAC1 from Rogaway's `Authenticated-Encryption with
+  ## Associated-Data'.
+  blksz = E.__class__.blksz
+  if VERBOSE: dump_ocb(E)
+  Lgamma, Lxinv = ocb_masks(E)
+  if tsz is None: tsz = blksz
+  a = Z(blksz)
+  o = E.encrypt(n ^ Lgamma[0])
+  if VERBOSE: print 'R = %s' % hex(o)
+  i = 1
+  y = C.WriteBuffer()
+  v, tl = blocks(m, blksz)
+  for x in v:
+    b = ntz(i)
+    o ^= Lgamma[b]
+    a ^= x
+    if VERBOSE:
+      print 'Z[%d]: %d -> %s' % (i - 1, b, hex(o))
+      print 'A[%d]: %s' % (i - 1, hex(a))
+    y.put(E.encrypt(x ^ o) ^ o)
+    i += 1
+  b = ntz(i)
+  o ^= Lgamma[b]
+  n = len(tl)
+  if VERBOSE:
+    print 'Z[%d]: %d -> %s' % (i - 1, b, hex(o))
+    print 'LEN = %s' % hex(C.MP(8*n).storeb(blksz))
+  yfinal = E.encrypt(C.MP(8*n).storeb(blksz) ^ Lxinv ^ o)
+  cfinal = tl ^ yfinal[:n]
+  a ^= o ^ (tl + yfinal[n:])
+  y.put(cfinal)
+  t = E.encrypt(a)
+  if h: t ^= pmac1(E, h)
+  return C.ByteString(y), C.ByteString(t[:tsz])
+
+def ocb2(E, n, h, m, tsz = None):
+  blksz = E.__class__.blksz
+  if tsz is None: tsz = blksz
+  p = prim(8*blksz)
+  L = E.encrypt(n)
+  o = mul_blk_gf(L, 2, p)
+  a = Z(blksz)
+  v, tl = blocks(m, blksz)
+  y = C.WriteBuffer()
+  for x in v:
+    a ^= x
+    y.put(E.encrypt(x ^ o) ^ o)
+    o = mul_blk_gf(o, 2, p)
+  n = len(tl)
+  yfinal = E.encrypt(C.MP(8*n).storeb(blksz) ^ o)
+  cfinal = tl ^ yfinal[:n]
+  a ^= (tl + yfinal[n:]) ^ mul_blk_gf(o, 3, p)
+  y.put(cfinal)
+  t = E.encrypt(a)
+  if h: t ^= pmac2(E, h)
+  return C.ByteString(y), C.ByteString(t[:tsz])
+
+OCB3_STRETCH = { 8: (5, 25),
+                 12: (6, 33),
+                 16: (6, 8),
+                 24: (7, 40),
+                 32: (7, 120) }
+
+def ocb3(E, n, h, m, tsz = None):
+  blksz = E.__class__.blksz
+  if tsz is None: tsz = blksz
+  Lstar, Ldollar, Lgamma = ocb3_masks(E)
+  if VERBOSE: dump_ocb3(E)
+
+  ## Figure out how much we need to glue onto the nonce.  This ends up being
+  ## [t mod w]_v || 0^p || 1 || N, where w is the block size in bits, t is
+  ## the tag length in bits, v = floor(log_2(w - 1)) + 1, and p = w - l(N) -
+  ## v - 1.  But this is an annoying way to think about it because of the
+  ## byte misalignment.  Instead, think of it as a byte-aligned prefix
+  ## encoding the tag and an `is the nonce full-length' flag, followed by
+  ## optional padding, and then the nonce:
+  ##
+  ##    F || N                  if l(N) = w - f
+  ##    F || 0^p || 1 || N      otherwise
+  ##
+  ## where F is [t mod w]_v || 0^{f-v-1} || b; f = floor(log_2(w - 1)) + 2;
+  ## b is 1 if l(N) = w - f, or 0 otherwise; and p = w - f - l(N) - 1.
+  tszbits = C.MP(8*blksz - 1).nbits
+  fwd = tszbits/8 + 1
+  f = tsz << 3 + 8*fwd - tszbits
+
+  ## Form the augmented nonce.
+  nb = C.WriteBuffer()
+  nsz, nwd = len(n), blksz - fwd
+  if nsz == nwd: f |= 1
+  nb.put(C.MP(f).storeb(fwd))
+  if nsz < nwd: nb.zero(nwd - nsz - 1).putu8(1)
+  nb.put(n)
+  nn = C.ByteString(nb)
+  if VERBOSE: print 'N\'        : %s' % hex(nn)
+
+  ## Calculate the initial offset.
+  split, shift = OCB3_STRETCH[blksz]
+  splitbits = 1 << split
+  t2ps = C.MP(0).setbit(splitbits)
+  lomask = (C.MP(0).setbit(split) - 1)
+  himask = ~lomask
+  top, bottom = nn&himask.storeb2c(blksz), C.MP.loadb(nn)&lomask
+  ktop = C.MP.loadb(E.encrypt(top))
+  stretch = (ktop << splitbits) | \
+      (((ktop ^ (ktop << shift)) >> (8*blksz - splitbits))%t2ps)
+  o = (stretch >> splitbits - bottom).storeb(blksz)
+  a = C.ByteString.zero(blksz)
+  if VERBOSE:
+    print 'bottom    : %d' % bottom
+    print 'Ktop      : %s' % hex(ktop.storeb(blksz))
+    print 'Stretch   : %s' % hex(stretch.storeb(blksz + (1 << split - 3)))
+    print 'Offset_0  : %s' % hex(o)
+
+  ## Split the message into blocks.
+  i = 1
+  y = C.WriteBuffer()
+  v, tl = blocks0(m, blksz)
+  for x in v:
+    b = ntz(i)
+    o ^= Lgamma[b]
+    a ^= x
+    if VERBOSE:
+      print 'Offset_%-3d: %s' % (i, hex(o))
+      print 'Checksum_%d: %s' % (i, hex(a))
+    y.put(E.encrypt(x ^ o) ^ o)
+    i += 1
+  if tl:
+    o ^= Lstar
+    n = len(tl)
+    pad = E.encrypt(o)
+    a ^= pad10star(tl, blksz)
+    if VERBOSE:
+      print 'Offset_*  : %s' % hex(o)
+      print 'Checksum_*: %s' % hex(a)
+    y.put(tl ^ pad[0:n])
+  o ^= Ldollar
+  t = E.encrypt(a ^ o) ^ pmac3(E, h)
+  return C.ByteString(y), C.ByteString(t[:tsz])
+
+def ocbgen(bc):
+  w = bc.blksz
+  return [(w, 0, 0), (w, 1, 0), (w, 0, 1),
+          (w, 0, 3*w),
+          (w, 3*w, 3*w),
+          (w, 0, 3*w + 5),
+          (w, 3*w - 5, 3*w + 5)]
+
+def ocb3gen(bc):
+  w = bc.blksz
+  return [(w - 2, 0, 0), (w - 2, 1, 0), (w - 2, 0, 1),
+          (w - 5, 0, 3*w),
+          (w - 3, 3*w, 3*w),
+          (w - 2, 0, 3*w + 5),
+          (w - 2, 3*w - 5, 3*w + 5)]
+
+###--------------------------------------------------------------------------
+### Main program.
+
+VERBOSE = LRVERBOSE = False
+
+class struct (object):
+  def __init__(me, **kw):
+    me.__dict__.update(kw)
+
+def mct(ocb, bc, ksz, nsz, tsz):
+  k = C.MP(8*tsz).storeb(ksz)
+  E = bc(k)
+  e = C.ByteString('')
+  n = C.MP(1)
+  cbuf = C.WriteBuffer()
+  for i in xrange(128):
+    s = C.ByteString.zero(i)
+    y, t = ocb(E, n.storeb(nsz), s, s, tsz); n += 1; cbuf.put(y).put(t)
+    y, t = ocb(E, n.storeb(nsz), e, s, tsz); n += 1; cbuf.put(y).put(t)
+    y, t = ocb(E, n.storeb(nsz), s, e, tsz); n += 1; cbuf.put(y).put(t)
+  _, t = ocb(E, n.storeb(nsz), C.ByteString(cbuf), e, tsz)
+  print hex(t)
+
+argc = len(argv)
+argi = 1
+
+def usage():
+  print >>stderr, """\
+usage: %s [-v] OCB BLKC OP ARGS...
+        mct KSZ NSZ TSZ
+        kat K N0 TSZ HSZ,MSZ ...
+        lraes W K M""" % argv[0]
+  exit(2)
+
+def arg(must = True, default = None):
+  global argi
+  if argi < argc: argi += 1; return argv[argi - 1]
+  elif not must: return default
+  else: usage()
+
+MODEMAP = { 'ocb1': ocb1,
+            'ocb2': ocb2,
+            'ocb3': ocb3 }
+
+def pat(sz):
+  b = C.WriteBuffer()
+  for i in xrange(sz): b.putu8(i%256)
+  return C.ByteString(b)
+
+opt = arg()
+if opt == '-v': VERBOSE = True; opt = arg()
+ocb = MODEMAP[opt]
+
+bcname = arg()
+bc = None
+for d in LRAES, C.gcprps:
+  try: bc = d[bcname]
+  except KeyError: pass
+  else: break
+if bc is None: raise KeyError, bcname
+
+mode = arg()
+if mode == 'mct':
+  ksz = int(arg()); nsz = int(arg()); tsz = int(arg())
+  mct(ocb, bc, ksz, nsz, tsz)
+  exit(0)
+
+elif mode == 'kat':
+  k = C.bytes(arg())
+  E = bc(k)
+  nspec = arg()
+  if nspec.endswith('+'): ninc = 1; nspec = nspec[:-1]
+  else: ninc = 0
+  n0 = C.bytes(nspec)
+  nz = C.MP.loadb(n0)
+  nsz = len(n0)
+  tsz = int(arg())
+
+  print 'K: %s' % hex(k)
+
+  while True:
+    hmsz = arg(must = False)
+    if hmsz is None: break
+    hsz, msz = map(int, hmsz.split(','))
+    n = nz.storeb(nsz)
+    h = pat(hsz)
+    m = pat(msz)
+    y, t = ocb(E, n, h, m, tsz)
+    print
+    print 'N: %s' % hex(n)
+    print 'A: %s' % hex(h)
+    print 'P: %s' % hex(m)
+    print 'C: %s%s' % (hex(y), hex(t))
+    nz += ninc
+
+elif mode == 'lraes':
+  w = int(arg())
+  k = C.bytes(arg())
+  m = C.bytes(arg())
+  LRVERBOSE = True
+  lr = LubyRackoffCipher(bc, w)
+  E = lr(k)
+  print
+  c = E.encrypt(m)
+  print 'E\'(K, m) = %s' % hex(c)
+
+else:
+  usage()
+
+###----- That's all, folks --------------------------------------------------