[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