Tiny Jambu Encryption and Decryption
The Tiny Jambu algorithm is a lightweight cryptographic algorithm written in C by Hongjun Wu and Tao Huang and is available here. We started by testing the encryption and decryption functions from the Tiny Jambu library. Examples were provided to verify that encryption and decryption were working correctly. Although these functions have many arguments such as associated data, nonce, and secret message numbers, we mainly made use of plaintext, ciphertext, and the key and set the other variables to some default values. The plaintext, ciphertext, and key are stored using character arrays which we initialize using hex values for each character.
Figure: Example provided to test the code
One issue we had was finding an unused variable “nsec”. The variable is an argument to both the encryption and decryption functions but is not used at all, which confused us. Keeping it uninitialized seemed to work, however. Our guess is that since the Tiny Jambu library we used is the opt implementation they did not include the use of “nsec”. Below is one of the provided examples to test the code as well as our code providing the correct output.
int main(void){
// variables
const unsigned char m[] = {0x00}; // plaintext
unsigned long long mlen = sizeof(m); // plaintext length
const unsigned char c[] = {0xA1, 0x75, 0xD5, 0xB5, 0xC1, 0xEE, 0x4A, 0x0F, 0xA1}; // ciphertext
unsigned long long clen = sizeof(c); // ciphertext length pointer
const unsigned char k[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; // 128-bit key
encrypt(m, mlen, k);
decrypt(c, clen, k);
return 0;
}
Figure: Main function for testing encryption/decryption
void encrypt(const unsigned char* m, unsigned long long mlen, const unsigned char* k){
unsigned long long c_length = 80; // ciphertext length
unsigned char c[c_length]; // ciphertext
unsigned long long *clen = &c_length; // ciphertext length pointer
const unsigned char ad[] = {0x00}; // associated data
unsigned long long adlen = sizeof(ad); // associated data length
const unsigned char *nsec; // secret message number
const unsigned char npub[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}; // public message number
crypto_aead_encrypt(c, clen, m, mlen, ad, adlen, nsec, npub, k);
printf("\n");
printf("Ciphertext = ");
for (int i = 0; i < c_length; i++){
printf("%02X", c[i]);
}
printf("\n");
}
Figure: Function to test encryption
void decrypt(const unsigned char* c, unsigned long long clen, const unsigned char* k){
unsigned long long m_length = 80; // plaintext length
unsigned char m[m_length]; // plaintext
unsigned long long *mlen = &m_length; // plaintext length pointer
const unsigned char ad[] = {0x00}; // associated data
unsigned long long adlen = sizeof(ad); // associated data length
unsigned char *nsec; // secret message number
const unsigned char npub[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B}; // public message number
crypto_aead_decrypt(m, mlen, nsec, c, clen, ad, adlen, npub, k);
// print
printf("Plaintext = ");
for (int i = 0; i < m_length; i++){
printf("%02X", m[i]);
}
printf("\n");
}
Figure: Function to test decryption
Figure: Correct encryption and decryption outputs
X3DH Key Exchange
The X3DH Key Agreement protocol was developed by Moxie Marlinspike and Trevor Perrin and is used by the Signal messaging app. The protocol has three phases:
- Alice publishes her pre-keys (IKA and SPKA) to a server
- Bob fetches a “pre-key bundle” from the server, verifies it, and sends an initial message to Alice
- Alice receives, verifies, and processes Bob’s initial message
We used an ED25519 library for all of our key generations. We edited the functions to only generate keys and not encrypt messages since we are using our own algorithm for that. We decided to use ED25519 instead of ED448 since the NSA has backdoor access to ED448. For the key derivation function required by X3DH we used HKDF. We used RFC6234 for verification of the keys. The output of our code is shown down below and shows that Bob and Alice both generate the same secret key.
Figure: X3DH steps
//Generating long-term Identity Key pair for Bob
ed25519_create_seed(bob_seed); //create randome seed
ed25519_create_keypair(bob_id_public_key, bob_id_private_key, bob_seed); //create keypair out of seed
//Generate SignedPreKey Pair for bob
ed25519_create_seed(bob_seed); //create random seed
ed25519_create_keypair(bob_spk_public_key, bob_spk_private_key, bob_seed); //create keypair out of seed
ed25519_sign(bob_spk_signature, bob_id_public_key, bob_id_private_key);
if (ed25519_verify(bob_spk_signature, bob_id_public_key)) {
printf("\nvalid signature\n");
} else {
printf("\ninvalid signature\n");
// Abort();
}
//Verifying on Alice's side
ed25519_create_seed(alice_seed);
ed25519_create_keypair(alice_id_public_key, alice_id_private_key, alice_seed);
//Generate Ephemeral keys
ed25519_create_seed(alice_seed);
ed25519_create_keypair(alice_ephemeral_public_key, alice_ephemeral_private_key, alice_seed);
Figure: Generating keys
if(hkdf(whichSha,salt,salt_len,dh_final,ikm_len,info,info_len,okm_integer,okm_len) == 0)
{
printf("HKDF is valid\n");
} else {
fprintf(stderr, "\nHKDF is invalid\n");
}
for(int i=0; i<okm_len;i++)
{
output_key[i] = okm_integer[i];
// printf("%d\n", output_key[i]);
}
Figure: Key derivation
Figure: X3DH key exchange verified