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

Joachim Strömbergson joachim at secworks.se
Mon May 4 06:43:06 UTC 2015


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Aloha!

Good to see a model for the keywrap. I didn't know you were going to do
one though Rob. I started working on a keywrap HW implementation and
looked for a model (I started writing a Python model too). Now I have
something to work against. ;-)

git at cryptech.is wrote:
> 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()
> 
> _______________________________________________ Commits mailing list 
> Commits at cryptech.is https://lists.cryptech.is/listinfo/commits


- -- 
Med vänlig hälsning, Yours

Joachim Strömbergson - Alltid i harmonisk svängning.
========================================================================
 Joachim Strömbergson          Secworks AB          joachim at secworks.se
========================================================================
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2
Comment: GPGTools - http://gpgtools.org
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQIcBAEBCAAGBQJVRxT6AAoJEF3cfFQkIuyNMHMQANHISMcYEgH710szmLNpiSlE
wgQUfIas9qVY6MsAf5H1gaqLBELGfJl3G1Jid6I+YZvoTgmfW7v1q//pTk+ljc0P
yvPd9jLRtJvHNaQJx4sStp7HVgbiMcmcBRsr9bZMIFzHb9DHEUn9Aa4YoKMIYq34
Z7nLQOWznQ4ft/kooNMPZH1ExIJ64tRh/a/i1MAKdl7ywS0+j6I0JyGm0OOL/SHl
zO6r7zEZh8e5Uo0m7KZvz9dkGlNEVGt/0NcVH2Ps2AicIsG6L8MJK2/D5im6Mq3s
hjD/RgZHAEMv8XhtUmvHukebGsHGgzlDsL0NVDnywvcw1PPYVWLwkRVvt/u/ezV/
Bav6UajfS15JyzHSx9fbiJJ2CivATMVUsUIIP2zzd15EIoeeKcRqEjDYcGOYSAIq
pUwxX8O83o6B4xSOHrfnCy3PVBdAXUkuIo6VHsbtp/B6k6N9A35ZdJLcHQpD6IJP
+4NCal/LeGpntOzc0dE3+5bp2E6FLM6XhY/2Td1qh7tkyralrPWLS3yjVGghSSah
6oHR81rAknERPmP6pLFOmjl4ABXEfOwFarEndrDhITs4fYbOzF+Z+FeLijX31jZA
AUtIEkUm+XaJ2keGPxOoqeBNUA7+NIbWAU3gA5ApKeqznjAd8LCnWLMzjVwohUJb
pE0rzuxS7krOAzFPIQ4Q
=wJZ3
-----END PGP SIGNATURE-----


More information about the Tech mailing list