Elephant - Light-weight cipherElephant is a light-weight crypto cipher created by Tim Beyne, Yu Long Chen, Christoph Dobraunig, and Bart Mennink. It is an authenticated encryption scheme, based on a nonce-based encrypt-then-MAC construction. We can provide a nonce (or IV) and which is the salt value for the cipher. Along it supports Authenticated encryption with Associated Data (AEAD) and where we can provide additional data for the cipher. This additional data is not sent with the cipher or used within the cipher, but can be used to authenicate it. An example might be to link a packet sequence number to the additional data, so that the cipher could not be played-back for another sequence number. One of the strengths of Elephant is that it has low footprint such as with a 160-bit permutation and can be parallelized. |
Outline
Within Elephant we have three different variations: Dumbo, Jumbo and Delirium (and which is a Belgian beer with a pink elephant logo).It was created by Tim Beyne, Yu Long Chen, Christoph Dobraunig, and Bart Mennink. Tim and Yu are from KU Leuven and imec-COSIC, Belgium, and Christoph and Bart from Radboud University, The Netherlands.
Overall, Elephant is an authenticated encryption scheme, based on a nonce-based encrypt-then-MAC construction. We can thus provide a nonce (also known as an initialization vector — IV) and which is the salt value for the cipher. This makes sure that the ciphertext will change when we are using the same key and the same plaintext. Along it supports Authenticated encryption with Associated Data (AEAD) and where we can provide additional data for the cipher. Unlike the nonce value, this additional data is not sent with the ciphertext or used within the encryption method but can be used to authenticate the ciphertext. An example might be to link a packet sequence number to the additional data so that the cipher could not be played-back for another sequence number.
Within lightweight crypto, we must optimize for memory storage, processing requirements, energy drain, and so on, along with keeping compatibility with a wide range of device, and for both a hardware and software implementation.
One of the strengths of Elephant is that it has a low footprint and can use a 160-bit permutation. This small value reduces the footprint of the method within memory. In order to speed the encryption process, the method can also be parallelized. There are three main methods:
- Dumbo: Elephant-Spongent-π[160] — this method is well-matched to hardware and gives the baseline level of security. The 160 value relates to the bit size of the permutation that is operated on. Overall it gives 112-bit security levels.
- Jumbo: Elephant-Spongent-π[176] —this is an improved method and achieves 127-bit security.
- Delirium: Elephant-Keccak-f[200] — this is a more software-focused approach with reasonably good hardware performance, and with 127-bit security.
Overall we have two C files and a few header files. To compile we can use the gcc compiler:
gcc main.c keccak.c -o elephant.exe
An outline of the C code is [1]:
#include "api.h" #include "crypto_aead.h" #include "elephant_200.h" #define CRYPTO_BYTES 64 int main (int argc, char *argv[]) { unsigned long long mlen; unsigned long long clen; unsigned char plaintext[CRYPTO_BYTES]; unsigned char cipher[CRYPTO_BYTES]; unsigned char npub[CRYPTO_NPUBBYTES]=""; unsigned char ad[CRYPTO_ABYTES]="1234"; unsigned char nsec[CRYPTO_ABYTES]=""; unsigned char key[CRYPTO_KEYBYTES]; char pl[CRYPTO_BYTES]="hello"; char chex[CRYPTO_BYTES]=""; char keyhex[2*CRYPTO_KEYBYTES+1]="0123456789ABCDEF0123456789ABCDEF"; char nonce[2*CRYPTO_NPUBBYTES+1]="000000000000111111111111"; if( argc > 1 ) { strcpy(pl,argv[1]); } if( argc > 2 ) { strcpy(keyhex,argv[2]); } strcpy(plaintext,pl); ascii2byte(keyhex,key); ascii2byte(nonce,npub); printf("Elephant light-weight cipher\n"); printf("Plaintext: %s\n",plaintext); printf("Key: %s\n",keyhex); printf("Nonce: %s\n",nonce); printf("Additional Information: %s\n\n",ad); printf("Plaintext: %s\n",plaintext); int ret = crypto_aead_encrypt(cipher,&clen,plaintext,strlen(plaintext),ad,strlen(ad),"",npub,key); string2hexString(cipher,clen,chex); printf("Cipher: %s, Len: %llu\n",chex, clen); ret = crypto_aead_decrypt(plaintext,&mlen,nsec,cipher,clen,ad,strlen(ad),npub,key); printf("Plaintext: %s, Len: %llu\n",plaintext, mlen); if (ret==0) { printf("Success!\n"); } return 0; } BYTE rotl(BYTE b) { return (b << 1) | (b >> 7); } int constcmp(const BYTE* a, const BYTE* b, SIZE length) { BYTE r = 0; for (SIZE i = 0; i < length; ++i) r |= a[i] ^ b[i]; return r; } // State should be BLOCK_SIZE bytes long // Note: input may be equal to output void lfsr_step(BYTE* output, BYTE* input) { BYTE temp = rotl(input[0]) ^ rotl(input[2]) ^ (input[13] << 1); for(SIZE i = 0; i < BLOCK_SIZE - 1; ++i) output[i] = input[i + 1]; output[BLOCK_SIZE - 1] = temp; } void xor_block(BYTE* state, const BYTE* block, SIZE size) { for(SIZE i = 0; i < size; ++i) state[i] ^= block[i]; } // Write the ith assocated data block to "output". // The nonce is prepended and padding is added as required. // adlen is the length of the associated data in bytes void get_ad_block(BYTE* output, const BYTE* ad, SIZE adlen, const BYTE* npub, SIZE i) { SIZE len = 0; // First block contains nonce // Remark: nonce may not be longer then BLOCK_SIZE if(i == 0) { memcpy(output, npub, CRYPTO_NPUBBYTES); len += CRYPTO_NPUBBYTES; } const SIZE block_offset = i * BLOCK_SIZE - (i != 0) * CRYPTO_NPUBBYTES; // If adlen is divisible by BLOCK_SIZE, add an additional padding block if(i != 0 && block_offset == adlen) { memset(output, 0x00, BLOCK_SIZE); output[0] = 0x01; return; } const SIZE r_outlen = BLOCK_SIZE - len; const SIZE r_adlen = adlen - block_offset; // Fill with associated data if available if(r_outlen <= r_adlen) { // enough AD memcpy(output + len, ad + block_offset, r_outlen); } else { // not enough AD, need to pad if(r_adlen > 0) // ad might be nullptr memcpy(output + len, ad + block_offset, r_adlen); memset(output + len + r_adlen, 0x00, r_outlen - r_adlen); output[len + r_adlen] = 0x01; } } // Return the ith ciphertext block. // clen is the length of the ciphertext in bytes void get_c_block(BYTE* output, const BYTE* c, SIZE clen, SIZE i) { const SIZE block_offset = i * BLOCK_SIZE; // If clen is divisible by BLOCK_SIZE, add an additional padding block if(block_offset == clen) { memset(output, 0x00, BLOCK_SIZE); output[0] = 0x01; return; } const SIZE r_clen = clen - block_offset; // Fill with ciphertext if available if(BLOCK_SIZE <= r_clen) { // enough ciphertext memcpy(output, c + block_offset, BLOCK_SIZE); } else { // not enough ciphertext, need to pad if(r_clen > 0) // c might be nullptr memcpy(output, c + block_offset, r_clen); memset(output + r_clen, 0x00, BLOCK_SIZE - r_clen); output[r_clen] = 0x01; } } // It is assumed that c is sufficiently long // Also, tag and c should not overlap void crypto_aead_impl( BYTE* c, BYTE* tag, const BYTE* m, SIZE mlen, const BYTE* ad, SIZE adlen, const BYTE* npub, const BYTE* k, int encrypt) { // Compute number of blocks const SIZE nblocks_c = 1 + mlen / BLOCK_SIZE; const SIZE nblocks_m = (mlen % BLOCK_SIZE) ? nblocks_c : nblocks_c - 1; const SIZE nblocks_ad = 1 + (CRYPTO_NPUBBYTES + adlen) / BLOCK_SIZE; const SIZE nb_it = (nblocks_c > nblocks_ad) ? nblocks_c : nblocks_ad + 1; // Storage for the expanded key L BYTE expanded_key[BLOCK_SIZE] = {0}; memcpy(expanded_key, k, CRYPTO_KEYBYTES); permutation(expanded_key); // Buffers for storing previous, current and next mask BYTE mask_buffer_1[BLOCK_SIZE] = {0}; BYTE mask_buffer_2[BLOCK_SIZE] = {0}; BYTE mask_buffer_3[BLOCK_SIZE] = {0}; memcpy(mask_buffer_2, expanded_key, BLOCK_SIZE); BYTE* previous_mask = mask_buffer_1; BYTE* current_mask = mask_buffer_2; BYTE* next_mask = mask_buffer_3; // Buffer to store current ciphertext block BYTE c_buffer[BLOCK_SIZE]; // Tag buffer and initialization of tag to zero BYTE tag_buffer[BLOCK_SIZE] = {0}; memset(tag, 0, CRYPTO_ABYTES); SIZE offset = 0; for(SIZE i = 0; i < nb_it; ++i) { // Compute mask for the next message lfsr_step(next_mask, current_mask); if(i < nblocks_m) { // Compute ciphertext block memcpy(c_buffer, npub, CRYPTO_NPUBBYTES); memset(c_buffer + CRYPTO_NPUBBYTES, 0, BLOCK_SIZE - CRYPTO_NPUBBYTES); xor_block(c_buffer, current_mask, BLOCK_SIZE); permutation(c_buffer); xor_block(c_buffer, current_mask, BLOCK_SIZE); const SIZE r_size = (i == nblocks_m - 1) ? mlen - offset : BLOCK_SIZE; xor_block(c_buffer, m + offset, r_size); memcpy(c + offset, c_buffer, r_size); } if(i < nblocks_c) { // Compute tag for ciphertext block get_c_block(tag_buffer, encrypt ? c : m, mlen, i); xor_block(tag_buffer, current_mask, BLOCK_SIZE); xor_block(tag_buffer, next_mask, BLOCK_SIZE); permutation(tag_buffer); xor_block(tag_buffer, current_mask, BLOCK_SIZE); xor_block(tag_buffer, next_mask, BLOCK_SIZE); xor_block(tag, tag_buffer, CRYPTO_ABYTES); } // If there is any AD left and i > 0, compute tag for AD block if(i > 0 && i <= nblocks_ad) { get_ad_block(tag_buffer, ad, adlen, npub, i - 1); xor_block(tag_buffer, previous_mask, BLOCK_SIZE); xor_block(tag_buffer, next_mask, BLOCK_SIZE); permutation(tag_buffer); xor_block(tag_buffer, previous_mask, BLOCK_SIZE); xor_block(tag_buffer, next_mask, BLOCK_SIZE); xor_block(tag, tag_buffer, CRYPTO_ABYTES); } // Cyclically shift the mask buffers // Value of next_mask will be computed in the next iteration BYTE* const temp = previous_mask; previous_mask = current_mask; current_mask = next_mask; next_mask = temp; offset += BLOCK_SIZE; } } // Remark: c must be at least mlen + CRYPTO_ABYTES long int crypto_aead_encrypt( unsigned char *c, unsigned long long *clen, const unsigned char *m, unsigned long long mlen, const unsigned char *ad, unsigned long long adlen, const unsigned char *nsec, const unsigned char *npub, const unsigned char *k) { (void)nsec; *clen = mlen + CRYPTO_ABYTES; BYTE tag[CRYPTO_ABYTES]; crypto_aead_impl(c, tag, m, mlen, ad, adlen, npub, k, 1); memcpy(c + mlen, tag, CRYPTO_ABYTES); return 0; } int crypto_aead_decrypt( unsigned char *m, unsigned long long *mlen, unsigned char *nsec, const unsigned char *c, unsigned long long clen, const unsigned char *ad, unsigned long long adlen, const unsigned char *npub, const unsigned char *k) { (void)nsec; if(clen < CRYPTO_ABYTES) return -1; *mlen = clen - CRYPTO_ABYTES; BYTE tag[CRYPTO_ABYTES]; crypto_aead_impl(m, tag, c, *mlen, ad, adlen, npub, k, 0); return (constcmp(c + *mlen, tag, CRYPTO_ABYTES) == 0) ? 0 : -1; }
A sample run is:
Elephant light-weight cipher Plaintext: abc Key: 0123456789ABCDEF0123456789ABCDEF Nonce: 000000000000111111111111 Additional Information: abc Plaintext: abc Cipher: 9653029C4307E7B9654F, Len: 19 Plaintext: abc, Len: 3 Success!