使用EVP接口的AES-GCM的OpenSSL C示例

时间:2012-08-28 04:57:13

标签: c openssl aes aes-gcm

对于AES-GCM加密/解密,我试过这个,但它有问题。

ctx     = EVP_CIPHER_CTX_new();

//Get the cipher.
cipher  = EVP_aes_128_gcm ();


#define     GCM_IV      "000000000000"
#define     GCM_ADD     "0000"
#define     TAG_SIZE    16
#define     ENC_SIZE    64


//Encrypt the data first.
//Set the cipher and context only.
retv    = EVP_EncryptInit (ctx, cipher, NULL, NULL);

//Set the nonce and tag sizes.
//Set IV length. [Optional for GCM].

retv    = EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_SET_IVLEN, strlen((const char *)GCM_IV), NULL);

//Now initialize the context with key and IV. 
retv    = EVP_EncryptInit (ctx, NULL, (const unsigned char *)keybuf, (const unsigned char *)GCM_IV);

//Add Additional associated data (AAD). [Optional for GCM]
retv    = EVP_EncryptUpdate (ctx, NULL, (int *)&enclen, (const unsigned char *)GCM_ADD, strlen(GCM_ADD));

//Now encrypt the data.
retv    = EVP_EncryptUpdate (ctx, (unsigned char *)encm, (int *)&enclen, (const unsigned char *)msg, _tcslen (msg) *sizeof(Char));

//Finalize.
retv    = EVP_EncryptFinal (ctx, (unsigned char *)encm + enclen, (int *)&enclen2);
enclen  += enclen2;


//Append authentication tag at the end.
retv    = EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_GET_TAG, TAG_SIZE, (unsigned char *)encm + enclen);

//DECRYPTION PART
//Now Decryption of the data.
//Then decrypt the data.
//Set just cipher.
retv    = EVP_DecryptInit(ctx, cipher, NULL, NULL);

//Set Nonce size.
retv    = EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_SET_IVLEN, strlen((const char *)GCM_IV), NULL);

//Set Tag from the data.
retv    = EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_SET_TAG, TAG_SIZE, (unsigned char *)encm + enclen);

//Set key and IV (nonce).
retv    = EVP_DecryptInit (ctx, NULL, (const unsigned char*)keybuf, (const unsigned char *)GCM_IV);

//Add Additional associated data (AAD).
retv    = EVP_DecryptUpdate (ctx, NULL, (int *)&declen, (const unsigned char *)GCM_ADD,
                             strlen((const char *)GCM_ADD));

//Decrypt the data.
retv    = EVP_DecryptUpdate (ctx, decm, (int *)&declen, (const unsigned char *)encm, enclen);


//Finalize.
retv    = EVP_DecryptFinal (ctx, (unsigned char*)decm + declen, (int *)&declen2);

此代码工作正常(经过一些修改)。它正在加密和解密消息。 问题是,在解密之前修改密文时,它仍会解密文本(但是,错误)。 根据我对经过身份验证的加密的理解,在这种情况下,它不应该解密修改后的密文。

我哪里错了? 我可以使用OpenSSL的EVP接口获得任何合适的AES-GCM示例吗?

4 个答案:

答案 0 :(得分:19)

