Я пишу класс 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)
.