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

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

...