diff options
Diffstat (limited to 'Build/source/libs/pplib/pplib-src/src/util/utilcrypt.c')
-rw-r--r-- | Build/source/libs/pplib/pplib-src/src/util/utilcrypt.c | 1190 |
1 files changed, 1190 insertions, 0 deletions
diff --git a/Build/source/libs/pplib/pplib-src/src/util/utilcrypt.c b/Build/source/libs/pplib/pplib-src/src/util/utilcrypt.c new file mode 100644 index 00000000000..2c77e42a4c7 --- /dev/null +++ b/Build/source/libs/pplib/pplib-src/src/util/utilcrypt.c @@ -0,0 +1,1190 @@ + +#include "utilmem.h" +#include "utilcrypt.h" +#include "utilcryptdef.h" +#include "utilmd5.h" + +/* rc4 */ + +/* +Initializer arguments: +- state - crypt state +- map - a space for rc4 bytes map; may be left NULL in which case will be allocated +- vkey - crypt key; may be left NULL iff map is provided and properly initialized +- keylength - the length of crypt key (from 5 to 16 bytes) +*/ + +rc4_state * rc4_state_initialize (rc4_state *state, rc4_map *map, const void *vkey, size_t keylength) +{ + int i, j; + uint8_t tmp; + const uint8_t *key; + key = (const uint8_t *)vkey; + if (keylength == 0 || keylength > 256) + return NULL; + state->flags = 0; + if (map != NULL) + { + state->map = map; + } + else + { + state->map = (rc4_map *)util_malloc(sizeof(rc4_map)); + state->flags |= RC4_STATE_ALLOC; + } + + if (key != NULL) + { + for (i = 0; i < 256; ++i) + state->smap[i] = (uint8_t)i; + for (i = 0, j = 0; i < 256; ++i) + { + j = (j + state->smap[i] + key[i % keylength]) & 255; + tmp = state->smap[i]; + state->smap[i] = state->smap[j]; + state->smap[j] = tmp; + } + } + state->i = 0; + state->j = 0; + state->flush = 0; /* caller is responsible to override if necessary */ + return state; +} + +void rc4_map_save (rc4_state *state, rc4_map *map) +{ + memcpy(map, state->map, sizeof(rc4_map)); +} + +void rc4_map_restore (rc4_state *state, rc4_map *map) +{ + memcpy(state->map, map, sizeof(rc4_map)); + //state->flags = 0; + //state->flush = 0; + state->i = 0; + state->j = 0; +} + +static uint8_t rc4_next_random_byte (rc4_state *state) +{ + uint8_t tmp; + state->i = (state->i + 1) & 255; + state->j = (state->j + state->smap[state->i]) & 255; + tmp = state->smap[state->i]; + state->smap[state->i] = state->smap[state->j]; + state->smap[state->j] = tmp; + return state->smap[(state->smap[state->i] + state->smap[state->j]) & 255]; +} + +iof_status rc4_crypt_state (iof *I, iof *O, rc4_state *state) +{ + uint8_t r; + int c; + while (iof_ensure(O, 1)) + { + if ((c = iof_get(I)) < 0) + return c == IOFERR ? IOFERR : (state->flush ? IOFEOF : IOFEMPTY); + r = rc4_next_random_byte(state); + //r = r ^ ((uint8_t)c); + //iof_set(O, r); + iof_set(O, r ^ ((uint8_t)c)); + } + return IOFFULL; +} + +iof_status rc4_crypt (iof *I, iof *O, const void *key, size_t keylength) +{ + int ret; + rc4_state state; + rc4_map map; + if (rc4_state_initialize(&state, &map, key, keylength) == NULL) + return IOFERR; + state.flush = 1; + ret = rc4_crypt_state(I, O, &state); + rc4_state_close(&state); + return ret; +} + +/* +Variants that operates on c-strings can worn inplace, so output and input can be the same address. +Variant that takes rc4_state pointer expects the state properly initialized. Keep in mind +the crypt procedure modifies rc4 bytes map. All returns the size of encrypted/decrypted +data, which is the same as input data length for rc4. +*/ + +size_t rc4_crypt_data (const void *input, size_t length, void *output, const void *key, size_t keylength) +{ + rc4_state state; + rc4_map map; + if (rc4_state_initialize(&state, &map, key, keylength) == NULL) + return 0; + return rc4_crypt_state_data(&state, input, length, output); + // no need to call rc4_state_close() +} + +size_t rc4_crypt_state_data (rc4_state *state, const void *input, size_t length, void *output) +{ /* state assumed to be initialized and with the proper state of smap */ + const uint8_t *inp; + uint8_t r, *out; + size_t size; + inp = (const uint8_t *)input; + out = (uint8_t *)output; + for (size = 0; size < length; ++size, ++inp, ++out) + { + r = rc4_next_random_byte(state); + *out = r ^ *inp; + } + return length; +} + +void rc4_state_close (rc4_state *state) +{ + if (state->smap != NULL && (state->flags & RC4_STATE_ALLOC)) + { + util_free(state->smap); + state->smap = NULL; + } +} + +/* aes; parts of code excerpted from https://github.com/kokke/tiny-AES128-C */ + +static const uint8_t sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const uint8_t rsbox[256] = +{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + +/* +The round constant word array, rcon[i], contains the values given by +x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +Note that i starts at 1, not 0). +*/ + +static const uint8_t rcon[255] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, + 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, + 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, + 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, + 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, + 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, + 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb }; + +/* block copying */ + +#define aes_copy_block(output, input) memcpy(output, input, 16) + +static void aes_copy_cbc (uint8_t *data, const uint8_t *input) +{ + uint8_t i; + for (i = 0; i < 16; ++i) + data[i] ^= input[i]; +} + +static void aes_copy_xor (uint8_t *data, const uint8_t *input, const uint8_t *iv) +{ + uint8_t i; + for (i = 0; i < 16; ++i) + data[i] = input[i] ^ iv[i]; +} + +/* key expansion */ + +#define AES_COLUMNS 4 // constant in aes + +static void key_expansion (aes_state *state, const uint8_t *key) +{ + uint32_t i, j; + uint8_t t[4], temp; + uint8_t *keydata, keywords, columns; + + keywords = (uint8_t)(state->keylength >> 2); + keydata = (uint8_t *)state->keyblock; + + /* the first round key is the key itself */ + for(i = 0; i < keywords; ++i) + { + keydata[(i * 4) + 0] = key[(i * 4) + 0]; + keydata[(i * 4) + 1] = key[(i * 4) + 1]; + keydata[(i * 4) + 2] = key[(i * 4) + 2]; + keydata[(i * 4) + 3] = key[(i * 4) + 3]; + } + + /* others derived from the first */ + for(columns = AES_COLUMNS * (state->rounds + 1); i < columns; ++i) + { + for(j = 0; j < 4; ++j) + t[j] = keydata[(i - 1) * 4 + j]; + if (i % keywords == 0) + { + /* rotate the 4 bytes in a word to the left once; [a0,a1,a2,a3] becomes [a1,a2,a3,a0] */ + temp = t[0]; + t[0] = t[1]; + t[1] = t[2]; + t[2] = t[3]; + t[3] = temp; + + /* take a four-byte input word and apply the S-box to each of the four bytes to produce an output word */ + t[0] = sbox[t[0]]; + t[1] = sbox[t[1]]; + t[2] = sbox[t[2]]; + t[3] = sbox[t[3]]; + + t[0] = t[0] ^ rcon[i / keywords]; + } + else if (keywords > 6 && i % keywords == 4) + { + t[0] = sbox[t[0]]; + t[1] = sbox[t[1]]; + t[2] = sbox[t[2]]; + t[3] = sbox[t[3]]; + } + keydata[i * 4 + 0] = keydata[(i - keywords) * 4 + 0] ^ t[0]; + keydata[i * 4 + 1] = keydata[(i - keywords) * 4 + 1] ^ t[1]; + keydata[i * 4 + 2] = keydata[(i - keywords) * 4 + 2] ^ t[2]; + keydata[i * 4 + 3] = keydata[(i - keywords) * 4 + 3] ^ t[3]; + } + +} + +/* +An original implementation uses no private buffers except a keyblock. We need private buffers to +keep a CBC vector between calls and to be able to read input data not necessarily in 16-bytes blocks. +Encrypter would actually require only one such buffer, as CBC vector is applied on input data before +the actual cipher procedure. And CBC for the next chunk is simply the output from the previous. +Decrypter, however, applies the cipher first, then applies CBC to the output with a buffered init +vector, and the vector for the next call is the row input before cipher. Hence we need two 16-bytes +buffers for decrypter. +*/ + +/* +aes_state * aes_state_initialize_ecb (aes_state *State, uint8_t *keyblock, const uint8_t *key) +{ + state->flags = 0; + + state->flags |= AES_ECB_MODE; + + if (keyblock == NULL) + { + keyblock = util_malloc(sizeof(aes_keyblock)); + state->flags |= AES_STATE_ALLOC; + } + state->keyblock = keyblock; + key_expansion(state, key); + state->flush = 0; + return state; +} +*/ + +void aes_pdf_mode (aes_state *state) +{ + state->flags |= AES_INLINE_IV; + state->flags &= ~AES_NULL_PADDING; +} + +/* +Initialize arguments: +- state - crypt state +- keyblock - a space for aes key expansion; can be left NULL in which case will be allocated +- key - crypt key; can be left NULL iff keyblock is given and properly initialized +- keylength - the length of the key (16 or 32 bytes) +- iv - 16-bytes CBC initialization vector; + - if left NULL for encoder, one is generated and stored as state->iv + - can also be left NULL for decorer, but then AES_INLINE_IV must be set, as this informs decoder to take + an initialization vector from the beginning of the encrypted stream + +At the first approach, an initialization vector was copied to state block during initialization and encoders +assumed that the state block is the current initialization vector. This simplifies encrypting procedure, +as the output from every 16-bytes chunk encryption is an initialization vector for the next chunk. However, +it makes api usage cumbersome, as the user has to know that iv may need to be copied to state block +before each call. +*/ + +static int aes_key_length (aes_state *state, size_t keylength) +{ + state->keylength = keylength; + switch (keylength) + { + case 16: + state->rounds = 10; + break; + case 24: + state->rounds = 12; + break; + case 32: + state->rounds = 14; + break; + default: + return 0; + } + return 1; +} + +aes_state * aes_encode_initialize (aes_state *state, aes_keyblock *keyblock, const void *key, size_t keylength, const void *iv) +{ + state->flags = 0; + if (!aes_key_length(state, keylength)) + return NULL; + if (iv != NULL) + aes_copy_block(state->iv, iv); + else + aes_generate_iv(state->iv); + state->flags |= AES_HAS_IV; + + if (keyblock == NULL) + { + keyblock = (aes_keyblock *)util_malloc(sizeof(aes_keyblock)); + state->flags |= AES_STATE_ALLOC; + } + state->keyblock = keyblock; + if (key != NULL) /* if NULL we assume keyblock is given and already expanded */ + key_expansion(state, (const uint8_t *)key); + state->flush = 0; + return state; +} + +aes_state * aes_decode_initialize (aes_state *state, aes_keyblock *keyblock, const void *key, size_t keylength, const void *iv) +{ + state->flags = 0; + if (!aes_key_length(state, keylength)) + return NULL; + if (iv != NULL) + { + aes_copy_block(state->iv, iv); + state->flags |= AES_HAS_IV; + } + /* else if AES_INLINE_IV flag is set will be read from input */ + + if (keyblock == NULL) + { + keyblock = (aes_keyblock *)util_malloc(sizeof(aes_keyblock)); + state->flags |= AES_STATE_ALLOC; + } + state->keyblock = keyblock; + if (key != NULL) /* otherwise keyblock is assumed present and properly initialized */ + key_expansion(state, (const uint8_t *)key); + state->flush = 0; + return state; +} + +void aes_state_close (aes_state *state) +{ + if (state->keyblock != NULL && (state->flags & AES_STATE_ALLOC)) + util_free(state->keyblock); +} + +/* add round key */ + +static void aes_round_key (aes_block block, aes_block keyblock) +{ + uint8_t i, j; + for(i = 0; i < 4; ++i) + for(j = 0; j < 4; ++j) + block[i][j] ^= keyblock[i][j]; +} + +#define aes_add_key(block, keyblock, round) aes_round_key(block, (*keyblock)[round]) + +/* substitution */ + +static void aes_encode_sub (aes_block block) +{ + uint8_t i, j, v; + for(i = 0; i < 4; ++i) + for(j = 0; j < 4; ++j) + v = block[i][j], block[i][j] = sbox[v]; +} + +/* rows shift; the row index is the shift offset, the first order is not shifted */ + +static void aes_encode_shift (aes_block block) +{ + uint8_t tmp; + + /* 1st row rotated once */ + tmp = block[0][1]; + block[0][1] = block[1][1]; + block[1][1] = block[2][1]; + block[2][1] = block[3][1]; + block[3][1] = tmp; + + /* 2nd row rotated twice */ + tmp = block[0][2]; + block[0][2] = block[2][2]; + block[2][2] = tmp; + tmp = block[1][2]; + block[1][2] = block[3][2]; + block[3][2] = tmp; + + /* 3rd row rotated 3 times */ + tmp = block[0][3]; + block[0][3] = block[3][3]; + block[3][3] = block[2][3]; + block[2][3] = block[1][3]; + block[1][3] = tmp; +} + +static uint8_t xtime (uint8_t x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +/* mix columns */ + +static void aes_encode_mix (aes_block block) +{ + uint8_t i, tmp, tm, t; + + for(i = 0; i < 4; ++i) + { + t = block[i][0]; + tmp = block[i][0] ^ block[i][1] ^ block[i][2] ^ block[i][3] ; + tm = block[i][0] ^ block[i][1]; tm = xtime(tm); block[i][0] ^= tm ^ tmp; + tm = block[i][1] ^ block[i][2]; tm = xtime(tm); block[i][1] ^= tm ^ tmp; + tm = block[i][2] ^ block[i][3]; tm = xtime(tm); block[i][2] ^= tm ^ tmp; + tm = block[i][3] ^ t ; tm = xtime(tm); block[i][3] ^= tm ^ tmp; + } +} + +/* multiply is used to multiply numbers in the field GF(2^8) */ + +#define multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +/* mix columns */ + +static void aes_decode_mix (aes_block block) +{ + int i; + uint8_t a, b, c, d; + + for(i = 0; i < 4; ++i) + { + a = block[i][0]; + b = block[i][1]; + c = block[i][2]; + d = block[i][3]; + block[i][0] = multiply(a, 0x0e) ^ multiply(b, 0x0b) ^ multiply(c, 0x0d) ^ multiply(d, 0x09); + block[i][1] = multiply(a, 0x09) ^ multiply(b, 0x0e) ^ multiply(c, 0x0b) ^ multiply(d, 0x0d); + block[i][2] = multiply(a, 0x0d) ^ multiply(b, 0x09) ^ multiply(c, 0x0e) ^ multiply(d, 0x0b); + block[i][3] = multiply(a, 0x0b) ^ multiply(b, 0x0d) ^ multiply(c, 0x09) ^ multiply(d, 0x0e); + } +} + +/* inverse substitution */ + +static void aes_decode_sub (aes_block block) +{ + uint8_t i, j, v; + for(i = 0; i < 4; ++i) + for(j = 0; j < 4; ++j) + v = block[i][j], block[i][j] = rsbox[v]; +} + +/* inverse shift rows */ + +static void aes_decode_shift (aes_block block) +{ + uint8_t tmp; + + /* 1st row rotated once right */ + tmp = block[3][1]; + block[3][1] = block[2][1]; + block[2][1] = block[1][1]; + block[1][1] = block[0][1]; + block[0][1] = tmp; + + /* 2st row rotated twice right */ + tmp = block[0][2]; + block[0][2] = block[2][2]; + block[2][2] = tmp; + tmp = block[1][2]; + block[1][2] = block[3][2]; + block[3][2] = tmp; + + /* 3rd row rotated 3 times right */ + tmp = block[0][3]; + block[0][3] = block[1][3]; + block[1][3] = block[2][3]; + block[2][3] = block[3][3]; + block[3][3] = tmp; +} + +/* aes block encoder */ + +static void aes_encode_cipher (aes_state *state) +{ + uint8_t round; + aes_add_key(state->block, state->keyblock, 0); + for (round = 1; round < state->rounds; ++round) + { + aes_encode_sub(state->block); + aes_encode_shift(state->block); + aes_encode_mix(state->block); + aes_add_key(state->block, state->keyblock, round); + } + aes_encode_sub(state->block); + aes_encode_shift(state->block); + aes_add_key(state->block, state->keyblock, state->rounds); +} + +/* aes block decoder */ + +static void aes_decode_cipher (aes_state *state) +{ + uint8_t round; + aes_add_key(state->block, state->keyblock, state->rounds); + for(round = state->rounds - 1; round > 0; --round) + { + aes_decode_shift(state->block); + aes_decode_sub(state->block); + aes_add_key(state->block, state->keyblock, round); + aes_decode_mix(state->block); + } + aes_decode_shift(state->block); + aes_decode_sub(state->block); + aes_add_key(state->block, state->keyblock, 0); +} + +/* tail block padding; RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0; pdf spec p. 119 */ + +#define aes_padding(state) ((state->flags & AES_NULL_PADDING) == 0) + +static void aes_put_padding (aes_state *state, uint8_t length) +{ + uint8_t pad; + pad = (aes_padding(state)) ? 16 - length : 0; + for (; length < 16; ++length) + state->data[length] = state->iv[length] ^ pad; +} + +static int aes_remove_padding (aes_state *state, uint8_t *data, uint8_t *length) +{ + uint8_t pad; + *length = 16; /* block length 16 means leave intact */ + if (aes_padding(state)) + { + pad = data[16 - 1]; + if (pad > 16) + return IOFERR; + for ( ; *length > 16 - pad; --(*length)) + if (data[*length - 1] != pad) + return IOFERR; + } + else + { + for ( ; *length > 0; --(*length)) + if (data[*length - 1] != '\0') + break; + } + return IOFEOF; +} + +/* aes codec */ + +/* make the cipher on input xor-ed with iv, save the output as a new iv, write the output */ +#define aes_encode_output(state, output) \ + (aes_encode_cipher(state), aes_copy_block(state->iv, state->data), aes_copy_block(output, state->data), output += 16) + +iof_status aes_encode_state (iof *I, iof *O, aes_state *state) +{ + int c; + + if (!(state->flags & AES_HAS_IV)) // weird + return IOFERR; + if ((state->flags & AES_INLINE_IV) && !(state->flags & AES_CONTINUE)) + { /* write iv at the beginning of encrypted data */ + if (!iof_ensure(O, 16)) + return IOFFULL; + aes_copy_block(O->pos, state->iv); + O->pos += 16; + state->flags |= AES_CONTINUE; + } + while (iof_ensure(O, 16)) + { + while (state->buffered < 16) + { + if ((c = iof_get(I)) != IOFEOF) + { /* get input byte XORed with iv */ + state->data[state->buffered] = state->iv[state->buffered] ^ ((uint8_t)c); + ++state->buffered; + } + else + { + if (state->flush) + { + if (state->buffered > 0 || aes_padding(state)) + { /* pad the last input chunk; for input divisable by 16, add 16 bytes 0x0f */ + aes_put_padding(state, state->buffered); + state->buffered = 16; + aes_encode_output(state, O->pos); + } + return IOFEOF; + } + else + return IOFEMPTY; + } + } + aes_encode_output(state, O->pos); + state->buffered = 0; + } + return IOFFULL; +} + +/* write iv to the output, save the raw input just buffered as iv for the next chunk, make the cipher, write out xoring with iv */ +#define aes_decode_output(state, output) \ + (aes_copy_block(output, state->iv), aes_copy_block(state->iv, state->data), aes_decode_cipher(state), aes_copy_cbc(output, state->data), output += 16) + +iof_status aes_decode_state (iof *I, iof *O, aes_state *state) +{ + int c, ret; + uint8_t lastlength; + + if ((state->flags & AES_INLINE_IV) && !(state->flags & AES_CONTINUE)) + { + while (state->buffered < 16) + { + if ((c = iof_get(I)) != IOFEOF) + state->iv[state->buffered++] = (uint8_t)c; + else + return state->flush ? IOFERR : IOFEMPTY; + } + state->flags |= AES_CONTINUE|AES_HAS_IV; + state->buffered = 0; + } + while (iof_ensure(O, 16)) + { + while (state->buffered < 16) + { + if ((c = iof_get(I)) != IOFEOF) + state->data[state->buffered++] = (uint8_t)c; + else + return state->flush ? IOFERR : IOFEMPTY; + } + aes_decode_output(state, O->pos); + if (state->flush) + { /* we have to check for EOF here, to remove eventual padding */ + if ((c = iof_get(I)) < 0) + { /* end of input at 16-bytes boundary; remove padding and quit */ + ret = aes_remove_padding(state, O->pos - 16, &lastlength); + O->pos -= 16 - lastlength; + return ret; + } + else + { /* beginning of the next block */ + state->buffered = 1; + state->data[0] = (uint8_t)c; + } + } + else + state->buffered = 0; + } + return IOFFULL; +} + +/* variants that works on c-strings; can work inplace (output==input) except encoder in pdf flavour */ + +/* +Codecs operating on c-string can generally work inplace (output==input), except encoder with AES_INLINE_IV flag set, +which outputs 16 bytes of initialization vector at the beginning of encrypted data. All return the size of encrypted/decrypted +data. Encoders output is the original length padded to a complete 16 bytes (plus eventual 16 bytes of initialization +vector, if AES_INLINE_IV is used). Default padding is unambiguously removed during decryption. AES_NULL_PADDING flag +forces using (ambiguous) NULL-byte padding, only if input length module 16 is greater then zero. + +An input data is supposed to be a complete data to be encrypted or decrypted. It is possible, however, to use those +codecs for scaterred data chunks by manipulating AES_INLINE_IV, AES_NULL_PADDING, AES_CONTINUE flags and data length. +Caller may assume that c-string codecs do not modify state flags. + +Encoder could actually be optimized by writing an initialization vector to a state block once. After every chunk encryption, +the output is the initialization vector for the next chunk. Since we use c-string codec variants on short strings, +the gain is neglectable in comparison with the weight of the aes crypt procedure. +*/ + +size_t aes_encode_data (const void *input, size_t length, void *output, const void *key, size_t keylength, const void *iv, int flags) +{ + aes_state state; + aes_keyblock keyblock; + + if (aes_encode_initialize(&state, &keyblock, key, keylength, iv) == NULL) + return 0; + state.flags |= flags; + return aes_encode_state_data(&state, input, length, output); + // aes_state_close(&state); +} + +size_t aes_encode_state_data (aes_state *state, const void *input, size_t length, void *output) +{ + const uint8_t *inp; + uint8_t *out, tail, t; + size_t size; + + inp = (const uint8_t *)input; + out = (uint8_t *)output; + + if (!(state->flags & AES_HAS_IV)) + return 0; + if ((state->flags & AES_INLINE_IV) && !(state->flags & AES_CONTINUE)) + { + aes_copy_block(out, state->iv); + out += 16; + } + // state->flags |= AES_CONTINUE; // do not modify state flags + + for (size = 0; size + 16 <= length; size += 16) + { + aes_copy_xor(state->data, inp, state->iv); + aes_encode_output(state, out); + inp += 16; + } + + if ((tail = (length % 16)) > 0 || aes_padding(state)) + { + for (t = 0; t < tail; ++t) + state->data[t] = inp[t] ^ state->iv[t]; + aes_put_padding(state, tail); + aes_encode_output(state, out); + size += 16; + } + if (state->flags & AES_INLINE_IV) + size += 16; /* iv written at the beginning of encoded data */ + + return size; +} + +size_t aes_decode_data (const void *input, size_t length, void *output, const void *key, size_t keylength, const void *iv, int flags) +{ + aes_state state; + aes_keyblock keyblock; + + if (aes_decode_initialize(&state, &keyblock, key, keylength, iv) == NULL) + return 0; + state.flags |= flags; + return aes_decode_state_data(&state, input, length, output); + // aes_state_close(&state); +} + +size_t aes_decode_state_data (aes_state *state, const void *input, size_t length, void *output) +{ + const uint8_t *inp; + uint8_t *out, lastlength; + size_t size; + + inp = (const uint8_t *)input; + out = (uint8_t *)output; + + if ((state->flags & AES_INLINE_IV) && !(state->flags & AES_CONTINUE)) + { + aes_copy_block(state->iv, inp); + // state->flags |= AES_HAS_IV; // do not modify state flags + inp += 16; + length = length >= 16 ? length - 16 : 0; + } + else if (!(state->flags & AES_HAS_IV)) + return 0; + // state->flags |= AES_CONTINUE; // do not modify state flags + for (size = 0; size + 16 <= length; size += 16) + { + aes_copy_block(state->data, inp); + aes_decode_output(state, out); + inp += 16; + } + + if (size >= 16) + { + aes_remove_padding(state, out - 16, &lastlength); + size = size - 16 + lastlength; + } + + return size; +} + +/* +pseudo-random bytes chain exceprted from eexec; not expected to have strong cryptographic properties +we only expect that it is (reasonably) unique and different for each call (not only function call, but also +a program call). A current trick with mangling pointer value gives satisfactory results, generally different +for every function call and a programm call. Note that the pseudo-input bytes starts from some inner address +bits, as they vary better; without that, the first byte tends to be "lazy". +*/ + +void random_bytes (uint8_t *output, size_t size) +{ + size_t i; + uint8_t p; + static uint16_t k = 55665; + for (i = 0; i < size; ++i) + { + p = ((uint8_t *)(&output))[(i + 2) % sizeof(uint8_t *)] ^ (uint8_t)size; // pseudo input byte ;) + k = (((p + k) * 52845 + 22719) & 65535); // xor-ed with pseudo-random sequence (kept between calls) + output[i] = p ^ (k >> 8); + } +} + +void aes_generate_iv (uint8_t output[16]) +{ + random_bytes(output, 16); +} + +/* filters */ + +// rc4 decoder function + +static size_t rc4_decoder (iof *F, iof_mode mode) +{ + rc4_state *state; + iof_status status; + size_t tail; + + state = iof_filter_state(rc4_state *, F); + switch(mode) + { + case IOFLOAD: + case IOFREAD: + if (F->flags & IOF_STOPPED) + return 0; + tail = iof_tail(F); + F->pos = F->buf + tail; + F->end = F->buf + F->space; + do { + status = rc4_decode_state(F->next, F, state); + } while (mode == IOFLOAD && status == IOFFULL && iof_resize_buffer(F)); + return iof_decoder_retval(F, "rc4", status); + case IOFCLOSE: + rc4_state_close(state); + iof_free(F); + return 0; + default: + break; + } + return 0; +} + +// rc4 encoder function + +static size_t rc4_encoder (iof *F, iof_mode mode) +{ + rc4_state *state; + iof_status status; + + state = iof_filter_state(rc4_state *, F); + switch (mode) + { + case IOFFLUSH: + state->flush = 1; + FALLTHRU // fall through + case IOFWRITE: + F->end = F->pos; + F->pos = F->buf; + status = rc4_encode_state(F, F->next, state); + return iof_encoder_retval(F, "rc4", status); + case IOFCLOSE: + if (!state->flush) + rc4_encoder(F, IOFFLUSH); + rc4_state_close(state); + iof_free(F); + return 0; + default: + break; + } + return 0; +} + +// aes decoder function + +static size_t aes_decoder (iof *F, iof_mode mode) +{ + aes_state *state; + iof_status status; + size_t tail; + + state = iof_filter_state(aes_state *, F); + switch(mode) + { + case IOFLOAD: + case IOFREAD: + if (F->flags & IOF_STOPPED) + return 0; + tail = iof_tail(F); + F->pos = F->buf + tail; + F->end = F->buf + F->space; + do { + status = aes_decode_state(F->next, F, state); + } while (mode == IOFLOAD && status == IOFFULL && iof_resize_buffer(F)); + return iof_decoder_retval(F, "aes", status); + case IOFCLOSE: + aes_state_close(state); + iof_free(F); + return 0; + default: + break; + } + return 0; +} + +// aes encoder function + +static size_t aes_encoder (iof *F, iof_mode mode) +{ + aes_state *state; + iof_status status; + + state = iof_filter_state(aes_state *, F); + switch (mode) + { + case IOFFLUSH: + state->flush = 1; + FALLTHRU // fall through + case IOFWRITE: + F->end = F->pos; + F->pos = F->buf; + status = aes_encode_state(F, F->next, state); + return iof_encoder_retval(F, "aes", status); + case IOFCLOSE: + if (!state->flush) + aes_encoder(F, IOFFLUSH); + aes_state_close(state); + iof_free(F); + return 0; + default: + break; + } + return 0; +} + +iof * iof_filter_rc4_decoder (iof *N, const void *key, size_t keylength) +{ + iof *I; + crypt_state_pointer P; + + I = iof_filter_reader(rc4_decoder, sizeof(rc4_state), &P.voidstate); + iof_setup_next(I, N); + if (rc4_state_init(P.rc4state, key, keylength) == NULL) + { + iof_discard(I); + return NULL; + } + P.rc4state->flush = 1; + return I; +} + +iof * iof_filter_rc4_encoder (iof *N, const void *key, size_t keylength) +{ + iof *O; + crypt_state_pointer P; + + O = iof_filter_writer(rc4_encoder, sizeof(rc4_state), &P.voidstate); + iof_setup_next(O, N); + if (rc4_state_init(P.rc4state, key, keylength) == NULL) + { + iof_discard(O); + return NULL; + } + // P.rc4state->flush = 1; + return O; +} + +/* aes crypt filters */ + +iof * iof_filter_aes_decoder (iof *N, const void *key, size_t keylength) +{ + iof *I; + crypt_state_pointer P; + + I = iof_filter_reader(aes_decoder, sizeof(aes_state), &P.voidstate); + iof_setup_next(I, N); + if (aes_decode_init(P.aesstate, key, keylength) == NULL) + { + iof_discard(I); + return NULL; + } + aes_pdf_mode(P.aesstate); + P.aesstate->flush = 1; + return I; +} + +iof * iof_filter_aes_encoder (iof *N, const void *key, size_t keylength) +{ + iof *O; + crypt_state_pointer P; + + O = iof_filter_writer(aes_encoder, sizeof(aes_state), &P.voidstate); + iof_setup_next(O, N); + if (aes_encode_init(P.aesstate, key, keylength) == NULL) + { + iof_discard(O); + return NULL; + } + aes_pdf_mode(P.aesstate); + // P.aesstate->flush = 1; + return O; +} + +/* test */ + +/* +static void show (void *p, size_t size, uint8_t round, uint8_t sym) +{ + uint8_t i; + printf("%c%c:", round, sym); + for (i = 0; i < size; ++i) + printf("%02x", ((uint8_t *)p)[i]); + printf("\n"); +} + +void aes_test (void) +{ + const uint8_t key[] = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const uint8_t iv[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; + const uint8_t inp[] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 }; + const uint8_t out[] = { + 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46, 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d, + 0x50, 0x86, 0xcb, 0x9b, 0x50, 0x72, 0x19, 0xee, 0x95, 0xdb, 0x11, 0x3a, 0x91, 0x76, 0x78, 0xb2, + 0x73, 0xbe, 0xd6, 0xb8, 0xe3, 0xc1, 0x74, 0x3b, 0x71, 0x16, 0xe6, 0x9e, 0x22, 0x22, 0x95, 0x16, + 0x3f, 0xf1, 0xca, 0xa1, 0x68, 0x1f, 0xac, 0x09, 0x12, 0x0e, 0xca, 0x30, 0x75, 0x86, 0xe1, 0xa7 }; + + uint8_t input[64], output[64]; + size_t inpsize, outsize; + int flags = AES_NULL_PADDING; + + //////////////////////////////////////////////////////////////////////////// + +//#define ENCODETO output +#define ENCODETO input // inplace + + inpsize = 64; + memcpy(input, inp, inpsize); + show(input, inpsize, '>', '>'); + outsize = aes_encode_data(input, inpsize, ENCODETO, key, 16, iv, flags); + show(ENCODETO, outsize, '<', '<'); + if (outsize == inpsize && memcmp(ENCODETO, out, outsize) == 0) + printf("ENCODER SUCCESS\n"); + else + printf("ENCODER FAILURE\n"); + + //////////////////////////////////////////////////////////////////////////// + +//#define DECODETO input +#define DECODETO output // in place + + outsize = 64; + memcpy(output, out, outsize); + show(output, outsize, '<', '<'); + inpsize = aes_decode_data(output, outsize, DECODETO, key, 16, iv, flags); + show(DECODETO, inpsize, '>', '>'); + if (inpsize == outsize && memcmp(DECODETO, inp, inpsize) == 0) + printf("DECODER SUCCESS\n"); + else + printf("DECODER FAILURE\n"); +} +*/ + +/* +Some example vectors + +================================ AES ECB 128-bit encryption mode ================================ + +Encryption key: 2b7e151628aed2a6abf7158809cf4f3c + +Test vector Cipher text +6bc1bee22e409f96e93d7e117393172a 3ad77bb40d7a3660a89ecaf32466ef97 +ae2d8a571e03ac9c9eb76fac45af8e51 f5d3d58503b9699de785895a96fdbaaf +30c81c46a35ce411e5fbc1191a0a52ef 43b1cd7f598ece23881b00e3ed030688 +f69f2445df4f9b17ad2b417be66c3710 7b0c785e27e8ad3f8223207104725dd4 + + +================================ AES ECB 192-bit encryption mode ================================ + +Encryption key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b + +Test vector Cipher text +6bc1bee22e409f96e93d7e117393172a bd334f1d6e45f25ff712a214571fa5cc +ae2d8a571e03ac9c9eb76fac45af8e51 974104846d0ad3ad7734ecb3ecee4eef +30c81c46a35ce411e5fbc1191a0a52ef ef7afd2270e2e60adce0ba2face6444e +f69f2445df4f9b17ad2b417be66c3710 9a4b41ba738d6c72fb16691603c18e0e + + +================================ AES ECB 256-bit encryption mode ================================ + +Encryption key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4 + +Test vector Cipher text +6bc1bee22e409f96e93d7e117393172a f3eed1bdb5d2a03c064b5a7e3db181f8 +ae2d8a571e03ac9c9eb76fac45af8e51 591ccb10d410ed26dc5ba74a31362870 +30c81c46a35ce411e5fbc1191a0a52ef b6ed21b99ca6f4f9f153e7b1beafed1d +f69f2445df4f9b17ad2b417be66c3710 23304b7a39f9f3ff067d8d8f9e24ecc7 + +================================ AES CBC 128-bit encryption mode ================================ + +Encryption key: 2b7e151628aed2a6abf7158809cf4f3c + +Initialization vector Test vector Cipher text +000102030405060708090A0B0C0D0E0F 6bc1bee22e409f96e93d7e117393172a 7649abac8119b246cee98e9b12e9197d +7649ABAC8119B246CEE98E9B12E9197D ae2d8a571e03ac9c9eb76fac45af8e51 5086cb9b507219ee95db113a917678b2 +5086CB9B507219EE95DB113A917678B2 30c81c46a35ce411e5fbc1191a0a52ef 73bed6b8e3c1743b7116e69e22229516 +73BED6B8E3C1743B7116E69E22229516 f69f2445df4f9b17ad2b417be66c3710 3ff1caa1681fac09120eca307586e1a7 + +================================ AES CBC 192-bit encryption mode ================================ + +Encryption key: 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b + +Initialization vector Test vector Cipher text +000102030405060708090A0B0C0D0E0F 6bc1bee22e409f96e93d7e117393172a 4f021db243bc633d7178183a9fa071e8 +4F021DB243BC633D7178183A9FA071E8 ae2d8a571e03ac9c9eb76fac45af8e51 b4d9ada9ad7dedf4e5e738763f69145a +B4D9ADA9AD7DEDF4E5E738763F69145A 30c81c46a35ce411e5fbc1191a0a52ef 571b242012fb7ae07fa9baac3df102e0 +571B242012FB7AE07FA9BAAC3DF102E0 f69f2445df4f9b17ad2b417be66c3710 08b0e27988598881d920a9e64f5615cd + +================================ AES CBC 256-bit encryption mode ================================ + +Encryption key: 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4 + +Initialization vector Test vector Cipher text +000102030405060708090A0B0C0D0E0F 6bc1bee22e409f96e93d7e117393172a f58c4c04d6e5f1ba779eabfb5f7bfbd6 +F58C4C04D6E5F1BA779EABFB5F7BFBD6 ae2d8a571e03ac9c9eb76fac45af8e51 9cfc4e967edb808d679f777bc6702c7d +9CFC4E967EDB808D679F777BC6702C7D 30c81c46a35ce411e5fbc1191a0a52ef 39f23369a9d9bacfa530e26304231461 +39F23369A9D9BACFA530E26304231461 f69f2445df4f9b17ad2b417be66c3710 b2eb05e2c39be9fcda6c19078c6a9d1b +*/
\ No newline at end of file |