4

我正在处理使用AES CCM 模式256 位密钥长度)加密大文件的任务。其他用于加密的参数是:

  • 标签大小:8字节
  • iv大小:12 字节

由于我们已经使用OpenSSL 1.0.1c ,因此我也想将它用于此任务。

文件的大小事先不知道,它们可能非常大。这就是为什么我想按块读取它们并使用EVP_EncryptUpdate单独加密每个块,直到文件大小。

不幸的是,只有当整个文件一次加密时,加密才对我有效。如果我尝试多次调用它,我会收到 EVP_EncryptUpdate 错误或奇怪的崩溃。我使用 gcc 4.7.2 在 Windows 7 和 Ubuntu Linux 上测试了加密。

我无法在OpenSSL站点上找到无法(或不可能)逐块加密数据的信息。

附加参考:

请参阅下面的代码,该代码演示了我试图实现的目标。不幸的是,它在 for 循环中指示的地方失败了。

#include <QByteArray>
#include <openssl/evp.h>

// Key in HEX representation
static const char keyHex[] = "d896d105b05aaec8305d5442166d5232e672f8d5c6dfef6f5bf67f056c4cf420";
static const char ivHex[]  = "71d90ebb12037f90062d4fdb";

// Test patterns
static const char orig1[] = "Very secret message.";

const int c_tagBytes      = 8;
const int c_keyBytes      = 256 / 8;
const int c_ivBytes       = 12;

bool Encrypt()
{
    EVP_CIPHER_CTX *ctx;
    ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);

    QByteArray keyArr = QByteArray::fromHex(keyHex);
    QByteArray ivArr = QByteArray::fromHex(ivHex);

    auto key = reinterpret_cast<const unsigned char*>(keyArr.constData());
    auto iv = reinterpret_cast<const unsigned char*>(ivArr.constData());

    // Initialize the context with the alg only
    bool success = EVP_EncryptInit(ctx, EVP_aes_256_ccm(), nullptr, nullptr);
    if (!success) {
        printf("EVP_EncryptInit failed.\n");
        return success;
    }

    success = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, c_ivBytes, nullptr);
    if (!success) {
        printf("EVP_CIPHER_CTX_ctrl(EVP_CTRL_CCM_SET_IVLEN) failed.\n");
        return success;
    }
    success = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, c_tagBytes, nullptr);
    if (!success) {
        printf("EVP_CIPHER_CTX_ctrl(EVP_CTRL_CCM_SET_TAG) failed.\n");
        return success;
    }

    success = EVP_EncryptInit(ctx, nullptr, key, iv);
    if (!success) {
        printf("EVP_EncryptInit failed.\n");
        return success;
    }

    const int bsize = 16;
    const int loops = 5;
    const int finsize = sizeof(orig1)-1; // Don't encrypt '\0'

    // Tell the alg we will encrypt size bytes
    // http://www.fredriks.se/?p=23
    int outl = 0;
    success = EVP_EncryptUpdate(ctx, nullptr, &outl, nullptr, loops*bsize + finsize);
    if (!success) {
        printf("EVP_EncryptUpdate for size failed.\n");
        return success;
    }
    printf("Set input size. outl: %d\n", outl);

    // Additional authentication data (AAD) is not used, but 0 must still be
    // passed to the function call:
    // http://incog-izick.blogspot.in/2011/08/using-openssl-aes-gcm.html
    static const unsigned char aadDummy[] = "dummyaad";
    success = EVP_EncryptUpdate(ctx, nullptr, &outl, aadDummy, 0);
    if (!success) {
        printf("EVP_EncryptUpdate for AAD failed.\n");
        return success;
    }
    printf("Set dummy AAD. outl: %d\n", outl);

    const unsigned char *in = reinterpret_cast<const unsigned char*>(orig1);
    unsigned char out[1000];
    int len;

    // Simulate multiple input data blocks (for example reading from file)
    for (int i = 0; i < loops; ++i) {
        // ** This function fails ***
        if (!EVP_EncryptUpdate(ctx, out+outl, &len, in, bsize)) {
            printf("DHAesDevice: EVP_EncryptUpdate failed.\n");
            return false;
        }
        outl += len;
    }

    if (!EVP_EncryptUpdate(ctx, out+outl, &len, in, finsize)) {
        printf("DHAesDevice: EVP_EncryptUpdate failed.\n");
        return false;
    }
    outl += len;

    int finlen;
    // Finish with encryption
    if (!EVP_EncryptFinal(ctx, out + outl, &finlen)) {
        printf("DHAesDevice: EVP_EncryptFinal failed.\n");
        return false;
    }
    outl += finlen;
    // Append the tag to the end of the encrypted output
    if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, c_tagBytes, out + outl)) {
        printf("DHAesDevice: EVP_CIPHER_CTX_ctrl failed.\n");
        return false;
    };
    outl += c_tagBytes;
    out[outl] = '\0';

    EVP_CIPHER_CTX_cleanup(ctx);
    EVP_CIPHER_CTX_free(ctx);

    QByteArray enc(reinterpret_cast<const char*>(out));

    printf("Plain text size: %d\n", loops*bsize + finsize);
    printf("Encrypted data size: %d\n", outl);

    printf("Encrypted data: %s\n", enc.toBase64().data());

    return true;
}

编辑(错误的解决方案)

我收到的反馈让我想到了一个不同的方向,我发现必须为每个要加密的块调用 EVP_EncryptUpdate 的大小,而不是文件的总大小。我在块被加密之前移动了它:像这样:

for (int i = 0; i < loops; ++i) {
    int buflen;
    (void)EVP_EncryptUpdate(m_ctx, nullptr, &buflen, nullptr, bsize);
    // Resize the output buffer to buflen here
    // ...
    // Encrypt into target buffer
    (void)EVP_EncryptUpdate(m_ctx, out, &len, in, buflen);
    outl += len;
}

AES CCM 加密逐块以这种方式工作,但不正确,因为每个块都被视为独立消息。

编辑 2

OpenSSL 的实现只有在一次加密完整的消息时才能正常工作。

http://marc.info/?t=136256200100001&r=1&w=1

我决定改用 Crypto++。

4

2 回答 2

0

对于 AEAD-CCM 模式,您无法在将相关数据馈送到上下文后加密数据。加密所有数据,并且只有在它传递关联数据之后。

于 2013-02-28T15:11:13.390 回答
0

我在这里发现了一些误解

首先 EVP_EncryptUpdate(ctx, nullptr, &outl 以这种方式调用是为了知道需要多少输出缓冲区,以便您可以分配缓冲区,第二次将第二个参数作为有效的足够大的缓冲区来保存数据。

当您实际添加加密输出时,您也传递了错误的(被先前的调用覆盖)值。

于 2013-02-28T15:17:05.097 回答