In Cybersecurity, Where Would You Find Dumbo, Jumbo and a Pink Elephant?

Well, you will find these names as part of the method of light-weight encryption method known as Elephant, and which is one of the final…

In Cybersecurity, Where Would You Find Dumbo, Jumbo and a Pink Elephant?

Well, you will find these names as part of the method of light-weight encryption method known as Elephant, and which is one of the final contenders for a NIST competition [here]. Whichever method wins the competition will probably become a standard on billions of devices.

Within Elephant [1] 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.

An outline of the C code is [here][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 [here]:

Elephant light-weight cipher
Plaintext: abc
Key: 0123456789ABCDEF0123456789ABCDEF
Nonce: 000000000000111111111111
Additional Information: abc
Plaintext: abc
Cipher: 9653029C4307E7B9654F, Len: 19
Plaintext: abc, Len: 3
Success!

And a running version:

Reference

[1] Elephant GitHub [here].