From 865fffeafdc6622285a2dd31e17999965569312a Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Sun, 3 May 2015 23:40:59 -0400 Subject: 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(+) create mode 100644 GNUmakefile create mode 100644 README.md create mode 100644 aes_keywrap.c create mode 100644 aes_keywrap.h create mode 100644 aes_keywrap.py 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 +#include +#include +#include + +#include + +#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() -- cgit v1.2.3