以下是每次更新调用时加密和解密128个字节的示例:

  int howmany, dec_success, len;
  const EVP_CIPHER *cipher;
  switch(key_len)
  {
  case 128: cipher  = EVP_aes_128_gcm ();break;
  case 192: cipher  = EVP_aes_192_gcm ();break;
  case 256: cipher  = EVP_aes_256_gcm ();break;
  default:break;
  }
  // Encrypt
  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
  EVP_EncryptInit (ctx, cipher, KEY, IV);
  EVP_EncryptUpdate (ctx, NULL, &howmany, AAD, aad_len);
  len = 0;
  while(len <= in_len-128)
  {
     EVP_EncryptUpdate (ctx, CIPHERTEXT+len, &howmany, PLAINTEXT+len, 128);
     len+=128;
  }
  EVP_EncryptUpdate (ctx, CIPHERTEXT+len, &howmany, PLAINTEXT+len, in_len - len);
  EVP_EncryptFinal (ctx, TAG, &howmany);
  EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_GET_TAG, 16, TAG);  
  EVP_CIPHER_CTX_free(ctx);
  // Decrypt
  ctx = EVP_CIPHER_CTX_new();      
  EVP_DecryptInit (ctx, cipher, KEY, IV);
  EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_SET_TAG, 16, ref_TAG);
  EVP_DecryptInit (ctx, NULL, KEY, IV);
  EVP_DecryptUpdate (ctx, NULL, &howmany, AAD, aad_len);
  len = 0;
  while(len <= in_len-128)
  {
     EVP_DecryptUpdate (ctx, decrypted_CT+len, &howmany, CIPHERTEXT+len, 128);
     len+=128;
  }
  EVP_DecryptUpdate (ctx, decrypted_CT+len, &howmany, CIPHERTEXT+len, in_len-len);
  dec_success = EVP_DecryptFinal (ctx, dec_TAG, &howmany);
  EVP_CIPHER_CTX_free(ctx);

最后,您应该检查dec_success的值是否为1。 如果修改CIPHERTEXT,在解密之前,应该得到值0。

答案 1 :(得分:8)

为现代性而编辑的答案:

您必须检查EVP_DecryptFinal()(或EVP_DecryptFinal_ex())调用的返回值,以确定您是否已成功解密密文。

OpenSSL现在提供了一个完美的AES GCM示例,用C语言编写。它甚至包括测试向量。您可以在https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c找到它或搜索“openssl evp aesgcm.c”

最初的5年问题及其接受的答案显示了使用EVP_ * Init()和EVP_ * Final()API的代码。这些已被弃用并被EVP_ * Init_ex()和EVP_ * Final_ex()取代,因为它们可以重用现有的上下文,而无需在每次调用时分配和释放它。 (openssl citation

根据我的经验,如果您正在为这些调用编写包装函数,为NULL调用EVP_EncryptUpdate_ex(),为AAD调用0。这可能在较新版本的OpenSSL中发生了变化,但截至2013年,它导致加密失败。

这个问题远远超出了这个问题的范围,但是如果它可以帮助任何人,这里是一个使用OpenSSL API的iOS / Objective C实现。

#include <openssl/rand.h>
#include <openssl/ecdsa.h>
#include <openssl/obj_mac.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/evp.h>

#define AES_256_KEY_LENGTH      32
#define AES_256_KEY_LENGTH_BITS 256
#define AES_256_IVEC_LENGTH     12
#define AES_256_GCM_TAG_LENGTH  16

// encrypt plaintext.
// key, ivec and tag buffers are required, aad is optional
// depending on your use, you may want to convert key, ivec, and tag to NSData/NSMutableData
+ (BOOL) aes256gcmEncrypt:(NSData*)plaintext
               ciphertext:(NSMutableData**)ciphertext
                      aad:(NSData*)aad
                      key:(const unsigned char*)key
                     ivec:(const unsigned char*)ivec
                      tag:(unsigned char*)tag {

    int status = 0;
    *ciphertext = [NSMutableData dataWithLength:[plaintext length]];
    if (! *ciphertext)
        return NO;

    // set up to Encrypt AES 256 GCM
    int numberOfBytes = 0;
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex (ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);

    // set the key and ivec
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_256_IVEC_LENGTH, NULL);
    EVP_EncryptInit_ex (ctx, NULL, NULL, key, ivec);

    // add optional AAD (Additional Auth Data)
    if (aad)
        status = EVP_EncryptUpdate( ctx, NULL, &numberOfBytes, [aad bytes], [aad length]);

    unsigned char * ctBytes = [*ciphertext mutableBytes];
    EVP_EncryptUpdate (ctx, ctBytes, &numberOfBytes, [plaintext bytes], (int)[plaintext length]);
    status = EVP_EncryptFinal_ex (ctx, ctBytes+numberOfBytes, &numberOfBytes);

    if (status && tag) {
        status = EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_GCM_GET_TAG, AES_256_GCM_TAG_LENGTH, tag);
    }
    EVP_CIPHER_CTX_free(ctx);
    return (status != 0); // OpenSSL uses 1 for success
}

