From aa611f6468b9b2b2fc67e571b6cd3c76fab6006c Mon Sep 17 00:00:00 2001 From: Mathias Tausig Date: Tue, 23 Apr 2019 10:34:32 +0200 Subject: [PATCH] crypto: Implement OCB encryption mode --- sys/crypto/modes/ocb.c | 333 +++++++++++++++++++++++++++++++++ sys/include/crypto/modes/ocb.h | 110 +++++++++++ 2 files changed, 443 insertions(+) create mode 100644 sys/crypto/modes/ocb.c create mode 100644 sys/include/crypto/modes/ocb.h diff --git a/sys/crypto/modes/ocb.c b/sys/crypto/modes/ocb.c new file mode 100644 index 0000000000..003520efd8 --- /dev/null +++ b/sys/crypto/modes/ocb.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2018 Mathias Tausig + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_crypto + * @{ + * + * @file + * @brief Offset Codebook (OCB3) AEAD mode as specified in RFC 7253 + * + * @author Mathias Tausig + * + */ + +#include "crypto/modes/ocb.h" +#include +#include + +#define OCB_MODE_ENCRYPT 1 +#define OCB_MODE_DECRYPT 2 + +struct ocb_state { + cipher_t *cipher; + uint8_t l_star[16]; + uint8_t l_zero[16]; + uint8_t l_dollar[16]; + uint8_t checksum[16]; + uint8_t offset[16]; +}; + +typedef struct ocb_state ocb_state_t; + +static void double_block(uint8_t source[16], uint8_t dest[16]) +{ + uint8_t msb = source[0] >> 7; + + for (uint8_t i = 0; i < 15; ++i) { + dest[i] = source[i] << 1 | source[i + 1] >> 7; + } + dest[15] = (source[15] << 1) ^ (0x87 * msb); +} + +static size_t ntz(size_t n) +{ + /* ntz must only be run on positive values */ + if (n == 0) { + return SIZE_MAX; + } + + size_t ret = 0; + + while (n % 2 == 0) { + ++ret; + n = n >> 1; + } + return ret; +} + +static void calculate_l_i(uint8_t l_zero[16], size_t i, uint8_t output[16]) +{ + memcpy(output, l_zero, 16); + while ((i--) > 0) { + double_block(output, output); + } +} + +static void xor_block(uint8_t block1[16], uint8_t block2[16], uint8_t output[16]) +{ + for (uint8_t i = 0; i < 16; ++i) { + output[i] = block1[i] ^ block2[i]; + } +} + +static void processBlock(ocb_state_t *state, size_t blockNumber, uint8_t input[16], uint8_t output[16], uint8_t mode) +{ + /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ + uint8_t l_i[16]; + + calculate_l_i(state->l_zero, ntz(blockNumber + 1), l_i); + xor_block(state->offset, l_i, state->offset); + /* Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i) */ + uint8_t cipher_output[16], cipher_input[16]; + xor_block(input, state->offset, cipher_input); + if (mode == OCB_MODE_ENCRYPT) { + state->cipher->interface->encrypt(&(state->cipher->context), cipher_input, cipher_output); + } + else if (mode == OCB_MODE_DECRYPT) { + state->cipher->interface->decrypt(&(state->cipher->context), cipher_input, cipher_output); + } + xor_block(state->offset, cipher_output, output); + /* Checksum_i = Checksum_{i-1} xor P_i */ + if (mode == OCB_MODE_ENCRYPT) { + xor_block(state->checksum, input, state->checksum); + } + else if (mode == OCB_MODE_DECRYPT) { + xor_block(state->checksum, output, state->checksum); + } +} + +static void hash(ocb_state_t *state, uint8_t *data, size_t data_len, uint8_t output[16]) +{ + /* Calculate the number of full blocks in data */ + size_t m = (data_len - (data_len % 16)) / 16; + size_t remaining_data_len = data_len - m * 16; + + /* Sum_0 = zeros(128) */ + memset(output, 0, 16); + /* Offset_0 = zeros(128) */ + uint8_t offset[16]; + memset(offset, 0, 16); + for (size_t i = 0; i < m; ++i) { + /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ + uint8_t l_i[16]; + calculate_l_i(state->l_zero, ntz(i + 1), l_i); + xor_block(offset, l_i, offset); + /* Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i) */ + uint8_t enciphered_block[16], cipher_input[16]; + xor_block(data, offset, cipher_input); + state->cipher->interface->encrypt(&(state->cipher->context), cipher_input, enciphered_block); + xor_block(output, enciphered_block, output); + + data += 16; + } + if (remaining_data_len > 0) { + /* Offset_* = Offset_m xor L_* */ + xor_block(offset, state->l_star, offset); + /* CipherInput = (A_* || 1 || zeros(127-bitlen(A_*))) xor Offset_* */ + uint8_t cipher_input[16]; + memset(cipher_input, 0, 16); + memcpy(cipher_input, data, remaining_data_len); + cipher_input[remaining_data_len] = 0x80; + xor_block(cipher_input, offset, cipher_input); + /* Sum = Sum_m xor ENCIPHER(K, CipherInput) */ + uint8_t enciphered_block[16]; + state->cipher->interface->encrypt(&(state->cipher->context), cipher_input, enciphered_block); + xor_block(output, enciphered_block, output); + } +} + +static void init_ocb(cipher_t *cipher, uint8_t tag_len, uint8_t *nonce, size_t nonce_len, ocb_state_t *state) +{ + + state->cipher = cipher; + + /* Key-dependent variables + + L_* = ENCIPHER(K, zeros(128)) + L_$ = double(L_*) + L_0 = double(L_$) + L_i = double(L_{i-1}) for every integer i > 0 + */ + uint8_t zero_block[16]; + memset(zero_block, 0, 16); + cipher->interface->encrypt(&(cipher->context), zero_block, state->l_star); + double_block(state->l_star, state->l_dollar); + double_block(state->l_dollar, state->l_zero); + + /* Nonce-dependent and per-encryption variables */ + /* Nonce = num2str(TAGLEN mod 128,7) || zeros(120-bitlen(N)) || 1 || N */ + uint8_t nonce_padded[16]; + memset(nonce_padded, 0, 16); + nonce_padded[0] = (tag_len * 8) << 1; + nonce_padded[15 - nonce_len] = 0x01; + memcpy(nonce_padded + 16 - nonce_len, nonce, nonce_len); + + /* bottom = str2num(Nonce[123..128])*/ + uint8_t bottom = (nonce_padded[15] << 2) >> 2; + /* Ktop = ENCIPHER(K, Nonce[1..122] || zeros(6)) */ + nonce_padded[15] = nonce_padded[15] & 0xC0; + uint8_t ktop[16]; + cipher->interface->encrypt(&(cipher->context), nonce_padded, ktop); + + /* Stretch = Ktop || (Ktop[1..64] xor Ktop[9..72]) */ + uint8_t stretch[24]; + memcpy(stretch, ktop, 16); + for (uint8_t i = 0; i < 8; ++i) { + stretch[16 + i] = ktop[i] ^ ktop[i + 1]; + } + + /* Offset_0 = Stretch[1+bottom..128+bottom] */ + uint8_t offset_start_byte = bottom / 8; + uint8_t offset_start_bit = bottom - offset_start_byte * 8; + for (uint8_t i = 0; i < 16; ++i) { + state->offset[i] = (stretch[offset_start_byte + i] << offset_start_bit) | (stretch[offset_start_byte + i + 1] >> (8 - offset_start_bit)); + } + + /* Checksum_0 = zeros(128) */ + memset(state->checksum, 0, 16); +} + +static int32_t run_ocb(cipher_t *cipher, uint8_t *auth_data, uint32_t auth_data_len, + uint8_t tag[16], uint8_t tag_len, uint8_t *nonce, size_t nonce_len, + uint8_t *input, size_t input_len, uint8_t *output, uint8_t mode) +{ + + /* OCB mode only works for ciphers of block length 16 */ + if (cipher->interface->block_size != 16) { + return OCB_ERR_INVALID_BLOCK_LENGTH; + } + + /* The tag can be at most 128 bit long */ + if (tag_len > 16 || tag_len == 0) { + return OCB_ERR_INVALID_TAG_LENGTH; + } + + /* The nonce can be at most 120 bit long */ + if (nonce_len >= 16 || nonce_len == 0) { + return OCB_ERR_INVALID_NONCE_LENGTH; + } + + ocb_state_t state; + init_ocb(cipher, tag_len, nonce, nonce_len, &state); + + /* Calculate the number of full blocks in data */ + size_t m = (input_len - (input_len % 16)) / 16; + size_t remaining_input_len = input_len - m * 16; + + /* Process any whole blocks */ + size_t output_pos = 0; + for (size_t i = 0; i < m; ++i) { + processBlock(&state, i, input, output + output_pos, mode); + output_pos += 16; + input += 16; + } + + /* Process any final partial block and compute raw tag */ + if (remaining_input_len > 0) { + /* Offset_* = Offset_m xor L_* */ + xor_block(state.offset, state.l_star, state.offset); + + /* Pad = ENCIPHER(K, Offset_*) */ + uint8_t pad[16]; + cipher->interface->encrypt(&(cipher->context), state.offset, pad); + + /* Encrypt: C_* = P_* xor Pad[1..bitlen(P_*)] */ + /* Decrypt: P_* = C_* xor Pad[1..bitlen(C_*)] */ + uint8_t final_block[remaining_input_len]; + memcpy(final_block, pad, remaining_input_len); + for (uint8_t i = 0; i < remaining_input_len; ++i) { + final_block[i] = input[i] ^ pad[i]; + } + memcpy(output + output_pos, final_block, remaining_input_len); + + /* Checksum_* = Checksum_m xor (P_* || 1 || zeros(127-bitlen(P_*))) */ + uint8_t padded_block[16]; + memset(padded_block, 0, 16); + if (mode == OCB_MODE_ENCRYPT) { + memcpy(padded_block, input, remaining_input_len); + } + else if (mode == OCB_MODE_DECRYPT) { + memcpy(padded_block, output + output_pos, remaining_input_len); + } + padded_block[remaining_input_len] = 0x80; + xor_block(state.checksum, padded_block, state.checksum); + output_pos += remaining_input_len; + } + /* else: C_* = */ + + /* Tag = ENCIPHER(K, Checksum_* xor Offset_* xor L_$) xor HASH(K,A) */ + /* Tag = ENCIPHER(K, Checksum_m xor Offset_m xor L_$) xor HASH(K,A) */ + uint8_t hash_value[16]; + hash(&state, auth_data, auth_data_len, hash_value); + uint8_t cipher_data[16]; + xor_block(state.checksum, state.offset, cipher_data); + xor_block(cipher_data, state.l_dollar, cipher_data); + + cipher->interface->encrypt(&(cipher->context), cipher_data, tag); + xor_block(tag, hash_value, tag); + + return output_pos; +} + +int32_t cipher_encrypt_ocb(cipher_t *cipher, uint8_t *auth_data, size_t auth_data_len, + uint8_t tag_len, uint8_t *nonce, size_t nonce_len, + uint8_t *input, size_t input_len, uint8_t *output) +{ + uint8_t tag[16]; + + if (input_len > (uint32_t)(INT32_MAX - tag_len)) { + // We would not be able to return the proper output length for data this long + return OCB_ERR_INVALID_DATA_LENGTH; + } + + int cipher_text_length = run_ocb(cipher, auth_data, auth_data_len, + tag, tag_len, nonce, nonce_len, + input, input_len, output, OCB_MODE_ENCRYPT); + + if (cipher_text_length < 0) { + // An error occured. Return the error code + return cipher_text_length; + } + /* C = C_1 || C_2 || ... || C_m || C_* || Tag[1..TAGLEN] */ + memcpy(output + cipher_text_length, tag, tag_len); + return (cipher_text_length + tag_len); +} + +int32_t cipher_decrypt_ocb(cipher_t *cipher, uint8_t *auth_data, size_t auth_data_len, + uint8_t tag_len, uint8_t *nonce, size_t nonce_len, + uint8_t *input, size_t input_len, uint8_t *output) +{ + + if (input_len - tag_len > INT32_MAX) { + // We would not be able to return the proper output length for data this long + return OCB_ERR_INVALID_DATA_LENGTH; + } + + uint8_t tag[16]; + int plain_text_length = run_ocb(cipher, auth_data, auth_data_len, + tag, tag_len, nonce, nonce_len, + input, input_len - tag_len, output, OCB_MODE_DECRYPT); + + if (plain_text_length < 0) { + // An error occured. Retur the error code + return plain_text_length; + } + /* Check the tag */ + if (memcmp(tag, input + input_len - tag_len, tag_len) == 0) { + /* Tag is valid */ + /* P = P_1 || P_2 || ... || P_m || P_* */ + return plain_text_length; + } + else { + /* Tag is not valid */ + /* Destroy the decrypted data to prevent misuse */ + memset(output, 0, input_len - tag_len); + return OCB_ERR_INVALID_TAG; + } +} diff --git a/sys/include/crypto/modes/ocb.h b/sys/include/crypto/modes/ocb.h new file mode 100644 index 0000000000..88b8a94c2c --- /dev/null +++ b/sys/include/crypto/modes/ocb.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2018 Mathias Tausig + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_crypto + * @{ + * + * @file ocb.h + * @brief Offset Codebook (OCB3) AEAD mode as specified in RFC 7253 + * + * NOTE: The OCB algorithm is covered by patents in the USA owned by Phillip Rogaway. + * A free licence is granted for any open-source or non-military project. + * Check http://web.cs.ucdavis.edu/~rogaway/ocb/grant.htm for details. + * + * @author Mathias Tausig + */ + +#ifndef CRYPTO_MODES_OCB_H +#define CRYPTO_MODES_OCB_H + +#include "crypto/ciphers.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name OCB error codes + * @{ + */ + +/** + * Returned if a nonce of bad length (empty or more than 15 bytes) was used + */ +#define OCB_ERR_INVALID_NONCE_LENGTH (-2) +/** + * OCB only works with ciphers with a block size of 128 bit + */ +#define OCB_ERR_INVALID_BLOCK_LENGTH (-3) +/** + * Returned if the amount of input data cannot be handled by this implementation + */ +#define OCB_ERR_INVALID_DATA_LENGTH (-3) +/** + * Returned if a tag of bad length was requested (empty or more than 16 bytes) + */ +#define OCB_ERR_INVALID_TAG_LENGTH (-4) +/** + * Returned if the authentication failed during decryption + */ +#define OCB_ERR_INVALID_TAG (-5) + +/** @} */ + +/** + * @brief Encrypt and authenticate data of arbitrary length in OCB mode. + * + * @param cipher Already initialized cipher struct + * @param auth_data Additional data to authenticate in MAC + * @param auth_data_len Length of additional data + * @param tag_len Length of the appended tag (at leat 1, at most 16 bytes) + * + * @param nonce Nonce for the encryption (must be unique) + * @param nonce_len Length of the nonce in bytes (at most 15) + * @param input pointer to input data to encrypt + * @param input_len length of the input data. + * input_len + tag_len must be smaller than INT32_MAX (2^31-1) + * @param output pointer to allocated memory for encrypted data. + * The tag will be appended to the ciphertext. + * It has to be of size data_len + tag_len. + * @return Length of the encrypted data (including the tag) or a (negative) error code + */ +int32_t cipher_encrypt_ocb(cipher_t *cipher, uint8_t *auth_data, size_t auth_data_len, + uint8_t tag_len, uint8_t *nonce, size_t nonce_len, + uint8_t *input, size_t input_len, uint8_t *output); + +/** + * @brief Decrypt and verify the authentication of OCB encrypted data. + * + * @param cipher Already initialized cipher struct + * @param auth_data Additional data to authenticate in MAC + * @param auth_data_len Length of additional data + * @param tag_len Length of the appended tag (at leat 1, at most 16 bytes) + * + * @param nonce Nonce for the encryption (must be unique) + * @param nonce_len Length of the nonce in bytes (at most 15) + * @param input pointer to the ciphertext with the tag appended + * @param input_len length of the input data. + * input_len - tag_len must be smaller than INT32_MAX (2^31-1) + * @param output pointer to allocated memory for the plaintext data. + * It has to be of size input_len - tag_len. + * Will contain only zeroes, if the authentication fails. + * @return Length of the plaintext data or a (negative) error code + */ +int32_t cipher_decrypt_ocb(cipher_t *cipher, uint8_t *auth_data, size_t auth_data_len, + uint8_t tag_len, uint8_t *nonce, size_t nonce_len, + uint8_t *input, size_t input_len, uint8_t *output); +#ifdef __cplusplus +} +#endif + +#endif /* CRYPTO_MODES_OCB_H */ +/** @} */