Шифрование / дешифрование с помощью AES-GCM с использованием OpenSSL

Я пишу класс AES-GCM для моего приложения. Требования ограничивают меня в C ++ 98 и статических ключах. У меня проблемы с модульным тестированием написанного мной кода. Я не могу зашифровать тестовую строку и расшифровать ее до первоначального вида. Что я делаю не так, используя OpenSSL EVP?

Класс AES, вот сочные кусочки:

/**
* @brief ctor for aescipher class
*
* @params secret      secret cryptographic key
* @params secret_len  array length of secret
* @params iv          initialization vector (note, For a given key, the
*                     IV MUST NOT repeat.)
* @params iv_len      array length of iv
*/
aescipher::aescipher(const uint8_t* secret, size_t secret_len,
const uint8_t* iv, size_t iv_len)
:    mSecretSize(secret_len)
,    mSecret(new uint8_t[secret_len])
,    mIVSize(iv_len)
,    mIV(new uint8_t[iv_len])
{
memcpy(mSecret, secret, mSecretSize);
memcpy(mIV, iv, mIVSize);
}

aescipher::~aescipher()
{
delete [] mSecret;
mSecret = NULL;

delete [] mIV;
mIV = NULL;
}

/**
* @brief generates a random 16-octet array suitable to use as an AES-GCM integrity check
*
* @param iv   unsigned 8-octet array for iv
*
* @returns    boolean - RAND_bytes() success
*/
/* static */ bool aescipher::generateIV(uint8_t iv[8])
{
return (RAND_bytes(iv, 8) == 1);
}

/**
* @brief encrypt data and aad using AES-GCM
*
* @param src      data to be encrypted
* @param src_len  array length of src
* @param aad      additional authenticated data included but not encrypted (optional/nullable)
* @param aad_len  array length of aad
* @param dst      destination data array for encrypted data
* @param dst_len  array length of dst
* @param tag      16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return         number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
const uint8_t *aad, size_t aad_size,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int cipher_len = 0;
int len = 0;
EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_EncryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);
// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_EncryptInit_ex(context, NULL, NULL, mSecret, mIV);
// Provide AAD if supplied
if (aad && aad_size) {
if (!EVP_EncryptUpdate(context, NULL, &len, aad, aad_size))
goto AES_ERROR;
}

if (!EVP_EncryptUpdate(context, dst, &len, src, src_len)) {
goto AES_ERROR;
}
cipher_len = len;

// There may be some final data left to encrypt if the input is
// not an exact multiple of the block size.
if (!EVP_EncryptFinal_ex(context, dst + len, &len))
goto AES_ERROR;
//Normally ciphertext bytes may be written during finalization,
// but this does not occur in GCM mode, so we make do manually.
cipher_len += len;
// Get the tag
if (!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, 16, tag))
goto AES_ERROR;

EVP_CIPHER_CTX_free(context);
return cipher_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

/**
* @brief convenience encrypt() function omitting AAD params
*
* @param src      data to be encrypted
* @param src_len  array length of src
* @param dst      destination data array for encrypted data
* @param dst_len  array length of dst
* @param tag      16 octet data array for Authentication Tag (Integrity Check Value per spec)
*
* @return         number of bytes written to dst
*/
size_t aescipher::encrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t tag[16])
{
return encrypt(src, src_len, NULL, 0, dst, dst_len, tag);
}/**
* @brief decrypt AES-GCM encrypted data
*
* @param src      encrypted data array
* @param src_len  array length of src
* @param dst      destination array of decrypted data
* @param dst_len  array length of dst
* @param tag      16 octet data array for Authentication Tag (Integrity Check Value per spec)
* @param aad      additional authenticated data received
* @param aad_len  array length of aad
*
* @return         bytes written to dst
*/
size_t aescipher::decrypt(const uint8_t* src, size_t src_len,
uint8_t* dst, size_t dst_len,
uint8_t* aad, size_t aad_len,
uint8_t tag[16])
{
if (!src || !src_len || !dst || !dst_len) return 0;
if (src_len > dst_len) return 0;

int output_len = 0;
int temp_len = 0;

EVP_CIPHER_CTX* context = EVP_CIPHER_CTX_new();
if (context == NULL) return 0;

EVP_DecryptInit_ex(context, EVP_aes_128_gcm(), NULL, NULL, NULL);

// set secret key and IV lengths
EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, mIVSize, NULL);
EVP_CIPHER_CTX_set_key_length(context, (int)mSecretSize);
// initialize secret key and IV
EVP_DecryptInit_ex(context, NULL, NULL, mSecret, mIV);

