/* ============================================================================== Name : micro_aes.c Author : polfosol Version : 11 Copyright : copyright © 2022 - polfosol Description : ANSI-C compatible implementation of µAES ™ library. ============================================================================== */ #include "micro_aes.h" /*----------------------------------------------------------------------------*\ Constants and MACROs \*----------------------------------------------------------------------------*/ enum basic_constants { BLOCKSIZE = 128 / 8, /* Block length in AES is 'always' 128-bits. */ KEYSIZE = AES_KEYLENGTH, Nb = BLOCKSIZE / 4, /* number of columns comprising a AES state. */ Nk = KEYSIZE / 4, /* number of 32 bit words in a key. */ ROUNDS = Nk + 6, /* number of rounds in AES Cipher. */ HB = BLOCKSIZE / 2, /* length of a half-block unit. */ LAST = BLOCKSIZE - 1, /* index of last element (LSB) in a block. */ MIDST = LAST / 2, /* index of half-block LSB. */ PL = BLOCKSIZE + 1 /* length of arithmetic blocks in poly-1305. */ }; #define IMPLEMENT(x) (x) > 0 #define INCREASE_SECURITY 0 /* see the comments at the bottom of header. */ #define INLINE_SUBROUTINES 0 #define SMALL_CIPHER 0 /** Lookup-tables are "static constant", so that they can be placed in read-only * storage instead of RAM. They can be computed dynamically trading ROM for RAM. * This may be useful in (embedded) bootloader applications, where ROM is often * limited. Note that sbox[y] = x, if and only if rsbox[x] = y. For more details * on dynamic sbox computation, see https://en.wikipedia.org/wiki/Rijndael_S-box */ static const char sbox[256] = "c|w{\362ko\3050\01g+\376\327\253v\312\202\311}""\372YG\360\255\324\242\257" "\234\244r\300\267\375\223&6\?\367\3144\245\345\361q\3301\25\4\307#\303\030" "\226\5\232\a\22\200\342\353\'\262u\t\203,\32\33nZ\240R;\326\263)\343/\204S" "\321\0\355 \374\261[j\313\2769JLX\317\320\357\252\373CM3\205E\371\02\177P<" "\237\250Q\243@\217\222\2358\365\274\266\332!\20\377\363\322\315\f\023\354_" "\227D\27\304\247~=d]\31s`\201O\334\"*\220\210F\356\270\24\336^\v\333\3402:" "\nI\06$\\\302\323\254b\221\225\344y\347\3107m\215\325N\251lV\364\352ez\256" "\b\272x%.\034\246\264\306\350\335t\37K\275\213\212p>\265fH\3\366\16a5W\271" "\206\301\035\236\341\370\230\21i\331\216\224\233\036\207\351\316U(\337\214" "\241\211\r\277\346BhA\231-\17\260T\273\26"; #if DECRYPTION static const char rsbox[256] = "R\tj\32506\2458\277@\243\236\201\363\327\373|\3439\202\233/\377\2074\216CD" "\304\336\351\313T{\2242\246\302#=\356L\225\vB\372\303N\b.\241f(\331$\262v[" "\242Im\213\321%r\370\366d\206h\230\026\324\244\\\314]e\266\222lpHP\375\355" "\271\332^\25FW\247\215\235\204\220\330\253\0\214\274\323\n\367\344X\05\270" "\263E\6\320,\036\217\312?\17\2\301\257\275\3\1\023\212k:\221\21AOg\334\352" "\227\362\317\316\360\264\346s\226\254t\"\347\2555\205\342\3717\350\34u\337" "nG\361\32q\35)\305\211o\267b\16\252\30\276\33\374V>K\306\322y \232\333\300" "\376x\315Z\364\037\335\2503\210\a\3071\261\22\20Y\'\200\354_`Q\177\251\031" "\265J\r-\345z\237\223\311\234\357\240\340;M\256*\365\260\310\353\273<\203S" "\231a\027+\004~\272w\326&\341i\024cU!\f}"; #endif /*----------------------------------------------------------------------------*\ Data types and private variables \*----------------------------------------------------------------------------*/ /** The array that stores all round keys during the AES key-expansion process */ static uint8_t RoundKey[BLOCKSIZE * ROUNDS + KEYSIZE]; /** block_t indicates fixed-size memory blocks, and state_t represents the state * matrix. note that state[i][j] means the i-th COLUMN and j-th ROW of matrix */ typedef uint8_t block_t[BLOCKSIZE]; typedef uint8_t state_t[Nb][4]; /*----------------------------------------------------------------------------*\ Auxiliary functions for the Rijndael algorithm \*----------------------------------------------------------------------------*/ #define SBoxValue(x) ( sbox[x]) #define InvSBoxValue(x) (rsbox[x]) /* omitted dynamic s-box calculation */ #define COPYDWORD(x, y) *(int32_t*) &y = *(int32_t*) &x #define XOR32BITS(x, y) *(int32_t*) &y ^= *(int32_t*) &x #if INLINE_SUBROUTINES /** note: 'long long' type is NOT supported in C89. so this may throw errors: */ #define xorBlock(x, y) \ ( \ *(long long*) &(y)[0] ^= *(long long const*) &(x)[0], \ *(long long*) &(y)[8] ^= *(long long const*) &(x)[8] \ ) #define xtime(x) ((x) & 0x80 ? (x) * 2 ^ 0x11b : (x) << 1) #define mixG8(a, b, c, d) b ^ c ^ d ^ \ xtime(a ^ b ^ xtime(a ^ c ^ xtime(a ^ b ^ c ^ d ))) #else /** XOR two 128bit blocks, i.e. add two numbers in Galois bit field GF(2^128) */ static void xorBlock( const block_t src, block_t dest ) { uint8_t i; for (i = 0; i < BLOCKSIZE; ++i) /* many CPUs have single instruction */ { /* such as XORPS for 128-bit-xor. */ dest[i] ^= src[i]; /* see the file: x86-improvements */ } } /** doubling in GF(2^8): left-shift and if carry bit is set, xor it with 0x1b */ static uint8_t xtime( uint8_t x ) { return (x > 0x7f) * 0x1b ^ (x << 1); } #if DECRYPTION /** inverse multiply in 8bit GF: mul(a,14) ^ mul(b,11) ^ mul(c,13) ^ mul(d,9) */ static uint8_t mixG8( uint8_t a, uint8_t b, uint8_t c, uint8_t d ) { b ^= a; d ^= b ^ c; c ^= a; a ^= d; c ^= xtime( d ); b ^= xtime( c ); a ^= xtime( b ); return a; /* or use (9 11 13 14) lookup tables */ } #endif #endif /*----------------------------------------------------------------------------*\ Main functions for the Rijndael encryption algorithm \*----------------------------------------------------------------------------*/ /** This function produces (ROUNDS+1) round keys, which are used in each round * to encrypt/decrypt the intermediate states. First round key is the main key * itself, and other rounds are constructed from the previous ones as follows */ static void KeyExpansion( const uint8_t* key ) { uint8_t rcon = 1, i; memcpy( RoundKey, key, KEYSIZE ); for (i = KEYSIZE; i < BLOCKSIZE * (ROUNDS + 1); i += 4) { switch (i % KEYSIZE) { case 0: memcpy( &RoundKey[i], &RoundKey[i - KEYSIZE], KEYSIZE ); if (4 / Nk && !rcon) { rcon = 0x1b; /* RCON reaches 0 only in AES-128, when Nk=4 */ } RoundKey[i ] ^= SBoxValue( RoundKey[i - 3] ) ^ rcon; RoundKey[i + 1] ^= SBoxValue( RoundKey[i - 2] ); RoundKey[i + 2] ^= SBoxValue( RoundKey[i - 1] ); RoundKey[i + 3] ^= SBoxValue( RoundKey[i - 4] ); rcon <<= 1; break; #if AES___== 256 case 48 - KEYSIZE: RoundKey[i ] ^= SBoxValue( RoundKey[i - 4] ); RoundKey[i + 1] ^= SBoxValue( RoundKey[i - 3] ); RoundKey[i + 2] ^= SBoxValue( RoundKey[i - 2] ); RoundKey[i + 3] ^= SBoxValue( RoundKey[i - 1] ); break; #endif default: XOR32BITS( RoundKey[ i - 4 ], RoundKey[ i ] ); break; } } } /** XOR the round keys with the rijndael state matrix; namely, add them in GF */ static void AddRoundKey( const uint8_t round, block_t state ) { xorBlock( RoundKey + BLOCKSIZE * round, state ); } /** Substitute values in the state matrix with associated values in the S-box */ static void SubBytes( block_t state ) { uint8_t i; for (i = 0; i < BLOCKSIZE; ++i) { state[i] = SBoxValue( state[i] ); } } /** Shift/rotate the rows of the state matrix to the left. Each row is shifted * with a different offset (= Row number). So the "zeroth" row is not shifted */ static void ShiftRows( state_t state ) { uint8_t tmp = state[0][1]; state[0][1] = state[1][1]; state[1][1] = state[2][1]; state[2][1] = state[3][1]; state[3][1] = tmp; /* the first row rotates 1 column(s) to left */ tmp = state[0][2]; state[0][2] = state[2][2]; state[2][2] = tmp; tmp = state[1][2]; state[1][2] = state[3][2]; state[3][2] = tmp; /* the second row rotates 2 columns to left, */ tmp = state[0][3]; state[0][3] = state[3][3]; state[3][3] = state[2][3]; state[2][3] = state[1][3]; state[1][3] = tmp; /* and the 3rd row rotates 3 columns to left */ } /** Use matrix multiplication in Galois field to mix the columns of the state */ static void MixColumns( state_t state ) { uint8_t C[4], i; for (i = 0; i < Nb; ++i) /*-> https://crypto.stackexchange.com/q/2402 */ { COPYDWORD( state[i], C[0] ); C[3] ^= C[1]; C[1] ^= C[0]; C[0] ^= C[2]; C[2] = xtime( C[0] ); C[0] ^= xtime( C[1] ); C[1] = xtime( C[3] ); state[i][0] ^= C[0] ^= C[3]; state[i][1] ^= C[0] ^= C[2]; state[i][2] ^= C[0] ^= C[1]; state[i][3] ^= C[0] ^= C[2]; } } /** Encrypt a plaintext input block and save the result/ciphertext as output. */ static void rijndaelEncrypt( const block_t input, block_t output ) { uint8_t r; state_t* mat = (void*) output; /* copy plaintext into the state matrix, and beware of undefined behavior */ if (input != output) memcpy( mat, input, BLOCKSIZE ); /* The encryption is carried out in #ROUNDS iterations, of which the first * #ROUNDS-1 are identical. The last round doesn't involve mixing columns */ for (r = 0; r != ROUNDS; ) { AddRoundKey( r, output ); SubBytes( output ); ShiftRows( *mat ); ++r != ROUNDS ? MixColumns( *mat ) : AddRoundKey( ROUNDS, output ); } } /*----------------------------------------------------------------------------*\ Block-decryption part of the Rijndael algorithm \*----------------------------------------------------------------------------*/ #if IMPLEMENT(DECRYPTION) /** Substitutes the values in state matrix with values of the inverted S-box. */ static void InvSubBytes( block_t state ) { uint8_t i; for (i = 0; i < BLOCKSIZE; ++i) { state[i] = InvSBoxValue( state[i] ); } } /** This function shifts (i.e rotates) the rows of the state matrix to right. */ static void InvShiftRows( state_t state ) { uint8_t tmp = state[3][1]; state[3][1] = state[2][1]; state[2][1] = state[1][1]; state[1][1] = state[0][1]; state[0][1] = tmp; /* the first row rotates 1 columns to right, */ tmp = state[0][2]; state[0][2] = state[2][2]; state[2][2] = tmp; tmp = state[1][2]; state[1][2] = state[3][2]; state[3][2] = tmp; /* the second row rotates 2 columns to right */ tmp = state[0][3]; state[0][3] = state[1][3]; state[1][3] = state[2][3]; state[2][3] = state[3][3]; state[3][3] = tmp; /* the third row rotates 3 columns to right. */ } /** Reverse the process of mixing columns by matrix multiplication in GF(2^8) */ static void InvMixColumns( state_t state ) { uint8_t C[4], i; for (i = 0; i < Nb; ++i) /*-> https://crypto.stackexchange.com/q/2569 */ { COPYDWORD( state[i], C[0] ); state[i][0] = mixG8( C[0], C[1], C[2], C[3] ); state[i][1] = mixG8( C[1], C[2], C[3], C[0] ); state[i][2] = mixG8( C[2], C[3], C[0], C[1] ); state[i][3] = mixG8( C[3], C[0], C[1], C[2] ); } } /** Decrypt a ciphertext input block and save the result/plaintext to output. */ static void rijndaelDecrypt( const block_t input, block_t output ) { uint8_t r; state_t* mat = (void*) output; /* copy input to the state matrix, i.e initialize the state by ciphertext */ if (input != output) memcpy( mat, input, BLOCKSIZE ); /* Decryption is carried out in #ROUNDS iterations. The rounds are similar * except for the first one which doesn't involve [reverse]mixing columns */ for (r = ROUNDS; r != 0; ) { r-- != ROUNDS ? InvMixColumns( *mat ) : AddRoundKey( ROUNDS, output ); InvShiftRows( *mat ); InvSubBytes( output ); AddRoundKey( r, output ); } } #endif /* DECRYPTION */ #if MICRO_RJNDL /** * @brief encrypt or decrypt a single block with a given key * @param key a byte array with a fixed size of KEYSIZE * @param mode mode of operation: 'E' (1) to encrypt, 'D' (0) to decrypt * @param x input byte array with BLOCKSIZE bytes * @param y output byte array with BLOCKSIZE bytes */ void AES_Cipher( const uint8_t* key, const char mode, const block_t x, block_t y ) { KeyExpansion( key ); mode & 1 ? rijndaelEncrypt( x, y ) : rijndaelDecrypt( x, y ); } #endif /*----------------------------------------------------------------------------*\ * Implementation of different block ciphers modes * * Definitions & Auxiliary Functions * \*----------------------------------------------------------------------------*/ /** function-pointer types, indicating functions that take fixed-size blocks: */ typedef void (*fmix_t)( const block_t, block_t ) SDCC_REENT; typedef void (*fdouble_t)( block_t ); #define AES_setkey(key) KeyExpansion( key ) #if INCREASE_SECURITY #define BURN(key) memset( key, 0, sizeof key ) #define SABOTAGE(buf, len) memset( buf, 0, len ) #if AEAD_MODES /** for constant-time comparison of memory blocks, to avoid "timing attacks". */ static uint8_t memcmp_s( const void* src, const void* dest, const uint8_t len ) { const volatile char *p1 = src, *p2 = (const volatile char*) dest; volatile uint8_t result = 0; char i; for (i = 0; i != len; i++) { result |= p1[i] ^ p2[i]; } return result; } #endif #else #define BURN(key) (void) key /* the line will be ignored */ #define SABOTAGE(buf, len) (void) buf #define memcmp_s memcmp #endif #if SMALL_CIPHER typedef uint8_t count_t; #define incBlock(block, index) ++block[index] #define xorBEint(buf, num, pos) buf[pos - 1] ^= (num) >> 8, buf[pos] ^= num #define copyLint(buf, num, pos) buf[pos + 1] = (num) >> 8, buf[pos] = num #else typedef size_t count_t; #if XTS || GCM_SIV /** copy a little endian integer to the block, with LSB at specified position */ static void copyLint( block_t block, size_t num, uint8_t pos ) { do block[pos++] = (uint8_t) num; while (num >>= 8); } #endif #if CTR || KWA || FPE /** xor a byte array with a big-endian integer, whose LSB is at specified pos */ static void xorBEint( uint8_t* buff, size_t num, uint8_t pos ) { do buff[pos--] ^= (uint8_t) num; while (num >>= 8); } #endif #if CTR /** increment the value of a 128-bit counter block, regarding its endian-ness */ static void incBlock( block_t block, uint8_t index ) { do /* increment the LSBs, */ if (++block[index]) /* ..until no overflow */ break; while ((index < 4 && ++index < 4) || --index > 8); } #endif #endif /* SMALL CIPHER */ #if EAX && !EAXP || SIV || OCB || CMAC /** Multiply a block by two in Galois bit field GF(2^128): big-endian version */ static void doubleBblock( block_t array ) { int c = 0, i; for (i = BLOCKSIZE; i > 0; c >>= 8) /* from last byte (LSB) to */ { /* first: left-shift, then */ c |= array[--i] << 1; /* append the previous MSBit */ array[i] = (uint8_t) c; } /* if first MSBit is carried */ array[LAST] ^= c * 0x87; /* .. A ^= 10000111b (B.E.) */ } #endif #if XTS || EAXP /** Multiply a block by two in Galois field GF(2^128): little-endian version. */ static void doubleLblock( block_t array ) { int i, c = 0; for (i = 0; i < BLOCKSIZE; c >>= 8) /* the same as doubleBblock */ { /* ..but with reversed bytes */ c |= array[i] << 1; array[i++] = (uint8_t) c; } array[0] ^= c * 0x87; /* A ^= 10000111b (L.E.) */ } #endif #if GCM /** Divide a 128-bit big-endian integer by two in Galois bit field GF(2^128). */ static void divideBblock( block_t array ) { unsigned i, c = 0; for (i = 0; i < BLOCKSIZE; ++i) /* from first to last byte, */ { /* prepend the previous LSB */ c = c << 8 | array[i]; /* then shift it to right. */ array[i] = c >> 1; } /* if block is odd (LSB = 1) */ if (c & 1) array[0] ^= 0xe1; /* .. A ^= 11100001b << 120 */ } /** Multiply two 128-bit numbers (big-endian blocks) in the Galois bit field. */ static void mulGF128( const block_t x, block_t y ) { uint8_t b, i; block_t result = { 0 }; /* working memory */ for (i = 0; i < BLOCKSIZE; ++i) { for (b = 0x80; b; b >>= 1) /* check all the bits of X, */ { if (x[i] & b) /* ..and if any bit is set, */ { xorBlock( y, result ); /* ..add Y to the result */ } divideBblock( y ); /* Y_next = (Y / 2) in GF */ } } memcpy( y, result, sizeof result ); /* result is saved into y */ } #endif /* GCM */ #if GCM_SIV /** Divide a block by two in 128-bit Galois field: the little-endian version. */ static void divideLblock( block_t array ) { unsigned c = 0, i; for (i = BLOCKSIZE; i--; ) /* similar to divideBblock ↑ */ { /* ..but with reversed bytes */ c = c << 8 | array[i]; array[i] = c >> 1; } if (c & 1) array[LAST] ^= 0xe1; /* (L.E) A ^= 11100001b<<120 */ } /** The so-called "dot multiplying" in GF(2^128), used in POLYVAL calculation */ static void dotGF128( const block_t x, block_t y ) { uint8_t b, i; block_t result = { 0 }; for (i = BLOCKSIZE; i--; ) { for (b = 0x80; b; b >>= 1) /* pretty much the same as */ { /* ..(reversed) mulGF128 */ divideLblock( y ); if (x[i] & b) { xorBlock( y, result ); } } } memcpy( y, result, sizeof result ); /* result is saved into y */ } #endif /* GCM-SIV */ #if CTR || CFB || OFB || CTS || OCB /** mix/cipher the block B and then xor the result with n bytes of X to get Y */ static void mixThenXor( fmix_t mix, const block_t B, block_t f, const uint8_t* X, uint8_t n, uint8_t* Y ) { if (n == 0) return; mix( B, f ); /* Y = f(B) ^ X */ while (n--) { Y[n] = f[n] ^ X[n]; } } #endif #if AEAD_MODES || FPE /** xor the result with input data and then apply the digest/mixing function. * repeat this process for each block of data, until all blocks are digested. */ static void xMac( const void* data, const size_t dataSize, const block_t seed, fmix_t mix, block_t result ) { uint8_t const* x; count_t n = dataSize / BLOCKSIZE; /* number of full blocks */ for (x = data; n--; x += BLOCKSIZE) { xorBlock( x, result ); /* M_next = mix(seed, M ^ X) */ mix( seed, result ); } if ((n = dataSize % BLOCKSIZE) > 0) /* if any partial block left */ { while (n--) { result[n] ^= x[n]; } mix( seed, result ); } } #endif #if CMAC || SIV || EAX || OCB /** calculate CMAC of input data using pre-calculated keys: K1 (D) and K2 (Q) */ static void cMac( const block_t K1, const block_t K2, const void* data, const size_t dataSize, block_t mac ) { const uint8_t s = dataSize ? (dataSize - 1) % BLOCKSIZE + 1 : 0; const uint8_t *k = K1, *ps = s ? (uint8_t*) data + dataSize - s : &s; xMac( data, dataSize - s, mac, &rijndaelEncrypt, mac ); if (s < BLOCKSIZE) { mac[s] ^= 0x80; /* pad( M_last; K1, K2 ) */ k = K2; } xorBlock( k, mac ); xMac( ps, s + !s, mac, &rijndaelEncrypt, mac ); } /** calculate key-dependent constants D and Q using a given doubling function */ static void getSubkeys( fdouble_t gfdouble, const char quad, const uint8_t* key, block_t D, block_t Q ) { AES_setkey( key ); rijndaelEncrypt( D, D ); /* H or L_* = Enc(zeros) */ if (quad) { gfdouble( D ); /* D or L_$ = double(L_*) */ } memcpy( Q, D, BLOCKSIZE ); gfdouble( Q ); /* Q or L_0 = double(L_$) */ } #endif #ifdef AES_PADDING /** in ECB mode & CBC without CTS, the last (partial) block has to be padded. */ static char padBlock( const uint8_t len, block_t block ) { uint8_t n = BLOCKSIZE - len, *p = &block[len]; #if AES_PADDING memset( p, n * (AES_PADDING != 2), n ); *p ^= (0x80) * (AES_PADDING == 2); /* either PKCS#7 / IEC7816-4 */ #else memset( p, 0, n % BLOCKSIZE ); /* default (zero) padding */ #endif return len || AES_PADDING; } #endif /*----------------------------------------------------------------------------*\ ECB-AES (electronic codebook mode) functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(ECB) /** * @brief encrypt the input plaintext using ECB-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer */ void AES_ECB_encrypt( const uint8_t* key, const void* pntxt, const size_t ptextLen, void* crtxt ) { uint8_t* y; count_t n = ptextLen / BLOCKSIZE; /* number of full blocks */ memcpy( crtxt, pntxt, ptextLen ); /* copy plaintext to output */ AES_setkey( key ); for (y = crtxt; n--; y += BLOCKSIZE) { rijndaelEncrypt( y, y ); /* C = Enc(P) */ } if (padBlock( ptextLen % BLOCKSIZE, y )) { rijndaelEncrypt( y, y ); } BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using ECB-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer * @return error if the ciphertext has a partial block */ char AES_ECB_decrypt( const uint8_t* key, const void* crtxt, const size_t crtxtLen, void* pntxt ) { uint8_t* y; count_t n = crtxtLen / BLOCKSIZE; memcpy( pntxt, crtxt, crtxtLen ); /* do in-place decryption */ AES_setkey( key ); for (y = pntxt; n--; y += BLOCKSIZE) { rijndaelDecrypt( y, y ); /* P = Dec(C) */ } BURN( RoundKey ); /* if padding is enabled, check whether the result is properly padded. error * must be thrown if it's not. here we skip that and just check the size. */ return crtxtLen % BLOCKSIZE ? M_DECRYPTION_ERROR : M_RESULT_SUCCESS; } #endif /* ECB */ /*----------------------------------------------------------------------------*\ CBC-AES (cipher block chaining) functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(CBC) /** * @brief encrypt the input plaintext using CBC-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer * @return error in CTS mode, if plaintext is a single partial block */ char AES_CBC_encrypt( const uint8_t* key, const block_t iVec, const void* pntxt, const size_t ptextLen, void* crtxt ) { uint8_t const* iv = iVec; uint8_t r = ptextLen % BLOCKSIZE, *y; count_t n = ptextLen / BLOCKSIZE; #if CTS if (n > 1 && !r && --n) r = BLOCKSIZE; /* CS3 ciphertext stealing */ if (n == 0) return M_DATALENGTH_ERROR; /* data size >= BLOCKSIZE */ #endif memcpy( crtxt, pntxt, ptextLen ); /* do in-place encryption */ AES_setkey( key ); for (y = crtxt; n--; y += BLOCKSIZE) { xorBlock( iv, y ); /* C = Enc(IV ^ P) */ rijndaelEncrypt( y, y ); /* IV_next = C */ iv = y; } #if CTS if (r) { block_t L = { 0 }; memcpy( L, y, r ); /* backup the last chunk */ memcpy( y, y - BLOCKSIZE, r ); /* 'steal' the cipher-text */ y -= BLOCKSIZE; /* ..to fill the last chunk */ iv = L; #else if (padBlock( r, y )) { #endif xorBlock( iv, y ); rijndaelEncrypt( y, y ); } BURN( RoundKey ); return M_RESULT_SUCCESS; } /** * @brief decrypt the input ciphertext using CBC-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer * @return error if the size of ciphertext is not a valid value */ char AES_CBC_decrypt( const uint8_t* key, const block_t iVec, const void* crtxt, const size_t crtxtLen, void* pntxt ) { uint8_t const *x = crtxt, *iv = iVec; uint8_t r = crtxtLen % BLOCKSIZE, *y; count_t n = crtxtLen / BLOCKSIZE; #if CTS if (n > 1 && !r && --n) r = BLOCKSIZE; if (n == 0) return M_DATALENGTH_ERROR; #else if (r != 0) return M_DATALENGTH_ERROR; #endif n -= r > 0; /* hold last 2 blocks in CTS */ AES_setkey( key ); for (y = pntxt; n--; y += BLOCKSIZE) { rijndaelDecrypt( x, y ); /* P = Dec(C) ^ IV */ xorBlock( iv, y ); /* IV_next = C */ iv = x; x += BLOCKSIZE; #if CTS } if (r) { /* last two blocks: {X, Z} */ const uint8_t* z = x + BLOCKSIZE; mixThenXor( &rijndaelDecrypt, x, y, z, r, y + BLOCKSIZE ); memcpy( y, z, r ); rijndaelDecrypt( y, y ); /* P2 = Z ^ Dec(X) = Z ^ Y */ xorBlock( iv, y ); /* P1 = IV ^ Dec(Z | *Y) */ #endif } BURN( RoundKey ); return M_RESULT_SUCCESS; } #endif /* CBC */ /*----------------------------------------------------------------------------*\ CFB-AES (cipher feedback) functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(CFB) /** * @brief the general scheme of CFB-AES block-ciphering algorithm * @param key encryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param mode mode of operation: (1) to encrypt, (0) to decrypt * @param input buffer of the input plain/cipher-text * @param dataSize size of input in bytes * @param output buffer of the resulting cipher/plain-text */ static void CFB_cipher( const uint8_t* key, const block_t iVec, const char mode, const void* input, const size_t dataSize, void* output ) { uint8_t const *iv = iVec, *x = input; uint8_t* y; block_t tmp; count_t n = dataSize / BLOCKSIZE; /* number of full blocks */ AES_setkey( key ); for (y = output; n--; y += BLOCKSIZE) { rijndaelEncrypt( iv, y ); /* both in en[de]cryption: */ xorBlock( x, y ); /* Y = Enc(IV) ^ X */ iv = mode ? y : x; /* IV_next = Ciphertext */ x += BLOCKSIZE; } mixThenXor( &rijndaelEncrypt, iv, tmp, x, dataSize % BLOCKSIZE, y ); BURN( RoundKey ); } /** * @brief encrypt the input plaintext using CFB-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer */ void AES_CFB_encrypt( const uint8_t* key, const block_t iVec, const void* pntxt, const size_t ptextLen, void* crtxt ) { CFB_cipher( key, iVec, 1, pntxt, ptextLen, crtxt ); } /** * @brief decrypt the input ciphertext using CFB-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer */ void AES_CFB_decrypt( const uint8_t* key, const block_t iVec, const void* crtxt, const size_t crtxtLen, void* pntxt ) { CFB_cipher( key, iVec, 0, crtxt, crtxtLen, pntxt ); } #endif /* CFB */ /*----------------------------------------------------------------------------*\ OFB-AES (output feedback) functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(OFB) /** * @brief encrypt the input plaintext using OFB-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer */ void AES_OFB_encrypt( const uint8_t* key, const block_t iVec, const void* pntxt, const size_t ptextLen, void* crtxt ) { count_t n = ptextLen / BLOCKSIZE; uint8_t* y; block_t iv; memcpy( iv, iVec, sizeof iv ); memcpy( crtxt, pntxt, ptextLen ); /* i.e. in-place encryption */ AES_setkey( key ); for (y = crtxt; n--; y += BLOCKSIZE) { rijndaelEncrypt( iv, iv ); /* IV_next = Enc(IV) */ xorBlock( iv, y ); /* C = IV_next ^ P */ } mixThenXor( &rijndaelEncrypt, iv, iv, y, ptextLen % BLOCKSIZE, y ); BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using OFB-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param iVec initialization vector * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer */ void AES_OFB_decrypt( const uint8_t* key, const block_t iVec, const void* crtxt, const size_t crtxtLen, void* pntxt ) { AES_OFB_encrypt( key, iVec, crtxt, crtxtLen, pntxt ); } #endif /* OFB */ /*----------------------------------------------------------------------------*\ Parallelizable, counter-based modes of AES: demonstrating the main idea + How to use it in a simple, non-authenticated API \*----------------------------------------------------------------------------*/ #if CTR enum ctr_based_modes { CTR_DEFAULT, SIV_CTR = 5, /* RFC-5297 */ SIVGCM_CTR = 8, /* RFC-8452 (GCM-SIV) */ CCM_GCM = 2 /* either CCM or GCM */ }; /** * @brief the general scheme of operation in block-counter mode * @param iCtr initialized counter block * @param mode specifies the "counter based" block-cipher or AEAD mode * @param input buffer of the input plain/cipher-text * @param dataSize size of input in bytes * @param output buffer of the resulting cipher/plain-text */ static void CTR_cipher( const block_t iCtr, const char mode, const void* input, const size_t dataSize, void* output ) { block_t c, enc; count_t n = dataSize / BLOCKSIZE; uint8_t index = LAST, *y; memcpy( output, input, dataSize ); /* do in-place en/decryption */ memcpy( c, iCtr, sizeof c ); switch (mode) { case SIV_CTR: c[+8] &= 0x7F; /* clear 2 bits in SIV mode */ c[12] &= 0x7F; break; case SIVGCM_CTR: c[LAST] |= 0x80; /* set 1 bit of L.E counter */ index = 0; break; case CCM_GCM: incBlock( c, index ); /* pre-increment in CCM/GCM */ break; } for (y = output; n--; y += BLOCKSIZE) { rijndaelEncrypt( c, enc ); /* both in en[de]cryption: */ xorBlock( enc, y ); /* Y = Enc(Ctr) ^ X */ incBlock( c, index ); /* Ctr_next = Ctr + 1 */ } mixThenXor( &rijndaelEncrypt, c, c, y, dataSize % BLOCKSIZE, y ); } #endif #if IMPLEMENT(CTR_NA) /** * @brief encrypt the input plaintext using CTR-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param iv initialization vector a.k.a. nonce * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer */ void AES_CTR_encrypt( const uint8_t* key, const uint8_t* iv, const void* pntxt, const size_t ptextLen, void* crtxt ) { #if PRESET_COUNTER uint8_t const* ctr = iv; /* block is pre-initialized */ #else block_t ctr = { 0 }; memcpy( ctr, iv, CTR_IV_LENGTH ); xorBEint( ctr, CTR_START_VALUE, LAST ); /* initialize the counter */ #endif AES_setkey( key ); CTR_cipher( ctr, CTR_DEFAULT, pntxt, ptextLen, crtxt ); BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using CTR-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param iv initialization vector a.k.a. nonce * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer */ void AES_CTR_decrypt( const uint8_t* key, const uint8_t* iv, const void* crtxt, const size_t crtxtLen, void* pntxt ) { AES_CTR_encrypt( key, iv, crtxt, crtxtLen, pntxt ); } #endif /* CTR */ /*----------------------------------------------------------------------------*\ XEX-AES based modes (xor-encrypt-xor): the basic idea + main functions of XTS-AES (XEX Tweaked-codebook with ciphertext Stealing) \*----------------------------------------------------------------------------*/ #if IMPLEMENT(XTS) /** * @brief encrypt or decrypt a data unit with XTS method * @param keypair pair of encryption keys, each one has KEYSIZE bytes * @param mode mode of operation: encrypting (1) or decrypting (0) * @param tweak data unit identifier block, similar to nonce in CTR mode * @param sectid sector id: in case of a null tweak, use this instead * @param dataSize size of input data, to be encrypted/decrypted * @param storage result of encryption/decryption process */ static void XTS_cipher( const uint8_t* keypair, const char mode, const block_t tweak, const size_t sectid, const size_t dataSize, void* storage ) { fmix_t cipher = mode ? &rijndaelEncrypt : &rijndaelDecrypt; uint8_t r = dataSize % BLOCKSIZE, *y; count_t n = dataSize / BLOCKSIZE - (r > 0); block_t T; if (tweak == NULL) { /* the `i` block is either */ memset( T, 0, sizeof T ); /* ..a little-endian number */ copyLint( T, sectid, 0 ); /* ..or a byte array (tweak) */ } else { memcpy( T, tweak, sizeof T ); } AES_setkey( keypair + KEYSIZE ); /* T = encrypt `i` with key2 */ rijndaelEncrypt( T, T ); AES_setkey( keypair ); /* now key1 is cipher key */ for (y = storage; n--; y += BLOCKSIZE) { /* XEX: xor-encrypt-xor */ xorBlock( T, y ); cipher( y, y ); xorBlock( T, y ); /* Y = T ^ Cipher( T ^ X ) */ doubleLblock( T ); /* T_next = alpha · T */ } if (r) { /* XTS: ciphertext stealing */ block_t L; memcpy( L, T, sizeof L ); doubleLblock( mode ? T : L ); /* T = α·L or L = α·T */ xorBlock( L, y ); cipher( y, y ); xorBlock( L, y ); memcpy( L, y, sizeof L ); memcpy( y, y + BLOCKSIZE, r ); /* 'steal' the cipher-text */ memcpy( y + BLOCKSIZE, L, r ); /* ..to fill the last chunk */ xorBlock( T, y ); cipher( y, y ); /* encrypt or decrypt the */ xorBlock( T, y ); /* ..block we stole from */ } BURN( RoundKey ); } /** * @brief encrypt the input plaintext using XTS-AES block-cipher method * @param keys two-part encryption key with a fixed size of 2*KEYSIZE * @param tweak tweak bytes of data unit, a.k.a sector ID (little-endian) * @param pntxt input plaintext buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer * @return error if plaintext is a single partial block */ char AES_XTS_encrypt( const uint8_t* keys, const uint8_t* tweak, const void* pntxt, const size_t ptextLen, void* crtxt ) { if (ptextLen < BLOCKSIZE) return M_DATALENGTH_ERROR; memcpy( crtxt, pntxt, ptextLen ); /* do in-place encryption */ XTS_cipher( keys, 1, tweak, 0, ptextLen, crtxt ); return M_RESULT_SUCCESS; } /** * @brief encrypt the input ciphertext using XTS-AES block-cipher method * @param keys two-part encryption key with a fixed size of 2*KEYSIZE * @param tweak tweak bytes of data unit, a.k.a sector ID (little-endian) * @param crtxt input ciphertext buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer * @return error if ciphertext is a single partial block */ char AES_XTS_decrypt( const uint8_t* keys, const uint8_t* tweak, const void* crtxt, const size_t crtxtLen, void* pntxt ) { if (crtxtLen < BLOCKSIZE) return M_DATALENGTH_ERROR; memcpy( pntxt, crtxt, crtxtLen ); /* in-place decryption */ XTS_cipher( keys, 0, tweak, 0, crtxtLen, pntxt ); return M_RESULT_SUCCESS; } #endif /* XTS */ /*----------------------------------------------------------------------------*\ CMAC-AES (cipher-based message authentication code): main function \*----------------------------------------------------------------------------*/ #if IMPLEMENT(CMAC) /** * @brief derive the AES-CMAC of input data using an encryption key * @param key AES encryption key * @param data buffer of input data * @param dataSize size of data in bytes * @param mac calculated CMAC hash */ void AES_CMAC( const uint8_t* key, const void* data, const size_t dataSize, block_t mac ) { block_t K1 = { 0 }, K2; memcpy( mac, K1, sizeof K1 ); /* initialize mac */ getSubkeys( &doubleBblock, 1, key, K1, K2 ); cMac( K1, K2, data, dataSize, mac ); BURN( RoundKey ); } #endif /* CMAC */ /*----------------------------------------------------------------------------*\ GCM-AES (Galois counter mode): authentication with GMAC & main functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(GCM) /** calculate G-Hash of ciphertext and AAD using an authentication subkey `H` */ static void gHash( const block_t H, const void* aData, const void* crtxt, const size_t aDataLen, const size_t crtxtLen, block_t gh ) { block_t len = { 0 }; xorBEint( len, aDataLen * 8, MIDST ); xorBEint( len, crtxtLen * 8, LAST ); /* save bit-sizes into len */ xMac( aData, aDataLen, H, &mulGF128, gh ); /* first digest AAD, then */ xMac( crtxt, crtxtLen, H, &mulGF128, gh ); /* ..ciphertext, and then */ xMac( len, sizeof len, H, &mulGF128, gh ); /* ..bit sizes into GHash */ } /** encrypt zeros to get authentication subkey H, and prepare the IV for GCM. */ static void GCMsetup( const uint8_t* key, const uint8_t* nonce, block_t auKey, block_t iv ) { AES_setkey( key ); rijndaelEncrypt( auKey, auKey ); /* auKey = Enc( zero block ) */ if (GCM_NONCE_LEN != 12) { gHash( auKey, NULL, nonce, 0, GCM_NONCE_LEN, iv ); return; } memcpy( iv, nonce, 12 ); iv[LAST] = 1; } /** * @brief encrypt the input plaintext using GCM-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: GCM_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer + message authentication tag */ void AES_GCM_encrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* pntxt, const size_t ptextLen, void* crtxt ) { uint8_t* tag = (uint8_t*) crtxt + ptextLen; block_t iv = { 0 }, H = { 0 }, G = { 0 }; GCMsetup( key, nonce, H, iv ); /* get IV & auth. subkey H */ CTR_cipher( iv, CCM_GCM, pntxt, ptextLen, crtxt ); rijndaelEncrypt( iv, iv ); BURN( RoundKey ); gHash( H, aData, crtxt, aDataLen, ptextLen, G ); xorBlock( iv, G ); memcpy( tag, G, GCM_TAG_LEN ); /* GMAC = Enc(iv) ^ G-HASH */ } /** * @brief decrypt the input ciphertext using GCM-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: GCM_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer + appended authentication tag * @param crtxtLen size of ciphertext, excluding tag * @param pntxt resulting plaintext buffer * @return whether message authentication/decryption was successful */ char AES_GCM_decrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* crtxt, const size_t crtxtLen, void* pntxt ) { uint8_t const* tag = (uint8_t*) crtxt + crtxtLen; block_t H = { 0 }, iv = { 0 }, G = { 0 }; GCMsetup( key, nonce, H, iv ); gHash( H, aData, crtxt, aDataLen, crtxtLen, G ); rijndaelEncrypt( iv, H ); xorBlock( H, G ); /* tag = Enc(iv) ^ G-HASH */ if (memcmp_s( tag, G, GCM_TAG_LEN )) { /* compare tags and */ BURN( RoundKey ); /* ..proceed if they match */ return M_AUTHENTICATION_ERROR; } CTR_cipher( iv, CCM_GCM, crtxt, crtxtLen, pntxt ); BURN( RoundKey ); return M_RESULT_SUCCESS; } #endif /* GCM */ /*----------------------------------------------------------------------------*\ CCM-AES (counter with CBC-MAC): CBC-MAC authentication & main functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(CCM) /** this function calculates the CBC-MAC of plaintext and authentication data */ static void CCMtag( const block_t iv, const void* aData, const void* pntxt, const size_t aDataLen, const size_t ptextLen, block_t M ) { block_t A = { 0 }; uint8_t p = 1, s = 0; memcpy( M, iv, BLOCKSIZE ); /* initialize CBC-MAC */ M[0] |= (CCM_TAG_LEN - 2) << 2; /* set some flags on M_* */ xorBEint( M, ptextLen, LAST ); /* copy data size into M_* */ if (aDataLen) /* construct the 'A' block */ { M[0] |= 0x40; rijndaelEncrypt( M, M ); /* flag M_* and encrypt it */ if (aDataLen > 0xFEFFL) { /* assuming aDataLen < 2^32 */ p += 4; A[0] = 0xFF, A[1] = 0xFE; /* prepend FFFE to aDataLen */ } xorBEint( A, aDataLen, p ); /* copy aDataLen into A */ s = sizeof A - ++p; memcpy( A + p, aData, aDataLen < s ? aDataLen : s ); } /* digest the first s bytes of aData, the rest of it, and then plaintext: */ xMac( A, sizeof A, M, &rijndaelEncrypt, M ); if (aDataLen > s) { xMac( (char*) aData + s, aDataLen - s, M, &rijndaelEncrypt, M ); } xMac( pntxt, ptextLen, M, &rijndaelEncrypt, M ); rijndaelEncrypt( iv, A ); /* tag = Enc(iv) ^ CBCMac */ xorBlock( A, M ); } /** * @brief encrypt the input plaintext using CCM-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: CCM_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer + message authentication tag */ void AES_CCM_encrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* pntxt, const size_t ptextLen, void* crtxt ) { uint8_t* tag = (uint8_t*) crtxt + ptextLen; block_t iv = { 14 - CCM_NONCE_LEN, 0 }, C; memcpy( iv + 1, nonce, CCM_NONCE_LEN ); AES_setkey( key ); CCMtag( iv, aData, pntxt, aDataLen, ptextLen, C ); CTR_cipher( iv, CCM_GCM, pntxt, ptextLen, crtxt ); BURN( RoundKey ); memcpy( tag, C, CCM_TAG_LEN ); } /** * @brief decrypt the input ciphertext using CCM-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: CCM_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer + appended authentication tag * @param crtxtLen size of ciphertext, excluding tag * @param pntxt resulting plaintext buffer * @return whether message decryption/authentication was successful */ char AES_CCM_decrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* crtxt, const size_t crtxtLen, void* pntxt ) { uint8_t const* tag = (uint8_t*) crtxt + crtxtLen; block_t iv = { 14 - CCM_NONCE_LEN, 0 }, C; memcpy( iv + 1, nonce, CCM_NONCE_LEN ); AES_setkey( key ); CTR_cipher( iv, CCM_GCM, crtxt, crtxtLen, pntxt ); CCMtag( iv, aData, pntxt, aDataLen, crtxtLen, C ); BURN( RoundKey ); if (memcmp_s( tag, C, CCM_TAG_LEN )) { SABOTAGE( pntxt, crtxtLen ); /* invalid tag: clear pntxt */ return M_AUTHENTICATION_ERROR; } return M_RESULT_SUCCESS; } #endif /* CCM */ /*----------------------------------------------------------------------------*\ SIV-AES (synthetic init-vector): nonce synthesis & main functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(SIV) /** calculate the CMAC* of AAD unit(s), then plaintext, and synthesize the IV */ static void S2V( const uint8_t* key, const void* aData, const void* pntxt, const size_t aDataLen, const size_t ptextLen, block_t IV ) { block_t K[2] = { { 0 } }, Y; uint8_t r, *Q = K[1]; memcpy( IV, *K, BLOCKSIZE ); /* initialize/clear IV */ getSubkeys( &doubleBblock, 1, key, *K, Q ); rijndaelEncrypt( *K, Y ); /* Y_0 = CMAC(zero block) */ /* in case of multiple AAD units, each one must be handled in a similar way. * for example, let aData be a 2-D array and aDataLen a null-terminated one. * then instead of `if (aDataLen) { cMac(*K, Q, aData, aDataLen, IV)` write: * for (r = 0; *aDataLen; ) { cMac( *K, Q, aData[r++], *aDataLen++, IV ); */ if (aDataLen) { cMac( *K, Q, aData, aDataLen, IV ); doubleBblock( Y ); /* Y_$ = double( Y_{i-1} ) */ xorBlock( IV, Y ); /* Y_i = Y_$ ^ CMAC(AAD_i) */ memset( IV, 0, BLOCKSIZE ); } if (ptextLen < sizeof Y) { /* for short messages: */ doubleBblock( Y ); /* Y = double( Y_n ) */ r = 0; } else if ((r = ptextLen % sizeof Y) > 0) { memset( *K, 0, BLOCKSIZE ); } xorBlock( Y, *K + r ); cMac( *K, *K, pntxt, ptextLen - r, IV ); /* CMAC*( Y xor_end MSG ) */ if (r == 0) return; cMac( NULL, Q, (char*) pntxt + ptextLen - r, r, IV ); } /** * @brief encrypt the input plaintext using SIV-AES block-cipher method * @param keys two-part encryption key with a fixed size of 2*KEYSIZE * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param iv synthesized I.V block, typically prepended to ciphertext * @param crtxt resulting cipher-text buffer */ void AES_SIV_encrypt( const uint8_t* keys, const void* aData, const size_t aDataLen, const void* pntxt, const size_t ptextLen, block_t iv, void* crtxt ) { S2V( keys, aData, pntxt, aDataLen, ptextLen, iv ); AES_setkey( keys + KEYSIZE ); CTR_cipher( iv, SIV_CTR, pntxt, ptextLen, crtxt ); BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using SIV-AES block-cipher method * @param keys two-part encryption key with a fixed size of 2*KEYSIZE * @param iv provided I.V block to validate * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer * @param crtxtLen size of ciphertext in bytes * @param pntxt resulting plaintext buffer * @return whether message decryption/authentication was successful */ char AES_SIV_decrypt( const uint8_t* keys, const block_t iv, const void* aData, const size_t aDataLen, const void* crtxt, const size_t crtxtLen, void* pntxt ) { block_t IV; AES_setkey( keys + KEYSIZE ); CTR_cipher( iv, SIV_CTR, crtxt, crtxtLen, pntxt ); S2V( keys, aData, pntxt, aDataLen, crtxtLen, IV ); BURN( RoundKey ); if (memcmp_s( IV, iv, sizeof IV )) /* verify the synthesized IV */ { SABOTAGE( pntxt, crtxtLen ); return M_AUTHENTICATION_ERROR; } return M_RESULT_SUCCESS; } #endif /* SIV */ /*----------------------------------------------------------------------------*\ SIV-GCM-AES (Galois counter mode with synthetic i.v) authentication & i.v synthesis with polyval + main functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(GCM_SIV) /** calculate the POLYVAL of plaintext and AAD using authentication subkey H. */ static void polyval( const block_t H, const void* aData, const void* pntxt, const size_t aDataLen, const size_t ptextLen, block_t pv ) { block_t len = { 0 }; /* save bit-sizes into len */ copyLint( len, aDataLen * 8, 0 ); copyLint( len, ptextLen * 8, HB ); xMac( aData, aDataLen, H, &dotGF128, pv ); /* first digest AAD, then */ xMac( pntxt, ptextLen, H, &dotGF128, pv ); /* ..plaintext, and then */ xMac( len, sizeof len, H, &dotGF128, pv ); /* ..bit sizes into POLYVAL */ } /** derive the pair of authentication-encryption-keys from main key and nonce */ static void GCM_SIVsetup( const uint8_t* key, const uint8_t* nonce, block_t AK ) { uint8_t iv[5 * HB + KEYSIZE], *h, *k; k = h = iv + BLOCKSIZE; memcpy( iv + 4, nonce, SIVGCM_NONCE_LEN ); AES_setkey( key ); for (*(int32_t*) iv = 0; *iv < 2 + Nk / 2; ++*iv) { rijndaelEncrypt( iv, k ); /* encrypt & take half, then */ k += HB; /* ..increment iv's LSB */ } AES_setkey( k - KEYSIZE ); /* set the main cipher-key */ memcpy( AK, h, BLOCKSIZE ); /* take authentication key */ } /** get the tag in GCM-SIV mode, given the nonce and calculated POLYVAL block */ static void GCM_SIVtag( const uint8_t* nonce, block_t pv, block_t tag ) { XOR32BITS( nonce[0], pv[0] ); XOR32BITS( nonce[4], pv[4] ); /* xor POLYVAL with nonce */ XOR32BITS( nonce[8], pv[8] ); pv[LAST] &= 0x7F; /* clear one bit & encrypt, */ rijndaelEncrypt( pv, tag ); } /** * @brief encrypt the input plaintext using SIV-GCM-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param nonce provided 96-bit nonce * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer + message authentication tag */ void GCM_SIV_encrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* pntxt, const size_t ptextLen, void* crtxt ) { block_t P = { 0 }; uint8_t *H, *tag = (uint8_t*) crtxt + ptextLen; H = tag; /* use a pre-allocated space */ GCM_SIVsetup( key, nonce, H ); polyval( H, aData, pntxt, aDataLen, ptextLen, P ); GCM_SIVtag( nonce, P, tag ); CTR_cipher( tag, SIVGCM_CTR, pntxt, ptextLen, crtxt ); BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using SIV-GCM-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param nonce provided 96-bit nonce * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer + appended authentication tag * @param crtxtLen size of ciphertext, excluding tag * @param pntxt resulting plaintext buffer * @return whether message decryption/authentication was successful */ char GCM_SIV_decrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* crtxt, const size_t crtxtLen, void* pntxt ) { block_t H, P = { 0 }; uint8_t const* tag = (uint8_t*) crtxt + crtxtLen; GCM_SIVsetup( key, nonce, H ); /* get authentication subkey */ CTR_cipher( tag, SIVGCM_CTR, crtxt, crtxtLen, pntxt ); polyval( H, aData, pntxt, aDataLen, crtxtLen, P ); GCM_SIVtag( nonce, P, P ); BURN( RoundKey ); if (memcmp_s( tag, P, SIVGCM_TAG_LEN )) { /* tag verification failed */ SABOTAGE( pntxt, crtxtLen ); return M_AUTHENTICATION_ERROR; } return M_RESULT_SUCCESS; } #endif /* GCM-SIV */ /*----------------------------------------------------------------------------*\ EAX-AES (encrypt-then-authenticate-then-translate): OMAC & main functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(EAX) #if EAXP #define GFDOUBLE doubleLblock #else #define GFDOUBLE doubleBblock #define nonceLen EAX_NONCE_LEN #endif /** this function calculates the OMAC of a data array using D (K1) and Q (K2) */ static void oMac( const uint8_t t, const block_t D, const block_t Q, const void* data, const size_t dataSize, block_t mac ) { #if EAXP if (!dataSize && t) { memset( mac, 0, BLOCKSIZE ); return; /* ignoring null ciphertext */ } memcpy( mac, t ? Q : D, BLOCKSIZE ); #else dataSize ? memset( mac, 0, BLOCKSIZE ) : memcpy( mac, D, BLOCKSIZE ); mac[LAST] ^= t; rijndaelEncrypt( mac, mac ); if (dataSize == 0) return; /* then OMAC = CMAC( [t]_n ) */ #endif cMac( D, Q, data, dataSize, mac ); } /** * @brief encrypt the input plaintext using EAX-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param nonce initialization vector with EAX_NONCE_LEN bytes unless EAX' * @param nonceLen size of the nonce byte array; should be non-zero in EAX' * @param aData additional authentication data; for EAX only, not EAX' * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param crtxt ciphertext result + auth. tag (4 bytes mac in EAX') */ void AES_EAX_encrypt( const uint8_t* key, const uint8_t* nonce, #if EAXP const size_t nonceLen, #else const void* aData, const size_t aDataLen, #endif const void* pntxt, const size_t ptextLen, void* crtxt ) { block_t D = { 0 }, Q, mac; #if !EAXP block_t tag; #endif uint8_t* auth = (uint8_t*) crtxt + ptextLen; getSubkeys( &GFDOUBLE, 1, key, D, Q ); oMac( 0, D, Q, nonce, nonceLen, mac ); /* N = OMAC(0; nonce) */ #if EAXP COPYDWORD( mac[12], *auth ); mac[12] &= 0x7F; /* clear 2 bits to get N' */ mac[14] &= 0x7F; #endif CTR_cipher( mac, CTR_DEFAULT, pntxt, ptextLen, crtxt ); #if EAXP oMac( 2, D, Q, crtxt, ptextLen, mac ); /* C' = CMAC'( ciphertext ) */ XOR32BITS( mac[12], *auth ); /* tag (mac) = N ^ C' */ #else oMac( 1, D, Q, aData, aDataLen, tag ); /* H = OMAC(1; adata) */ xorBlock( mac, tag ); oMac( 2, D, Q, crtxt, ptextLen, mac ); /* C = OMAC(2; ciphertext) */ xorBlock( mac, tag ); /* tag = N ^ H ^ C */ memcpy( auth, tag, EAX_TAG_LEN ); #endif BURN( RoundKey ); } /** * @brief decrypt the input ciphertext using EAX-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param nonce initialization vector with EAX_NONCE_LEN bytes unless EAX' * @param nonceLen size of the nonce byte array; should be non-zero in EAX' * @param aData additional authentication data; for EAX only, not EAX' * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer + appended authentication tag * @param crtxtLen size of cipher-text; excluding tag / 4-bytes mac in EAX' * @param pntxt resulting plaintext buffer * @return whether message authentication/decryption was successful */ char AES_EAX_decrypt( const uint8_t* key, const uint8_t* nonce, #if EAXP const size_t nonceLen, #else const void* aData, const size_t aDataLen, #endif const void* crtxt, const size_t crtxtLen, void* pntxt ) { block_t D = { 0 }, Q, mac, tag; uint8_t const* auth = (uint8_t*) crtxt + crtxtLen; getSubkeys( &GFDOUBLE, 1, key, D, Q ); oMac( 2, D, Q, crtxt, crtxtLen, tag ); /* C = OMAC(2; ciphertext) */ #if EAXP oMac( 0, D, Q, nonce, nonceLen, mac ); /* N = CMAC'( nonce ) */ XOR32BITS( auth[0], tag[12] ); XOR32BITS( mac[12], tag[12] ); mac[12] &= 0x7F; mac[14] &= 0x7F; /* clear 2 bits to get N' */ if (*(int32_t*) &tag[12] != 0) /* result of mac validation */ #else oMac( 1, D, Q, aData, aDataLen, mac ); /* H = OMAC(1; adata) */ xorBlock( mac, tag ); oMac( 0, D, Q, nonce, nonceLen, mac ); /* N = OMAC(0; nonce) */ xorBlock( mac, tag ); /* tag = N ^ H ^ C */ if (memcmp_s( auth, tag, EAX_TAG_LEN )) #endif { /* authenticate then decrypt */ BURN( RoundKey ); return M_AUTHENTICATION_ERROR; } CTR_cipher( mac, CTR_DEFAULT, crtxt, crtxtLen, pntxt ); BURN( RoundKey ); return M_RESULT_SUCCESS; } #endif /* EAX */ /*----------------------------------------------------------------------------*\ OCB-AES (offset codebook mode): auxiliary and main functions + how to parallelize it by independent calculation of the offset blocks \*----------------------------------------------------------------------------*/ #if IMPLEMENT(OCB) static void nop( const block_t x, block_t y ) {} /** Calculate the offset block (Δ_i) at a specified index, given the initial Δ_0 * and L$ blocks. This method has minimum memory usage, but it might be slow. To * make it faster, pre-calculate all L_{i}s (avoid doubling inside the loop). */ static void getDelta( const count_t index, const block_t Ld, const block_t delta0, block_t delta ) { count_t mask, bit = 1; block_t L; /* Δ_i = Δ_{i-1} ^ L_ntz(i) */ memcpy( L, Ld, sizeof L ); /* initialize L_$ and Δ */ memcpy( delta, delta0, BLOCKSIZE ); while ((mask = index - bit) < index) { /* L_0 = double( L_$ ), */ doubleBblock( L ); /* L_k = double( L_{k-1} ) */ mask &= bit <<= 1; if (!mask) { xorBlock( L, delta ); } } } /** * @brief encrypt or decrypt the data using OCB method * @param key encryption or decryption key * @param nonce byte array with a fixed size of OCB_NONCE_LEN * @param ptextLen size of input 'plain'text; will be zero in decryption * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param dataSize size of input data, to be encrypted/decrypted * @param data result of encryption/decryption process * @param tag calculated tag from plaintext checksum, and PMAC of aData */ static void OCB_cipher( const uint8_t* key, const uint8_t* nonce, const size_t ptextLen, const void* aData, const size_t aDataLen, const size_t dataSize, void* data, block_t tag ) { fmix_t cipher = ptextLen ? &rijndaelEncrypt : &rijndaelDecrypt; block_t offset[4] = { { 0 } }; /* [L_$] [L_*] [Ktop] [Δ_n] */ uint8_t *y; uint8_t *Ld = offset[0], *Ls = offset[1], *kt = offset[2], *del = offset[3]; count_t n = nonce[OCB_NONCE_LEN - 1] % 64, i; uint8_t const s = 8 - n % 8, *x = kt + n / 8; memcpy( kt + BLOCKSIZE - OCB_NONCE_LEN, nonce, OCB_NONCE_LEN ); kt[0 ] |= OCB_TAG_LEN << 4; kt[LAST - OCB_NONCE_LEN] |= 1; /* set one and clear last */ kt[LAST] &= 0xC0; /* ..six bits of nonce (kt) */ getSubkeys( &doubleBblock, 0, key, Ls, Ld ); rijndaelEncrypt( kt, kt ); /* construct K_top */ memcpy( del, kt + 1, HB ); /* stretch K_top */ for (i = 0; i < BLOCKSIZE; ++i) { del[i] ^= kt[i]; kt[i] = (x[i] << 8 | x[i + 1]) >> s; /* shift the stretched K_top */ } xMac( data, ptextLen, NULL, &nop, tag ); /* get plaintext? checksum */ i = 0, n = dataSize / BLOCKSIZE; for (y = data; i < n; y += BLOCKSIZE) { /* calculate Δ_i using */ getDelta( ++i, Ld, kt, del ); /* ..my 'magic' algorithm */ xorBlock( del, y ); cipher( y, y ); xorBlock( del, y ); /* Y = Δ_i ^ Cipher(Δ_i ^ X) */ } if (n == 0) { del = kt; /* Δ_N = Δ_0 = K_top */ kt = offset[3]; } if ((i = dataSize % BLOCKSIZE) != 0) { /* Y_* = Enc(L_* ^ Δ_N) ^ X */ tag[i] ^= 0x80; /* and pad X_* or checksum */ xorBlock( Ls, del ); mixThenXor( &rijndaelEncrypt, del, kt, y, i, y ); } xMac( data, dataSize - ptextLen, NULL, &nop, tag ); cMac( Ld, NULL, del, BLOCKSIZE, tag ); /*- tag = Enc( checksum ^ Δ_* ^ L_$ ) so far. next, add "PMAC" of aData: **/ i = 0, n = aDataLen / BLOCKSIZE; for (x = aData; i < n; x += BLOCKSIZE) { getDelta( ++i, Ld, x, del ); rijndaelEncrypt( del, del ); /* Δ = Enc( A_i ^ Δ_i ) */ xorBlock( del, tag ); /* add Δ to the tag */ } if ((i = aDataLen % BLOCKSIZE) != 0) { memset( kt, 0, BLOCKSIZE ); getDelta( n, Ld, kt, del ); cMac( NULL, Ls, x, i, del ); /* Δ = Enc(L_* ^ A_* ^ Δ_N) */ xorBlock( del, tag ); /* add Δ to the tag */ } BURN( RoundKey ); } /** * @brief encrypt the input stream using OCB-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: OCB_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param pntxt input plain-text buffer * @param ptextLen size of plaintext in bytes * @param crtxt resulting cipher-text buffer + message authentication tag */ void AES_OCB_encrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* pntxt, const size_t ptextLen, void* crtxt ) { block_t tag = { 0 }; uint8_t* auth = (uint8_t*) crtxt + ptextLen; memcpy( crtxt, pntxt, ptextLen ); /* doing in-place encryption */ OCB_cipher( key, nonce, ptextLen, aData, aDataLen, ptextLen, crtxt, tag ); memcpy( auth, tag, OCB_TAG_LEN ); } /** * @brief decrypt the input stream using OCB-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param nonce a.k.a initialization vector with fixed size: OCB_NONCE_LEN * @param aData additional authentication data * @param aDataLen size of additional authentication data * @param crtxt input cipher-text buffer + appended authentication tag * @param crtxtLen size of ciphertext, excluding tag * @param pntxt resulting plaintext buffer * @return whether message decryption/authentication was successful */ char AES_OCB_decrypt( const uint8_t* key, const uint8_t* nonce, const void* aData, const size_t aDataLen, const void* crtxt, const size_t crtxtLen, void* pntxt ) { block_t tag = { 0 }; uint8_t const* auth = (uint8_t*) crtxt + crtxtLen; memcpy( pntxt, crtxt, crtxtLen ); /* doing in-place decryption */ OCB_cipher( key, nonce, 0, aData, aDataLen, crtxtLen, pntxt, tag ); if (memcmp_s( auth, tag, OCB_TAG_LEN )) { SABOTAGE( pntxt, crtxtLen ); return M_AUTHENTICATION_ERROR; } return M_RESULT_SUCCESS; } #endif /* OCB */ /*----------------------------------------------------------------------------*\ KW-AES: Main functions for AES key-wrapping (RFC-3394) \*----------------------------------------------------------------------------*/ #if IMPLEMENT(KWA) /** * @brief encrypt the input secret with key-wrapping method * @param kek key-encryption-key a.k.a master key * @param secret input plaintext secret * @param secretLen size of secret in bytes * @param wrapped wrapped secret, prepended with an additional half-block * @return error if size is not a multiple of HB, or size < BLOCKSIZE */ char AES_KEY_wrap( const uint8_t* kek, const void* secret, const size_t secretLen, void* wrapped ) { uint8_t *w = wrapped; uint8_t *r = w + HB, *endpoint = w + secretLen; block_t A; count_t i, n = secretLen / HB; /* number of semi-blocks */ if (n < 2 || secretLen % HB) return M_DATALENGTH_ERROR; memset( A, 0xA6, HB ); /* initialization vector */ memcpy( r, secret, secretLen ); /* in-place encryption */ AES_setkey( kek ); for (i = 0, n *= 6; i++ < n; ) { memcpy( A + HB, r, HB ); /* R = LSB(64, A) */ rijndaelEncrypt( A, A ); /* A* = Enc( L | R ) */ memcpy( r, A + HB, HB ); /* L* = MSB(64, A*) ^ i */ xorBEint( A, i, MIDST ); r = (r == endpoint ? w : r) + HB; } BURN( RoundKey ); memcpy( w, A, HB ); /* authentication vector */ return M_RESULT_SUCCESS; } /** * @brief unwrap/decrypt a wrapped secret according to RFC-3394 * @param kek key-encryption-key a.k.a master key * @param wrapped cipher-text input, the wrapped secret * @param wrapLen size of ciphertext/wrapped input in bytes * @param secret unwrapped secret, which is a half block shorter than input * @return whether decrypting/unwrapping secret was successful */ char AES_KEY_unwrap( const uint8_t* kek, const void* wrapped, const size_t wrapLen, void* secret ) { uint8_t const* w = wrapped; uint8_t *r = secret, *end = (uint8_t*) secret + wrapLen - HB; block_t A; count_t i, n = wrapLen / HB; /* number of semi-blocks */ if (n < 0x3 || wrapLen % HB) return M_DATALENGTH_ERROR; memcpy( A, w, HB ); memcpy( r, w + HB, wrapLen - HB ); /* in-place decryption */ AES_setkey( kek ); for (--n, i = n * 6; i; --i) { r = (r == secret ? end : r) - HB; xorBEint( A, i, MIDST ); memcpy( A + HB, r, HB ); /* L = MSB(64, A) ^ i */ rijndaelDecrypt( A, A ); /* A* = Dec( L | R ) */ memcpy( r, A + HB, HB ); /* R* = LSB(64, A*) */ } BURN( RoundKey ); for (n = 0, i = HB; i--; ) /* authenticate/error-check */ { n |= A[i] - 0xA6; } return n ? M_AUTHENTICATION_ERROR : M_RESULT_SUCCESS; } #endif /* KWA */ /*----------------------------------------------------------------------------*\ Poly1305-AES message authentication: auxiliary functions and main API \*----------------------------------------------------------------------------*/ #if IMPLEMENT(POLY1305) /** add two little-endian blocks x and y up to length len, so that: y = y + x */ static void addLblocks( const uint8_t* x, const uint8_t len, uint8_t* y ) { int a, i; for (i = a = 0; i < len; a >>= 8) { a += x[i] + y[i]; y[i++] = (uint8_t) a; } } /** derive modulo(2^130-5) for a large integer saved in a little-endian block */ static void modP1305( uint8_t* block, int32_t q ) { if (q < 4) return; /* q is block's MSBs: B>>128 */ block[PL - 1] &= 3; /* subtract (q >> 2) << 130 */ for (q = (q >> 2) * 5; q; q >>= 8) /* suppose Q = B / (2 ^ 130) */ { /* ..then "almost" always: */ q += *block; /* mod = B - Q * (2^130-5) */ *block++ = (uint8_t) q; /* so subtract Q * 2^130 and */ } /* ..then add (Q * 5) := q */ } /** modular multiplication of two little-endian poly1305 blocks: y *= x mod P */ static void mulLblocks( const uint8_t* x, uint8_t* y ) { uint8_t n = PL, result[PL] = { 0 }; while (n--) { /* Y = [Y_0][Y_1]...[Y_16], */ uint8_t s = 8 * (n != 0), i; /* multiply X by MSB of Y */ int32_t m = 0, y_n = y[n]; /* ..and add to the result */ for (i = 0; i < sizeof result; ++i) /* if Y has another byte in */ { /* ..queue, shift the result */ m >>= 8; /* ..but don't shift for Y_0 */ m += (y_n * x[i] + result[i]) << s; result[i] = (uint8_t) m; } modP1305( result, m ); /* modular multiplication */ } memcpy( y, result, sizeof result ); } /** * @brief derive the Poly1305-AES mac of message using a nonce and key pair. * @param keys pair of encryption/mixing keys (k, r); size = KEYSIZE + 16 * @param nonce a 128 bit string which is encrypted by AES_k * @param data buffer of input data * @param dataSize size of data in bytes * @param mac calculated Poly1305-AES mac */ void AES_Poly1305( const uint8_t* keys, const block_t nonce, const void* data, const size_t dataSize, block_t mac ) { uint8_t r[PL], rk[PL] = { 1 }, c[PL] = { 0 }, poly[PL] = { 0 }, s = PL - 1; count_t q = (dataSize - 1) / BLOCKSIZE; const char* pos = (const char*) data + dataSize; AES_setkey( keys ); rijndaelEncrypt( nonce, mac ); /* derive AES_k(nonce) */ BURN( RoundKey ); if (!dataSize) return; memcpy( r, keys + KEYSIZE, s ); /* extract r from (K,r) pair */ for (r[s] = 0; s > 3; s -= 3) { r[s--] &= 0xFC; /* clear bottom 2 bits */ r[s ] &= 0x0F; /* clear top 4 bits */ } s = dataSize - BLOCKSIZE * q; /* size of last chunk */ do { memcpy( c, pos -= s, s ); /* copy message to chunk */ c[s] = 1; /* append 1 to each chunk */ s = BLOCKSIZE; mulLblocks( r, rk ); /* r^k = r^{k-1} * r */ mulLblocks( rk, c ); /* calculate c_{q-k} * r^k */ addLblocks( c, PL, poly ); /* ..and add it to poly, */ modP1305( poly, poly[s] ); /* ..then take mod(2^130-5) */ } while (q--); q = poly[s] * 4; /* still, in some rare cases */ if (poly[0] > 0xFA && q == 12) /* ..poly may be >= 2^130-5 */ { /* ..if so, set q=16 */ for (q = 1; poly[q] == 0xFF; ++q); } modP1305( poly, q / 4 ); addLblocks( poly, BLOCKSIZE, mac ); /* add AES_k(nonce) to poly */ } #endif /* POLY1305 */ /*----------------------------------------------------------------------------*\ FPE-AES (format-preserving encryption): definitions & auxiliary functions \*----------------------------------------------------------------------------*/ #if IMPLEMENT(FPE) #if CUSTOM_ALPHABET #include "micro_fpe.h" #else #define ALPHABET "0123456789" #define RADIX 10 /* strlen (ALPHABET) */ #define LOGRDX 3.32192809488736 /* log2 (RADIX) */ #define MINLEN 6 /* ceil (6 / log10 (RADIX)) */ #define MAXLEN 56 /* only if FF_X == 3 */ #endif #if ALPHABET_IS_NON_ASCII typedef wchar_t alpha_t; #else typedef char alpha_t; /* type of plain/ciphertext */ #endif #if RADIX <= 1 + UCHAR_MAX typedef unsigned char radix_t; /* digit type in base-radix */ #else typedef unsigned short radix_t; #endif #if FF_X == 3 /* FF3 'tweakLen' is fixed */ #define FPE_PERMUTE FF3_cipher #define TWEAK_PARAMS uint8_t* tweak #define TWEAK_AGRS tweak #else /* FF1 implementation: */ #define FPE_PERMUTE FF1_cipher #define TWEAK_PARAMS uint8_t* tweak, const size_t tweakLen #define TWEAK_AGRS tweak, tweakLen static size_t b1; /* the b constant in FF1 */ /** convert the string s in base-RADIX to a big-endian number, denoted by num */ static void numRadix( const radix_t* s, size_t len, uint8_t* num, size_t bytes ) { memset( num, 0, bytes ); while (len--) { /* we can reasonably assume */ size_t i, y = *s++; /* .. RADIX^2 <= SIZE_MAX+1 */ for (i = bytes; i--; y >>= 8) { y += num[i] * RADIX; num[i] = (uint8_t) y; /* num = num * RADIX + y */ } } } /** convert a big-endian number to string s, its representation in base-RADIX */ static void strRadix( const uint8_t* num, size_t bytes, radix_t* s, size_t len ) { memset( s, 0, sizeof (radix_t) * len ); while (bytes--) { size_t i, x = *num++; for (i = len; i--; x /= RADIX) { x += s[i] << 8; /* str = (str << 8) + x */ s[i] = x % RADIX; } } } /** add two numbers x, y in base-RADIX and save the result in y, i.e. y += x; */ static void addRadix( const radix_t* x, const size_t len, radix_t* y ) { size_t i, a = 0; for (i = len; i--; a /= RADIX) /* big-endian addition */ { a += y[i] + x[i]; /* a /= RADIX is equivalent */ y[i] = a % RADIX; /* ..to: a = (a >= RADIX) */ } } /** subtract two numbers in base-RADIX and save the result in y, i.e. y -= x; */ static void subRadix( const radix_t* x, const size_t len, radix_t* y ) { size_t i, s = 1; for (i = len; i--; s /= RADIX) /* big-endian subtraction */ { s += RADIX - 1 + y[i] - x[i]; y[i] = s % RADIX; } } /** derive C at step i of FF1 rounds, given the values: u, v and PRF_init = P */ static void FF1round( const uint8_t i, const block_t P, const size_t u, const size_t v, radix_t* C ) { block_t R = { 0 }; uint8_t* num = (uint8_t*) &C[u]; /* use pre-allocated memory, */ size_t k = b1 % sizeof R, ext = (i & 1) * u; numRadix( C - v - ext, v, num, b1 ); /* ..to get NUM_radix(B) */ num[-1] = i; memcpy( R + LAST - k, num - 1, k + 1 ); /* feed NUM_radix to the PRF */ xMac( P, BLOCKSIZE, R, &rijndaelEncrypt, R ); xMac( num + k, b1 - k, R, &rijndaelEncrypt, R ); memcpy( num, R, sizeof R ); /* R = PRF(P || Q) */ k = (b1 + 3L) / sizeof R; /* total additional blocks */ for (ext = 0; k; ext = k--) { /* S = R | Enc(R ^[k]) | ... */ xorBEint( R, ext ^ k, LAST ); rijndaelEncrypt( R, num + k * sizeof R ); } strRadix( num, (b1 + 7) & ~3L, C, u ); /* take first 'd' bytes of S */ } /** encrypt/decrypt a base-RADIX string X with length len using FF1 algorithm */ static void FF1_cipher( const uint8_t* key, const char mode, const TWEAK_PARAMS, const size_t len, radix_t* X ) { size_t u = (len + !mode) / 2, t = tweakLen; radix_t* xC = X + len; block_t P = { 1, 2, 1, RADIX >> 16, RADIX >> 8 & 0xFF, RADIX & 0xFF, 10 }; uint8_t i = t % sizeof P + b1 % sizeof P < BLOCKSIZE ? t % sizeof P : 0; AES_setkey( key ); P[7] ^= len / 2; /* initializing P block */ xorBEint( P, len, 11 ); xorBEint( P, t, LAST ); rijndaelEncrypt( P, P ); /* P -> PRF(P || tweak) */ xMac( tweak, t - i, P, &rijndaelEncrypt, P ); while (i) { P[--i] ^= tweak[--t]; } for (; i < 10 * mode; ++i, u = t) /* Feistel procedure */ { /* encryption rounds */ FF1round( i, P, u, t = len - u, xC ); addRadix( xC, u, X + (i & 1) * t ); /* add C to A then swap A,B */ } for (i ^= 10; i-- != 0x00; u = t) /* decryption rounds */ { FF1round( i, P, u, t = len - u, xC ); subRadix( xC, u, X + (i & 1) * t ); /* subtract C from A */ } } #endif /* FF1 */ #if FF_X == 3 /* FF3/FF3-1 method: */ /** converts a string in base-RADIX to a little-endian number, denoted by num */ static void numRadix( const radix_t* s, uint8_t len, uint8_t* num, char bytes ) { memset( num, 0, bytes ); while (len--) { size_t i, y = s[len]; for (i = 0; i < bytes; y >>= 8) /* bytes = 12 "always" */ { y += num[i] * RADIX; num[i++] = (uint8_t) y; /* num = num * RADIX + y */ } } } /** convert a little-endian number to its base-RADIX representation string: s */ static void strRadix( const uint8_t* num, char bytes, radix_t* s, uint8_t len ) { memset( s, 0, sizeof (radix_t) * len ); while (bytes--) { size_t i, x = num[bytes]; for (i = 0; i < len; x /= RADIX) { x += s[i] << 8; /* str = (str << 8) + x */ s[i++] = x % RADIX; } } } /** add two numbers x, y in base-RADIX and save the result in y, i.e. y += x; */ static void addRadix( const radix_t* x, const uint8_t len, radix_t* y ) { size_t i, a = 0; for (i = 0; i < len; a /= RADIX) /* little-endian addition */ { a += y[i] + x[i]; y[i++] = a % RADIX; } } /** subtract two numbers in base-RADIX and save the result in y, i.e. y -= x; */ static void subRadix( const radix_t* x, const uint8_t len, radix_t* y ) { size_t i, s = 1; for (i = 0; i < len; s /= RADIX) /* little-endian subtraction */ { s += RADIX - 1 + y[i] - x[i]; y[i++] = s % RADIX; } } /** calculate C at step i of FF3 rounds, given the values: u, v and tweak (T) */ static void FF3round( const uint8_t i, const uint8_t* T, const uint8_t u, const uint8_t v, radix_t* C ) { uint8_t w = (i & 1) * 4, ext = (i & 1) * u; block_t P; COPYDWORD( T[w], P[12] ); /* W = (i is odd) ? TR : TL */ P[12] ^= i; numRadix( C - v - ext, v, P, 12 ); /* get REV. NUM_radix( B ) */ rijndaelEncrypt( P, P ); strRadix( P, sizeof P, C, u ); /* C = REV. STR_m( c ) */ } /** encrypt/decrypt a base-RADIX string X with size len using FF3-1 algorithm */ static void FF3_cipher( const uint8_t* key, const char mode, const TWEAK_PARAMS, const size_t len, radix_t* X ) { radix_t* xC = X + len; uint8_t T[8], u = (len + mode) / 2, v, i, *k = (void*) xC; memcpy( k, tweak, FF3_TWEAK_LEN ); if (FF3_TWEAK_LEN == 7) /* FF3 vs. FF3-1 */ { k[7] = (uint8_t) (k[3] << 4); k[3] &= 0xF0; } for (i = 8; i --> 0; ) T[7 - i] = k[i]; for (i = KEYSIZE; i; ) k[--i] = *key++; /* key/tweak are reversed */ AES_setkey( k ); SABOTAGE( k, KEYSIZE ); for (; i < 8 * mode; ++i, u = v) /* Feistel procedure */ { /* encryption rounds */ FF3round( i, T, u, v = len - u, xC ); addRadix( xC, u, X + (i & 1) * v ); /* add C to A then swap A,B */ } for (i ^= 8; i-- != 0x00; u = v) /* decryption rounds */ { FF3round( i, T, u, v = len - u, xC ); subRadix( xC, u, X + (i & 1) * v ); } } #endif /* FF3 */ /*----------------------------------------------------------------------------*\ FPE-AES: main functions \*----------------------------------------------------------------------------*/ #include /** * @brief encrypt or decrypt the input string with FPE method * @param key encryption or decryption key * @param mode mode of operation: encrypting (1) or decrypting (0) * @param tweak tweak byte array * @param tweakLen size of tweak. must be exactly 7 in FF3-1 * @param input input string, consisted of characters in ALPHABET * @param dataSize length of the string, or its number of characters * @param output result of format-preserved encryption/decryption * @return error character if failed, or '\0' if ended in success */ static char FPE_cipher( const uint8_t* key, const char mode, const TWEAK_PARAMS, const void* input, const size_t dataSize, void* output ) { size_t v = (dataSize + 1) / 2; size_t n = (dataSize + v) * sizeof (radix_t); alpha_t const* alpha = ALPHABET; alpha_t* y = output; radix_t* index; if (dataSize < MINLEN) return 'l'; #if FF_X == 3 if (dataSize > MAXLEN) return 'L'; v *= sizeof (radix_t); n += v < KEYSIZE ? KEYSIZE - v : 0; #else /* extra memory is needed.. */ b1 = (size_t) (LOGRDX * v + 8 - 1e-14) / 8; /* to store NUM_radix and.. */ n += (b1 + 4 + LAST) & ~LAST; /* mix it in Feistel rounds */ #endif if ((index = malloc( n )) == NULL) { return 'm'; /* memory allocation failed */ } for (n = 0; n < dataSize; ++n) /* find index of each char */ { const alpha_t *ch = (alpha_t*) input + n; for (v = 0x0; *ch != alpha[v]; ) { if (++v == RADIX) { free( index ); /* invalid character found */ return 'C'; } } *(index + n) = (radix_t) v; } /* now permute the indexes: */ FPE_PERMUTE( key, mode, TWEAK_AGRS, n, index ); BURN( RoundKey ); for (y[n] = 0; n--; ) /* construct the output as */ { /* null-terminated? string */ y[n] = alpha[index[n]]; } free( index ); return 0; } /** * @brief encrypt the input string using FPE-AES block-cipher method * @param key encryption key with a fixed size specified by KEYSIZE * @param tweak tweak byte array; similar to nonce in other schemes * @param tweakLen size of tweak. must be exactly 7 in FF3-1 * @param pntxt input plaintext string, consisted of ALPHABET characters * @param ptextLen size of plaintext string, or number of characters * @param crtxt resulting ciphertext string * @return whether encryption was successful */ char AES_FPE_encrypt( const uint8_t* key, const TWEAK_PARAMS, const void* pntxt, const size_t ptextLen, void* crtxt ) { char result = FPE_cipher( key, 1, TWEAK_AGRS, pntxt, ptextLen, crtxt ); return result == 0 ? M_RESULT_SUCCESS : M_ENCRYPTION_ERROR; } /** * @brief decrypt a ciphertext string using FPE-AES block-cipher method * @param key decryption key with a fixed size specified by KEYSIZE * @param tweak tweak byte array; similar to nonce in other schemes * @param tweakLen size of tweak. must be exactly 7 in FF3-1 * @param crtxt input ciphertext string, consisted of ALPHABET characters * @param crtxtLen size of ciphertext string, or number of characters * @param pntxt resulting plaintext string * @return whether decryption was successful */ char AES_FPE_decrypt( const uint8_t* key, const TWEAK_PARAMS, const void* crtxt, const size_t crtxtLen, void* pntxt ) { char result = FPE_cipher( key, 0, TWEAK_AGRS, crtxt, crtxtLen, pntxt ); return result == 0 ? M_RESULT_SUCCESS : M_DECRYPTION_ERROR; } #endif /* FPE */