[Cryptech-Commits] [user/sra/aes-keywrap] 01/01: Initial commit of AES Key Wrap implementation.

git at cryptech.is git at cryptech.is
Mon May 4 03:42:19 UTC 2015


This is an automated email from the git hooks/post-receive script.

sra at hactrn.net pushed a commit to branch master
in repository user/sra/aes-keywrap.

commit 865fffeafdc6622285a2dd31e17999965569312a
Author: Rob Austein <sra at hactrn.net>
Date:   Sun May 3 23:40:59 2015 -0400

    Initial commit of AES Key Wrap implementation.
---
 GNUmakefile    |  22 +++
 README.md      |  27 ++++
 aes_keywrap.c  | 300 ++++++++++++++++++++++++++++++++++++++
 aes_keywrap.h  |  55 +++++++
 aes_keywrap.py | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 855 insertions(+)

diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..6ff3963
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,22 @@
+CRYPTLIB_DIR := /Users/sra/cryptlib/cryptlib-3.4.2
+
+CFLAGS	+= -g -I${CRYPTLIB_DIR} -DAES_KEY_WRAP_SELF_TEST
+LDFLAGS += -g -L${CRYPTLIB_DIR} -lcl
+
+EXE := aes_key_wrap
+SRC := $(wildcard *.c)
+OBJ := $(SRC:.c=.o)
+
+all: ${EXE}
+
+clean:
+	rm -f *.o ${EXE}
+
+${EXE}: ${OBJ}
+	${CC} ${LDFLAGS} -o $@ $^
+
+aes_key_wrap.o = aes_key_wrap.c aes_key_wrap.h
+
+test: ${EXE}
+	./${EXE}
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..78f3bb9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+AES key wrap
+============
+
+A preliminary implementation of AES Key Wrap, RFC 5649 flavor, using
+Cryptlib to supply the AES ECB transformations.
+
+aes_keywrap.py contains two different Python implementations:
+
+1. An implementation using Python longs as 64-bit integers; and
+
+2. An implementation using Python arrays.
+
+The first of these is the easiest to understand, as it can just do
+(long) integer arithmetic and follow the specification very closely.
+The second is closer to what one would do to implement this in an
+assembly language like C.
+
+aes_keywrap.[ch] is a C implementation.  The API for this is not yet
+set in stone.
+
+All three implementations include test vectors.
+
+The two implementations based on byte arrays use shift and mask
+operations to handle the two numerical values ("m" and "t") which
+require byte swapping on little endian hardware; this is not the most
+efficient implementation possible, but it's portable, and will almost
+certainly be lost in the noise under the AES operations.
diff --git a/aes_keywrap.c b/aes_keywrap.c
new file mode 100644
index 0000000..fc1c1bd
--- /dev/null
+++ b/aes_keywrap.c
@@ -0,0 +1,300 @@
+/*
+ * Implementation of RFC 5649 variant of AES Key Wrap, using Cryptlib
+ * to supply the AES ECB encryption and decryption functions.
+ *
+ * Note that there are two different block sizes involved here: the
+ * key wrap algorithm deals entirely with 64-bit blocks, while AES
+ * itself deals with 128-bit blocks.  In practice, this is not as
+ * confusing as it sounds, because we combine two 64-bit blocks to
+ * create one 128-bit block just prior to performing an AES operation,
+ * then split the result back to 64-bit blocks immediately afterwards.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <cryptlib.h>
+
+#include "aes_keywrap.h"
+
+aes_key_wrap_status_t aes_key_wrap(const CRYPT_CONTEXT K,
+                                   const unsigned char * const Q,
+                                   const size_t m,
+                                   unsigned char *C,
+                                   size_t *C_len)
+{
+  unsigned char aes_block[16];
+  unsigned long n;
+  long i, j;
+
+  assert(AES_KEY_WRAP_CIPHERTEXT_SIZE(m) % 8 == 0);
+
+  if (Q == NULL || C == NULL || C_len == NULL || *C_len < AES_KEY_WRAP_CIPHERTEXT_SIZE(m))
+    return AES_KEY_WRAP_BAD_ARGUMENTS;
+
+  *C_len = AES_KEY_WRAP_CIPHERTEXT_SIZE(m);
+
+  memmove(C + 8, Q, m);
+  if (m % 8 != 0)
+    memset(C + 8 + m, 0, 8 -  (m % 8));
+  C[0] = 0xA6;
+  C[1] = 0x59;
+  C[2] = 0x59;
+  C[3] = 0xA6;
+  C[4] = (m >> 24) & 0xFF;
+  C[5] = (m >> 16) & 0xFF;
+  C[6] = (m >>  8) & 0xFF;
+  C[7] = (m >>  0) & 0xFF;
+
+  n = (AES_KEY_WRAP_CIPHERTEXT_SIZE(m) / 8) - 1;
+
+  if (n == 1) {
+    if (cryptEncrypt(K, C, 16) != CRYPT_OK)
+      return AES_KEY_WRAP_ENCRYPTION_FAILED;
+  }
+
+  else {
+    for (j = 0; j <= 5; j++) {
+      for (i = 1; i <= n; i++) {
+        unsigned long t = n * j + i;
+        memcpy(aes_block + 0, C,         8);
+        memcpy(aes_block + 8, C + i * 8, 8);
+        if (cryptEncrypt(K, aes_block, sizeof(aes_block)) != CRYPT_OK)
+          return AES_KEY_WRAP_ENCRYPTION_FAILED;
+        memcpy(C,         aes_block + 0, 8);
+        memcpy(C + i * 8, aes_block + 8, 8);
+        C[7] ^= t & 0xFF; t >>= 8;
+        C[6] ^= t & 0xFF; t >>= 8;
+        C[5] ^= t & 0xFF; t >>= 8;
+        C[4] ^= t & 0xFF;
+      }
+    }
+  }
+
+  return AES_KEY_WRAP_OK;
+}
+
+aes_key_wrap_status_t aes_key_unwrap(const CRYPT_CONTEXT K,
+                                     const unsigned char * const C,
+                                     const size_t C_len,
+                                     unsigned char *Q,
+                                     size_t *Q_len)
+{
+  unsigned char aes_block[16];
+  unsigned long n;
+  long i, j;
+  size_t m;
+
+  if (C == NULL || Q == NULL || C_len % 8 != 0 || C_len < 16 || Q_len == NULL || *Q_len < C_len)
+    return AES_KEY_WRAP_BAD_ARGUMENTS;
+
+  n = (C_len / 8) - 1;
+
+  if (Q != C)
+    memmove(Q, C, C_len);
+
+  if (n == 1) {
+    if (cryptDecrypt(K, Q, 16) != CRYPT_OK)
+      return AES_KEY_WRAP_DECRYPTION_FAILED;
+  }
+
+  else {
+    for (j = 5; j >= 0; j--) {
+      for (i = n; i >= 1; i--) {
+        unsigned long t = n * j + i;
+        Q[7] ^= t & 0xFF; t >>= 8;
+        Q[6] ^= t & 0xFF; t >>= 8;
+        Q[5] ^= t & 0xFF; t >>= 8;
+        Q[4] ^= t & 0xFF;
+        memcpy(aes_block + 0, Q,         8);
+        memcpy(aes_block + 8, Q + i * 8, 8);
+        if (cryptDecrypt(K, aes_block, sizeof(aes_block)) != CRYPT_OK)
+          return AES_KEY_WRAP_DECRYPTION_FAILED;
+        memcpy(Q,         aes_block + 0, 8);
+        memcpy(Q + i * 8, aes_block + 8, 8);
+      }
+    }
+  }
+
+  if (Q[0] != 0xA6 || Q[1] != 0x59 || Q[2] != 0x59 || Q[3] != 0xA6)
+    return AES_KEY_WRAP_BAD_MAGIC;
+
+  m = (((((Q[4] << 8) + Q[5]) << 8) + Q[6]) << 8) + Q[7];
+
+  if (m <= 8 * (n - 1) || m > 8 * n)
+    return AES_KEY_WRAP_BAD_LENGTH;
+
+  if (m % 8 != 0)
+    for (i = m + 8; i < 8 * (n + 1); i++)
+      if (Q[i] != 0x00)
+        return AES_KEY_WRAP_BAD_PADDING;
+
+  *Q_len = m;
+
+  memmove(Q, Q + 8, m);
+
+  return AES_KEY_WRAP_OK;
+}
+
+const char *aes_key_wrap_error_string(const aes_key_wrap_status_t code)
+{
+  switch (code) {
+  case AES_KEY_WRAP_OK:                 return "OK";
+  case AES_KEY_WRAP_BAD_ARGUMENTS:      return "Bad argument";
+  case AES_KEY_WRAP_ENCRYPTION_FAILED:  return "Encryption failed";
+  case AES_KEY_WRAP_DECRYPTION_FAILED:  return "Decryption failed";
+  case AES_KEY_WRAP_BAD_MAGIC:          return "Bad AIV magic number";
+  case AES_KEY_WRAP_BAD_LENGTH:         return "Encoded length out of range";
+  case AES_KEY_WRAP_BAD_PADDING:        return "Nonzero padding detected";
+  default:                              return NULL;
+  }
+}
+
+
+#ifdef AES_KEY_WRAP_SELF_TEST
+
+/*
+ * Test cases from RFC 5649.
+ */
+
+typedef struct {
+  const char *K;                /* Key-encryption-key */
+  const char *Q;                /* Plaintext */
+  const char *C;                /* Ciphertext */
+} test_case_t;
+
+static const test_case_t test_case[] = {
+
+  { "5840df6e29b02af1 ab493b705bf16ea1 ae8338f4dcc176a8",                       /* K */
+    "c37b7e6492584340 bed1220780894115 5068f738",                               /* Q */
+    "138bdeaa9b8fa7fc 61f97742e72248ee 5ae6ae5360d1ae6a 5f54f373fa543b6a"},     /* C */
+
+  { "5840df6e29b02af1 ab493b705bf16ea1 ae8338f4dcc176a8",                       /* K */
+    "466f7250617369",                                                           /* Q */
+    "afbeb0f07dfbf541 9200f2ccb50bb24f" }                                       /* C */
+
+};
+
+static int parse_hex(const char *hex, unsigned char *bin, size_t *len, const size_t max)
+{
+  static const char whitespace[] = " \t\r\n";
+  size_t i;
+
+  assert(hex != NULL && bin != NULL && len != NULL);
+
+  hex += strspn(hex, whitespace);
+
+  for (i = 0; *hex != '\0' && i < max; i++, hex += 2 + strspn(hex + 2, whitespace))
+    if (sscanf(hex, "%2hhx", &bin[i]) != 1)
+      return 0;
+
+  *len = i;
+
+  return *hex == '\0';
+}
+
+static const char *format_hex(const unsigned char *bin, const size_t len, char *hex, const size_t max)
+{
+  size_t i;
+
+  assert(bin != NULL && hex != NULL && len * 3 < max);
+
+  if (len == 0)
+    return "";
+
+  for (i = 0; i < len; i++)
+    sprintf(hex + 3 * i, "%02x:", bin[i]);
+
+  hex[len * 3 - 1] = '\0';
+  return hex;
+}
+
+#ifndef TC_BUFSIZE
+#define TC_BUFSIZE      4096
+#endif
+
+static int run_test(const test_case_t * const tc)
+{
+  unsigned char K[TC_BUFSIZE], Q[TC_BUFSIZE], C[TC_BUFSIZE], q[TC_BUFSIZE], c[TC_BUFSIZE];
+  size_t K_len, Q_len, C_len, q_len = sizeof(q), c_len = sizeof(c);
+  char h1[TC_BUFSIZE * 3],  h2[TC_BUFSIZE * 3];
+  aes_key_wrap_status_t ret;
+  CRYPT_CONTEXT ctx;
+  int ok = 1;
+
+  assert(tc != NULL);
+
+  if (!parse_hex(tc->K, K, &K_len, sizeof(K)))
+    return printf("couldn't parse KEK %s\n", tc->K), 0;
+
+  if (!parse_hex(tc->Q, Q, &Q_len, sizeof(Q)))
+    return printf("couldn't parse plaintext %s\n", tc->Q), 0;
+
+  if (!parse_hex(tc->C, C, &C_len, sizeof(C)))
+    return printf("couldn't parse ciphertext %s\n", tc->C), 0;
+
+  if (cryptCreateContext(&ctx, CRYPT_UNUSED, CRYPT_ALGO_AES) != CRYPT_OK)
+    return printf("couldn't create context\n"), 0;
+
+  if (cryptSetAttribute(ctx, CRYPT_CTXINFO_MODE, CRYPT_MODE_ECB) != CRYPT_OK ||
+      cryptSetAttributeString(ctx, CRYPT_CTXINFO_KEY, K, K_len)  != CRYPT_OK)
+    ok = printf("couldn't initialize KEK\n"), 0;
+
+  if (ok) {
+
+    if ((ret = aes_key_wrap(ctx, Q, Q_len, c, &c_len)) != AES_KEY_WRAP_OK)
+      ok = printf("couldn't wrap %s: %s\n", tc->Q, aes_key_wrap_error_string(ret)), 0;
+
+    if ((ret = aes_key_unwrap(ctx, C, C_len, q, &q_len)) != AES_KEY_WRAP_OK)
+      ok = printf("couldn't unwrap %s: %s\n", tc->C, aes_key_wrap_error_string(ret)), 0;
+
+    if (C_len != c_len || memcmp(C, c, C_len) != 0)
+      ok = printf("ciphertext mismatch:\n  Want: %s\n  Got:  %s\n",
+                  format_hex(C, C_len, h1, sizeof(h1)),
+                  format_hex(c, c_len, h2, sizeof(h2))), 0;
+
+    if (Q_len != q_len || memcmp(Q, q, Q_len) != 0)
+      ok = printf("plaintext mismatch:\n  Want: %s\n  Got:  %s\n",
+                  format_hex(Q, Q_len, h1, sizeof(h1)),
+                  format_hex(q, q_len, h2, sizeof(h2))), 0;
+
+  }
+
+  cryptDestroyContext(ctx);
+  return ok;
+}
+
+int main (int argc, char *argv[])
+{
+  int i;
+
+  if (cryptInit() != CRYPT_OK) 
+    return printf("Couldn't initialize Cryptlib\n"), 1;
+
+  for (i = 0; i < sizeof(test_case)/sizeof(*test_case); i++) {
+    printf("Running test case #%d...", i);
+    if (run_test(&test_case[i]))
+      printf("OK\n");
+  }
+
+  if (cryptEnd() != CRYPT_OK)
+    return printf("Cryptlib unhappy on shutdown\n"), 1;
+
+  return 0;
+}
+
+#endif
+
+/*
+ * "Any programmer who fails to comply with the standard naming, formatting,
+ *  or commenting conventions should be shot.  If it so happens that it is
+ *  inconvenient to shoot him, then he is to be politely requested to recode
+ *  his program in adherence to the above standard."
+ *                      -- Michael Spier, Digital Equipment Corporation
+ *
+ * Local variables:
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/aes_keywrap.h b/aes_keywrap.h
new file mode 100644
index 0000000..2264c00
--- /dev/null
+++ b/aes_keywrap.h
@@ -0,0 +1,55 @@
+/*
+ * Implementation of RFC 5649 variant of AES Key Wrap, using Cryptlib
+ * to supply the AES ECB encryption and decryption functions.
+ */
+
+#ifndef _AES_KEYWRAP_
+#define _AES_KEYWRAP_
+
+/*
+ * Input and output buffers can overlap (we use memmove()), but be
+ * warned that failures can occur after we've started writing to the
+ * output buffer, so if the input and output buffers do overlap, the
+ * input may have been overwritten by the time the failure occurs.
+ */
+
+typedef enum {
+  AES_KEY_WRAP_OK,                      /* Success */
+  AES_KEY_WRAP_BAD_ARGUMENTS,           /* Null pointers or similar */
+  AES_KEY_WRAP_ENCRYPTION_FAILED,       /* cryptEncrypt() failed */
+  AES_KEY_WRAP_DECRYPTION_FAILED,       /* cryptDecrypt() failed */
+  AES_KEY_WRAP_BAD_MAGIC,               /* MSB(32,A) != 0xA65959A6 */
+  AES_KEY_WRAP_BAD_LENGTH,              /* LSB(32,A) out of range */
+  AES_KEY_WRAP_BAD_PADDING              /* Nonzero padding detected */
+} aes_key_wrap_status_t;
+  
+extern aes_key_wrap_status_t aes_key_wrap(const CRYPT_CONTEXT kek,
+                                          const unsigned char * const plaintext,
+                                          const size_t plaintext_length,
+                                          unsigned char *cyphertext,
+                                          size_t *ciphertext_length);
+
+extern aes_key_wrap_status_t aes_key_unwrap(const CRYPT_CONTEXT kek,
+                                            const unsigned char * const ciphertext,
+                                            const size_t ciphertext_length,
+                                            unsigned char *plaintext,
+                                            size_t *plaintext_length);
+
+extern const char *
+aes_key_wrap_error_string(const aes_key_wrap_status_t code);
+
+/*
+ * AES_KEY_WRAP_CIPHERTEXT_SIZE() tells you how big the ciphertext
+ * will be for a given plaintext size.
+ */
+
+#define AES_KEY_WRAP_CIPHERTEXT_SIZE(_plaintext_length_) \
+  ((size_t) (((_plaintext_length_) + 15) & ~7))
+
+#endif
+
+/*
+ * Local variables:
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/aes_keywrap.py b/aes_keywrap.py
new file mode 100644
index 0000000..a191e3e
--- /dev/null
+++ b/aes_keywrap.py
@@ -0,0 +1,451 @@
+# minas-ithil.hactrn.net:/Users/sra/cryptech/aes-keywrap.py, 30-Apr-2015 09:10:55, sra
+#
+# Python prototype of an AES Key Wrap implementation, RFC 5649 flavor
+# per Russ, using Cryptlib to supply the AES code.
+#
+# Terminology mostly follows the RFC, including variable names.
+#
+# Block sizes get confusing: AES Key Wrap uses 64-bit blocks, not to
+# be confused with AES, which uses 128-bit blocks.  In practice, this
+# is less confusing than when reading the description, because we
+# concatenate two 64-bit blocks just prior to performing an AES ECB
+# operation, then immediately split the result back into a pair of
+# 64-bit blocks.
+#
+# The spec uses both zero based and one based arrays, probably because
+# that's the easiest way of coping with the extra block of ciphertext.
+
+
+from cryptlib_py import *
+from struct import pack, unpack
+import atexit
+
+
+def bin2hex(bytes):
+  return ":".join("%02x" % ord(b) for b in bytes)
+
+def hex2bin(text):
+  return "".join(text.split()).translate(None, ":").decode("hex")
+
+
+def start_stop(start, stop):            # syntactic sugar
+  step = -1 if start > stop else 1
+  return xrange(start, stop + step, step)
+
+
+class Block(long):
+  """
+  One 64-bit block, a Python long with some extra methods.
+  """
+
+  def __new__(cls, v):
+    # Python voodoo, nothing to see here, move along.
+    assert v >= 0 and v.bit_length() <= 64
+    return super(Block, cls).__new__(cls, v)
+
+  @classmethod
+  def from_bytes(cls, v):
+    assert isinstance(v, str) and len(v) == 8
+    return cls(unpack(">Q", v)[0])
+
+  def to_bytes(self):
+    assert self >= 0 and self.bit_length() <= 64
+    return pack(">Q", self)
+
+  @classmethod
+  def from_words(cls, hi, lo):
+    assert hi >= 0 and hi.bit_length() <= 32
+    assert lo >= 0 and lo.bit_length() <= 32
+    return cls((hi << 32L) + lo)
+
+  def to_words(self):
+    assert self >= 0 and self.bit_length() <= 64
+    return ((self >> 32) & 0xFFFFFFFF), (self & 0xFFFFFFFF)
+
+
+class Buffer(array):
+  """
+  Python type B array with a few extra methods.
+  """
+
+  def __new__(cls, initializer = None):
+    if initializer is None:
+      return super(Buffer, cls).__new__(cls, "B")
+    else:
+      return super(Buffer, cls).__new__(cls, "B", initializer)
+
+  def get_block(self, i):
+    return self[8*i:8*(i+1)]
+
+  def set_block(self, i, v):
+    assert len(v) == 8
+    self[8*i:8*(i+1)] = v
+
+
+class KEK(object):
+  """
+  Key encryption key, based on a Cryptlib encryption context.
+
+  This can work with either Block objects or Python array.
+  """
+
+  def __init__(self, salt = None, passphrase = None, size = None, key = None, generate = False):
+    self.ctx = cryptCreateContext(CRYPT_UNUSED, CRYPT_ALGO_AES)
+    atexit.register(cryptDestroyContext, self.ctx)
+    self.ctx.CTXINFO_MODE = CRYPT_MODE_ECB
+    if size is not None:
+      assert size % 8 == 0
+      self.ctx.CTXINFO_KEYSIZE = size / 8
+    if salt is None and passphrase is not None:
+      salt = "\x00" * 8            # Totally unsafe salt value, don't use this at home kids
+    if salt is not None:
+      self.ctx.CTXINFO_KEYING_SALT = salt
+    if passphrase is not None:
+      self.ctx.CTXINFO_KEYING_VALUE = passphrase
+    if key is not None:
+      self.ctx.CTXINFO_KEY = key
+    if generate:
+      cryptGenerateKey(self.ctx)
+
+  def encrypt_block(self, b1, b2):
+    """
+    Concatenate two 64-bit blocks into a 128-bit block, encrypt it
+    with AES-ECB, return the result split back into 64-bit blocks.
+    """
+
+    aes_block = array("c", pack(">QQ", b1, b2))
+    cryptEncrypt(self.ctx, aes_block)
+    return tuple(Block(b) for b in unpack(">QQ", aes_block.tostring()))
+
+  def encrypt_array(self, b1, b2):
+    """
+    Concatenate two 64-bit blocks into a 128-bit block, encrypt it
+    with AES-ECB, return the result split back into 64-bit blocks.
+    """
+
+    aes_block = b1 + b2
+    cryptEncrypt(self.ctx, aes_block)
+    return aes_block[:8], aes_block[8:]
+
+  def decrypt_block(self, b1, b2):
+    """
+    Concatenate two 64-bit blocks into a 128-bit block, decrypt it
+    with AES-ECB, return the result split back into 64-bit blocks.
+
+    Blocks can be represented either as Block objects or as 8-byte
+    Python arrays.
+    """
+
+    aes_block = array("c", pack(">QQ", b1, b2))
+    cryptDecrypt(self.ctx, aes_block)
+    return tuple(Block(b) for b in unpack(">QQ", aes_block.tostring()))
+
+  def decrypt_array(self, b1, b2):
+    """
+    Concatenate two 64-bit blocks into a 128-bit block, decrypt it
+    with AES-ECB, return the result split back into 64-bit blocks.
+
+    Blocks can be represented either as Block objects or as 8-byte
+    Python arrays.
+    """
+
+    aes_block = b1 + b2
+    cryptDecrypt(self.ctx, aes_block)
+    return aes_block[:8], aes_block[8:]
+
+
+def block_wrap_key(Q, K):
+  """
+  Wrap a key according to RFC 5649 section 4.1.
+
+  Q is the plaintext to be wrapped, a byte string.
+
+  K is the KEK with which to encrypt.
+
+  Returns C, the wrapped ciphertext.
+  """
+
+  m = len(Q)
+  if m % 8 != 0:
+    Q += "\x00" * (8 - (m % 8))
+  assert len(Q) % 8 == 0
+
+  n = len(Q) / 8
+  P = [Block.from_bytes(Q[i:i+8]) for i in xrange(0, len(Q), 8)]
+  assert len(P) == n
+
+  P.insert(0, None)                     # Make P one-based
+  A = Block.from_words(0xA65959A6, m) # RFC 5649 section 3 AIV 
+  
+  if n == 1:
+    C = K.encrypt_block(A, P[1])
+
+  else:
+    # RFC 3394 section 2.2.1
+    R = [p for p in P]
+    for j in start_stop(0, 5):
+      for i in start_stop(1, n):
+        B_hi, B_lo = K.encrypt_block(A, R[i])
+        A = Block(B_hi ^ (n * j + i))
+        R[i] = B_lo
+    C = R
+    C[0] = A
+
+  assert len(C) == n + 1
+  return "".join(c.to_bytes() for c in C)
+
+
+def array_wrap_key(Q, K):
+  """
+  Wrap a key according to RFC 5649 section 4.1.
+
+  Q is the plaintext to be wrapped, a byte string.
+
+  K is the KEK with which to encrypt.
+
+  Returns C, the wrapped ciphertext.
+  """
+
+  m = len(Q)                            # Plaintext length
+  R = Buffer("\xa6\x59\x59\xa6")        # Magic MSB(32,A)
+  for i in xrange(24, -8, -8):
+    R.append((m >> i) & 0xFF)           # Build LSB(32,A)
+  R.fromstring(Q)                       # Append Q
+  if m % 8 != 0:                        # Pad Q if needed
+    R.fromstring("\x00" * (8 - (m % 8)))
+
+  assert len(R) % 8 == 0
+  n = (len(R) / 8) - 1
+
+  if n == 1:
+    B1, B2 = K.encrypt_array(R.get_block(0), R.get_block(1))
+    R.set_block(0, B1)
+    R.set_block(1, B2)
+
+  else:
+    # RFC 3394 section 2.2.1
+    for j in start_stop(0, 5):
+      for i in start_stop(1, n):
+        B1, B2 = K.encrypt_array(R.get_block(0), R.get_block(i))
+        t = n * j + i
+        R.set_block(0, B1)
+        R.set_block(i, B2)
+        R[7] ^= t & 0xFF; t >>= 8
+        R[6] ^= t & 0xFF; t >>= 8
+        R[5] ^= t & 0xFF; t >>= 8
+        R[4] ^= t & 0xFF
+
+  assert len(R) == (n + 1) * 8
+  return R.tostring()
+
+
+class UnwrapError(Exception):
+  "Something went wrong during unwrap."
+
+
+def block_unwrap_key(C, K):
+  """
+  Unwrap a key according to RFC 5649 section 4.2.
+
+  C is the ciphertext to be unwrapped, a byte string
+
+  K is the KEK with which to decrypt.
+
+  Returns Q, the unwrapped plaintext.
+  """
+
+  if len(C) % 8 != 0:
+    raise UnwrapError("Ciphertext length %d is not an integral number of blocks" % len(C))
+
+  n = (len(C) / 8) - 1
+  C = [Block.from_bytes(C[i:i+8]) for i in xrange(0, len(C), 8)]
+  assert len(C) == n + 1
+
+  P = [None for i in xrange(n+1)]
+
+  if n == 1:
+    A, P[1] = K.decrypt_block(C[0], C[1])
+
+  else:
+    # RFC 3394 section 2.2.2 steps (1), (2), and part of (3)
+    A = C[0]
+    R = C
+    for j in start_stop(5, 0):
+      for i in start_stop(n, 1):
+        B_hi, B_lo = K.decrypt_block(Block(A ^ (n * j + i)), R[i])
+        A = B_hi
+        R[i] = B_lo
+    P = R
+
+  magic, m = A.to_words()
+
+  if magic != 0xA65959A6:
+    raise UnwrapError("Magic value in AIV should hae been 0xA65959A6, was 0x%08x" % magic)
+
+  if m <= 8 * (n - 1) or m > 8 * n:
+    raise UnwrapError("Length encoded in AIV out of range: m %d, n %d" % (m, n))
+
+  Q = "".join(p.to_bytes() for p in P[1:])
+  assert len(Q) == 8 * n
+
+  if any(q != "\x00" for q in Q[m:]):
+    raise UnwrapError("Nonzero trailing bytes %s" % bin2hex(Q[m:]))
+
+  return Q[:m]
+
+
+def array_unwrap_key(C, K):
+  """
+  Unwrap a key according to RFC 5649 section 4.2.
+
+  C is the ciphertext to be unwrapped, a byte string
+
+  K is the KEK with which to decrypt.
+
+  Returns Q, the unwrapped plaintext.
+  """
+
+  if len(C) % 8 != 0:
+    raise UnwrapError("Ciphertext length %d is not an integral number of blocks" % len(C))
+
+  n = (len(C) / 8) - 1
+  R = Buffer(C)
+
+  if n == 1:
+    B1, B2 = K.decrypt_array(R.get_block(0), R.get_block(1))
+    R.set_block(0, B1)
+    R.set_block(1, B2)
+
+  else:
+    # RFC 3394 section 2.2.2 steps (1), (2), and part of (3)
+    for j in start_stop(5, 0):
+      for i in start_stop(n, 1):
+        t = n * j + i
+        R[7] ^= t & 0xFF; t >>= 8
+        R[6] ^= t & 0xFF; t >>= 8
+        R[5] ^= t & 0xFF; t >>= 8
+        R[4] ^= t & 0xFF
+        B1, B2 = K.decrypt_array(R.get_block(0), R.get_block(i))
+        R.set_block(0, B1)
+        R.set_block(i, B2)
+
+  if R[:4].tostring() != "\xa6\x59\x59\xa6":
+    raise UnwrapError("Magic value in AIV should hae been 0xA65959A6, was 0x%02x%02x%02x%02x" % (R[0], R[1], R[2], R[3]))
+
+  m = (((((R[4] << 8) + R[5]) << 8) + R[6]) << 8) + R[7]
+
+  if m <= 8 * (n - 1) or m > 8 * n:
+    raise UnwrapError("Length encoded in AIV out of range: m %d, n %d" % (m, n))
+
+  del R[:8]
+  assert len(R) == 8 * n
+
+  if any(r != 0 for r in R[m:]):
+    raise UnwrapError("Nonzero trailing bytes %s" % ":".join("%02x" % r for r in R[m:]))
+
+  del R[m:]
+  assert len(R) == m
+  return R.tostring()
+
+
+def loopback_test(K, I):
+  """
+  Run one test.   Inputs are KEK and a chunk of plaintext.
+
+  Test is just encrypt followed by decrypt to see if we can get
+  matching results without throwing any errors.
+  """
+
+  print "Testing:", repr(I)
+  C = wrap_key(I, K)
+  print "Wrapped: [%d]" % len(C), bin2hex(C)
+  O = unwrap_key(C, K)
+  if I != O:
+    raise RuntimeError("Input and output plaintext did not match: %r <> %r" % (I, O))
+  print
+
+
+def rfc5649_test(K, Q, C):
+  print "Testing: [%d]" % len(Q), bin2hex(Q)
+  print "Wrapped: [%d]" % len(C), bin2hex(C)
+  c = wrap_key(Q, K)
+  q = unwrap_key(C, K)
+  if q != Q:
+    raise RuntimeError("Input and output plaintext did not match: %s <> %s" % (bin2hex(Q), bin2hex(q)))
+  if c != C:
+    raise RuntimeError("Input and output ciphertext did not match: %s <> %s" % (bin2hex(C), bin2hex(c)))
+  print
+
+
+def run_tests():
+
+  print "Test vectors from RFC 5649"
+  print
+
+  rfc5649_test(K = KEK(size = 192, key = hex2bin("5840df6e29b02af1 ab493b705bf16ea1 ae8338f4dcc176a8")),
+               Q = hex2bin("c37b7e6492584340 bed1220780894115 5068f738"),
+               C = hex2bin("138bdeaa9b8fa7fc 61f97742e72248ee 5ae6ae5360d1ae6a 5f54f373fa543b6a"))
+
+  rfc5649_test(K = KEK(size = 192, key = hex2bin("5840df6e29b02af1 ab493b705bf16ea1 ae8338f4dcc176a8")),
+               Q = hex2bin("466f7250617369"),
+               C = hex2bin("afbeb0f07dfbf541 9200f2ccb50bb24f"))
+
+  print "Deliberately mangled test vectors to see whether we notice"
+  print "These *should* detect errors" 
+
+  for d in (dict(K = KEK(size = 192, key = hex2bin("5840df6e29b02af0 ab493b705bf16ea1 ae8338f4dcc176a8")),
+                 Q = hex2bin("466f7250617368"),
+                 C = hex2bin("afbeb0f07dfbf541 9200f2ccb50bb24f")),
+            dict(K = KEK(size = 192, key = hex2bin("5840df6e29b02af0 ab493b705bf16ea1 ae8338f4dcc176a8")),
+                 Q = hex2bin("466f7250617368"),
+                 C = hex2bin("afbeb0f07dfbf541 9200f2ccb50bb24f 0123456789abcdef")),
+            dict(K = KEK(size = 192, key = hex2bin("5840df6e29b02af1 ab493b705bf16ea1 ae8338f4dcc176a8")),
+                 Q = hex2bin("c37b7e6492584340 bed1220780894115 5068f738"),
+                 C = hex2bin("138bdeaa9b8fa7fc 61f97742e72248ee 5ae6ae5360d1ae6a"))):
+    print
+    try:
+      rfc5649_test(**d)
+    except UnwrapError as e:
+      print "Detected an error during unwrap: %s" % e
+    except RuntimeError as e:
+      print "Detected an error in test function: %s" % e
+
+  print
+  print "Loopback tests of various lengths"
+  print
+
+  K = KEK(size = 128, key = hex2bin("00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f"))
+  loopback_test(K, "!")
+  loopback_test(K, "!")
+  loopback_test(K, "Yo!")
+  loopback_test(K, "Hi, Mom")
+  loopback_test(K, "1" * (64 / 8))
+  loopback_test(K, "2" * (128 / 8))
+  loopback_test(K, "3" * (256 / 8))
+  loopback_test(K, "3.14159265358979323846264338327950288419716939937510")
+  loopback_test(K, "3.14159265358979323846264338327950288419716939937510")
+  loopback_test(K, "Hello!  My name is Inigo Montoya. You killed my AES key wrapper. Prepare to die.")
+
+
+def main():
+  cryptInit()
+  atexit.register(cryptEnd)
+  global wrap_key, unwrap_key
+
+  if False:
+    print "Testing with Block (Python long) implementation"
+    print
+    wrap_key   = block_wrap_key
+    unwrap_key = block_unwrap_key
+    run_tests()
+
+  if True:
+    print "Testing with Python array implementation"
+    print
+    wrap_key   = array_wrap_key
+    unwrap_key = array_unwrap_key
+    run_tests()
+
+
+if __name__ == "__main__":
+  main()



More information about the Commits mailing list