// Provide AAD if supplied
if (!EVP_DecryptUpdate(context, NULL, &temp_len, aad, aad_len)) {
goto AES_ERROR;
}
if (!EVP_DecryptUpdate(context, dst, &temp_len, src, src_len)) {
goto AES_ERROR;
}
output_len = temp_len;

// Set expected tag value.
if(!EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
goto AES_ERROR;
}
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
if (EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len) <= 0){
goto AES_ERROR;
}
output_len += temp_len;

EVP_CIPHER_CTX_free(context);
return output_len;

AES_ERROR:
EVP_CIPHER_CTX_free(context);
return 0;
}

Теперь для модульного теста.

К вашему сведению, я не генерирую IV для модульного теста, потому что у меня есть кодированные Base64 зашифрованные данные для хранения в источнике модульного теста, поэтому мне нужно использовать статический IV с ним. Мне просто нужно проверить класс. Не волнуйтесь, генерация уникального IV произойдет на более высоком уровне. На данный момент в тестах нет AAD.

Base64 и все неопределенные классы являются модульными и функциональными, поэтому их определения не имеют отношения к вопросу.

Секретный ключ, который я использую:

const unsigned char *secret = reinterpret_cast<const unsigned char*>("Open** Sesame**");

Зашифровать тест (проходит тесты, но encrypted_data на самом деле могут быть неверные данные!):

const unsigned char *msg = reinterpret_cast<const unsigned char*>(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encrypted_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
const size_t msg_len = 127;
size_t ciphertext_len = 256;
unsigned char ciphertext[ciphertext_len];
memset(ciphertext, 0, sizeof(ciphertext));

uint8_t iv[8], tag[16];
memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.encrypt(msg, msg_len, ciphertext, ciphertext_len, tag);

unsigned char encrypted_data[ciphertext_len];
(void)Base64::decode(encrypted_msg.c_str(), encrypted_msg.length(), encrypted_data, ciphertext_len);

TK_assertTrue(memcmp(ciphertext, encrypted_data, bytes) == 0);
TK_assertTrue(bytes == msg_len);

Расшифровка теста (не проходит):

std::string decrypted_msg(
"Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe.");
std::string encoded_msg(
"WFcN70vG6REhBj6ccvXpvoF3UzeMOahuKpBulwyAxMG6Xx9ttt4WQtwvTbi27RvnHd3NxaJ2CvfzOgZAFxMfU/t8hc9smLBryNPTWXSVvG7+HF+hrGP9dwVaDX36MYdsiZV+7BdJnJ6EIGhTBnmWWtReheC5Ju/ledYymChmqQ==");
std::string gcm_tag("hDlT5X7CGPwoPmVZSP2ezQ==");
const size_t msg_len = 127;
uint8_t iv[8], tag[16], enc_msg[msg_len], msg[msg_len], aad[msg_len];

// decode encrypted data
Base64::decode(encoded_msg.c_str(), encoded_msg.length(), enc_msg, msg_len);
Base64::decode(gcm_tag.c_str(), gcm_tag.length(), tag, sizeof(tag));

memset(iv, 1, sizeof(iv));
aescipher cipher(secret, secretlen, iv, sizeof(iv));
size_t bytes = cipher.decrypt(enc_msg, msg_len, msg, msg_len, aad, msg_len, tag);
TK_assertTrue(bytes == msg_len);
TK_assertTrue(strncmp(reinterpret_cast<const char*>(msg), decrypted_msg.c_str(), bytes) == 0);

Обратите внимание, что в тестовом модуле расшифровки, aescipher::decrypt терпит неудачу в EVP_DecryptFinal_ex(context, dst + temp_len, &temp_len),

0

Решение

Задача ещё не решена.

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]