// decrypt ciphertext.
// key, ivec and tag buffers are required, aad is optional
// depending on your use, you may want to convert key, ivec, and tag to NSData/NSMutableData
+ (BOOL) aes256gcmDecrypt:(NSData*)ciphertext
                plaintext:(NSMutableData**)plaintext
                      aad:(NSData*)aad
                      key:(const unsigned char *)key
                     ivec:(const unsigned char *)ivec
                      tag:(unsigned char *)tag {

    int status = 0;

    if (! ciphertext || !plaintext || !key || !ivec)
        return NO;

    *plaintext = [NSMutableData dataWithLength:[ciphertext length]];
    if (! *plaintext)
        return NO;

    // set up to Decrypt AES 256 GCM
    int numberOfBytes = 0;
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    EVP_DecryptInit_ex (ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);

    // set the key and ivec
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_256_IVEC_LENGTH, NULL);
    status = EVP_DecryptInit_ex (ctx, NULL, NULL, key, ivec);

    // Set expected tag value. A restriction in OpenSSL 1.0.1c and earlier requires the tag before any AAD or ciphertext
    if (status && tag)
        EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, AES_256_GCM_TAG_LENGTH, tag);

    // add optional AAD (Additional Auth Data)
    if (aad)
        EVP_DecryptUpdate(ctx, NULL, &numberOfBytes, [aad bytes], [aad length]);

    status = EVP_DecryptUpdate (ctx, [*plaintext mutableBytes], &numberOfBytes, [ciphertext bytes], (int)[ciphertext length]);
    if (! status) {
        //DDLogError(@"aes256gcmDecrypt: EVP_DecryptUpdate failed");
        return NO;
    }
    EVP_DecryptFinal_ex (ctx, NULL, &numberOfBytes);
    EVP_CIPHER_CTX_free(ctx);
    return (status != 0); // OpenSSL uses 1 for success
}

答案 2 :(得分:3)

OpenSSL有一个关于使用AES-GCM密码的不错的wiki页面。还提供了代码示例。该页面的链接是Authenticated_Decryption_using_GCM_mode

我关注了这个wiki并为AES-GCM制定了解密方法。代码段复制在

下面
int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *aad,
    int aad_len, unsigned char *tag, unsigned char *key, unsigned char *iv,
    unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len;
    int plaintext_len;
    int ret;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

    /* Initialise the decryption operation. */
    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();

    /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
        handleErrors();

    /* Initialise key and IV */
    if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
        handleErrors();

    /* Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        handleErrors();
    plaintext_len = len;

    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
        handleErrors();

    /* Finalise the decryption. A positive return value indicates success,
     * anything else is a failure - the plaintext is not trustworthy.
     */
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    if(ret > 0)
    {
        /* Success */
        plaintext_len += len;
        return plaintext_len;
    }
    else
    {
        /* Verify failed */
        return -1;
    }
}

另外,正如人们所指出的那样,你应该检查从EVP_DecryptFinal_ex()返回的值。如果您的密码文本被修改了一点,它仍然可以被解密,但最终的返回值将不是真的,因为无法验证身份验证标记(或mac)。

答案 3 :(得分:2)

OpenSSL不负责身份验证。您应该检查EVP_DecryptFinal的返回值。如果为1则解密数据的验证TAG等于您提供的TAG。

如果标签不同,则应将解密数据丢弃为伪造。 如果标签相同,那么数据就可以了。

由于身份验证是增量的,并且可以多次调用Update,因此必须先解密数据,然后才能完成身份验证。