diff --git a/puer.c b/puer.c index 278fd15..d4fffd8 100644 --- a/puer.c +++ b/puer.c @@ -2,6 +2,7 @@ #include #include #include +#include void xxtea128(uint32_t const key[4], uint32_t block[4]) { // Encryption half of the XXTEA algorithm, with block size limited @@ -65,6 +66,20 @@ void word2bytes(unsigned char *bytes, uint32_t word) { bytes[3] = word>>24; } +void block2words(uint32_t words[4], unsigned char const bytes[16]) { + words[0] = bytes2word(&bytes[0]); + words[1] = bytes2word(&bytes[4]); + words[2] = bytes2word(&bytes[8]); + words[3] = bytes2word(&bytes[12]); +} + +void words2block(unsigned char bytes[16], uint32_t const words[4]) { + word2bytes(&bytes[0], words[0]); + word2bytes(&bytes[4], words[1]); + word2bytes(&bytes[8], words[2]); + word2bytes(&bytes[12], words[3]); +} + struct hashstate { // A_n and B_n of the MDC-2 algorithm uint32_t a[4]; @@ -108,10 +123,7 @@ void compress_hash(struct hashstate *state) { // M_i uint32_t message[4]; - message[0] = bytes2word(&state->buffer[0]); - message[1] = bytes2word(&state->buffer[4]); - message[2] = bytes2word(&state->buffer[8]); - message[3] = bytes2word(&state->buffer[12]); + block2words(message, state->buffer); // V_i = M_i ^ E(M_i, A_i) // Note: In this description A_i is the *key*, not the plaintext @@ -289,6 +301,132 @@ void kdf(unsigned char key[16], unsigned char salt[32], unsigned char passphrase memcpy(key, final_hash, 16); } +// 16 bit authentication tag +const int mprime = (16-2)/2; +// 32 bit = 4 byte length field +const int lprime = 4-1; + +void ccm_mac(unsigned char mac[16], uint32_t key[4], uint32_t messageindex, unsigned char message[], uint32_t length) { + // CCM specifies that the length field is big endian while we are + // natively little endian. Flip it. + unsigned char length_bytes[4]; + length_bytes[0] = length >> 24; + length_bytes[1] = length >> 16; + length_bytes[2] = length >> 8; + length_bytes[3] = length; + uint32_t be_length = bytes2word(length_bytes); + + // First block is special + uint32_t mac_words[4] = {mprime<<3 | lprime, 0, messageindex, be_length}; + xxtea128(key, mac_words); + + // Process all full blocks + size_t index = 0; + for (; index + 16 <= length; index += 16) { + // Xor the plaintext block and previous encrypted block + uint32_t block[4]; + block2words(block, &message[index]); + mac_words[0] ^= block[0]; + mac_words[1] ^= block[1]; + mac_words[2] ^= block[2]; + mac_words[3] ^= block[3]; + // Encrypt + xxtea128(key, mac_words); + } + if (index < length) { + // Pad with zeros to block width + unsigned char fullblock[16]; + memset(fullblock, 0, 16); + memcpy(fullblock, &message[index], length - index); + // Xor the plaintext block and previous encrypted block + uint32_t block[4]; + block2words(block, fullblock); + mac_words[0] ^= block[0]; + mac_words[1] ^= block[1]; + mac_words[2] ^= block[2]; + mac_words[3] ^= block[3]; + // Encrypt + xxtea128(key, mac_words); + } + + words2block(mac, mac_words); +} + +void ccm_xor_block(unsigned char block[16], uint32_t key[4], uint32_t messageindex, uint32_t counter) { + // CCM specifies that the counter field is big endian while we are + // natively little endian. Flip it. + unsigned char counter_bytes[4]; + counter_bytes[0] = counter >> 24; + counter_bytes[1] = counter >> 16; + counter_bytes[2] = counter >> 8; + counter_bytes[3] = counter; + uint32_t be_counter = bytes2word(counter_bytes); + + uint32_t words[4] = {lprime, 0, messageindex, be_counter}; + xxtea128(key, words); + unsigned char keystream[16]; + words2block(keystream, words); + + for (size_t i = 0; i < 16; i++) { + block[i] ^= keystream[i]; + } +} + +// IMPORTANT: The underlying message[] must be addressable until the index +// ceil(length / 16)*16, but the length only reflects the actual message +// length +void ccm_encrypt(unsigned char key[16], uint32_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) { + uint32_t key_words[4]; + block2words(key_words, key); + + // Authenticate + ccm_mac(mac, key_words, messageindex, message, length); + + // Encrypt + // MAC is xored with first block of keystream + ccm_xor_block(mac, key_words, messageindex, 0); + // Xor the message + for (uint32_t index = 0; index < length; index += 16) { + // Message blocks are numbered from index 1 onwards + ccm_xor_block(&message[index], key_words, messageindex, index + 1); + } +} + +// Same requirements for message[] hold as with ccm_encrypt +bool ccm_decrypt(unsigned char key[16], uint32_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) { + uint32_t key_words[4]; + block2words(key_words, key); + + // Decrypt + // MAC is xored with first block of keystream + ccm_xor_block(mac, key_words, messageindex, 0); + // Xor the message + for (uint32_t index = 0; index < length; index += 16) { + // Message blocks are numbered from index 1 onwards + ccm_xor_block(&message[index], key_words, messageindex, index + 1); + } + + // Compute the expected authentication tag + unsigned char computed_mac[16]; + ccm_mac(computed_mac, key_words, messageindex, message, length); + + // Compare the expected and actual tag in constant time + unsigned char different = 0; + for (size_t i = 0; i < 16; i++) { + different |= computed_mac[i] ^ mac[i]; + } + + // Do the tags match? + if (different) { + // Nope. Wipe what we decrypted and return false + explicit_bzero(message, length); + return false; + } + + // They do, return true + return true; +} + int main(void) { unsigned char key[16] = {0}; unsigned char salt[32] = "seasaltrocksalt seasaltrocksalt"; @@ -297,6 +435,31 @@ int main(void) { for (size_t i = 0; i < 16; i++) { printf("%02hhx ", key[i]); } + printf("\n\n"); + + char *message = "Syökää parsaa ja palvokaa saatanaa"; + unsigned char buf[64] = {69}; + strcpy((char*)buf, message); + + ccm_encrypt(key, 25, buf, strlen(message), &buf[48]); + for (size_t i = 0; i < sizeof(buf); i++) { + printf("%02hhx ", buf[i]); + if (i % 16 == 15) printf("\n"); + } printf("\n"); - return 0; + + memset(&buf[strlen(message)], 0x0f, 48-strlen(message)); + for (size_t i = 0; i < sizeof(buf); i++) { + printf("%02hhx ", buf[i]); + if (i % 16 == 15) printf("\n"); + } + printf("\n"); + + + bool auth = ccm_decrypt(key, 25, buf, strlen(message), &buf[48]); + printf("auth %s\n", auth ? "succeeded" : "failed"); + for (size_t i = 0; i < sizeof(buf); i++) { + printf("%02hhx ", buf[i]); + if (i % 16 == 15) printf("\n"); + } }