简体   繁体   English

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

[英]OpenSSL C example of AES-GCM using EVP interfaces

For AES-GCM encryption/decryption, I tried this, but it has a problem. 对于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);

This code is working fine (with some modifications). 这段代码运行良好(进行了一些修改)。 It is encrypting and decrypting the message. 它正在加密和解密消息。 The problem is that when cipher text is modified before decryption, it still decrypts the text (however, wrong). 问题在于,在解密之前修改密文时,它仍然对文本解密(但是,错误)。 As per my understanding of authenticated encryption, in such cases, it should not decrypt the modified cipher texts. 根据我对认证加密的理解,在这种情况下,它不应解密修改后的密文。

Where am I wrong? 我哪里错了? Can I get any suitable example of AES-GCM using EVP interfaces of OpenSSL? 我可以使用OpenSSL的EVP接口获得AES-GCM的任何合适示例吗?

Here is an example to encrypt and decrypt 128 bytes every call to update for example: 这是一个示例,该示例在每次调用更新时加密和解密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);

In the end you should check that the value of dec_success is 1. If you modify the CIPHERTEXT, before you decrypt it, you should get value of 0. 最后,您应该检查dec_success的值是否为1。如果修改CIPHERTEXT,则在解密之前,应将其值设为0。

Answer edited for modernity: 为现代而编辑的答案:

You must check the return value from the call to EVP_DecryptFinal() (or EVP_DecryptFinal_ex()) in order to determine if you have successfully decrypted the ciphertext. 您必须检查从调用EVP_DecryptFinal()(或EVP_DecryptFinal_ex())返回的值,以确定您是否已成功解密密文。

OpenSSL now provides a perfectly functional example of AES GCM, written in C. It even includes test vectors. OpenSSL现在提供了用C编写的AES GCM的功能完善的示例。它甚至包括测试向量。 You can find it here https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c or search for "openssl evp aesgcm.c" 您可以在这里找到它https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c或搜索“ openssl evp aesgcm.c”

The original 5-year old question, and its accepted answer, show code that uses the EVP_*Init() and EVP_*Final() APIs. 最初的5年问题及其公认的答案显示了使用EVP_ * Init()和EVP_ * Final()API的代码。 These have been deprecated and replaced by EVP_*Init_ex() and EVP_*Final_ex() "because they can reuse an existing context without allocating and freeing it up on each call." 这些已被弃用,并由EVP_ * Init_ex()和EVP_ * Final_ex()取代,“因为它们可以重用现有上下文,而无需在每次调用时分配和释放它。” ( openssl citation ) openssl引文

In my experience, if you are writing a wrapper function for these calls, do not call EVP_EncryptUpdate_ex() with NULL and 0 for the AAD. 以我的经验,如果要为这些调用编写包装函数, 请不要使用NULL和AAD的0调用EVP_EncryptUpdate_ex()。 This could have changed in newer versions of OpenSSL but as of 2013 it caused encryption failure. 在更新版本的OpenSSL中可能已更改,但从2013年开始,它导致加密失败。

It is pretty far out of scope for this question but in case it helps anyone, here is a working iOS / Objective C implementation that uses the OpenSSL APIs. 对于这个问题,这已经远远超出了范围,但是如果它可以帮助任何人,这是使用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
}

OpenSSL has a nice wiki page on using AES-GCM ciphers. OpenSSL在使用AES-GCM密码方面有一个不错的Wiki页面。 Code examples are also provided. 还提供了代码示例。 The link to the page is Authenticated_Decryption_using_GCM_mode 该页面的链接为Authenticated_Decryption_using_GCM_mode

I followed this wiki and worked out decryption for AES-GCM. 我按照此Wiki进行了AES-GCM的解密。 Code segment is copied below 代码段复制到下面

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;
    }
}

Also, as people pointed out, you should check the value returned from EVP_DecryptFinal_ex(). 另外,正如人们指出的那样,您应该检查从EVP_DecryptFinal_ex()返回的值。 If your cipher text is modified a bit, it still can be decrypted, but the final returning value will not be true, because the authentication tag (or the mac) can not be verified. 如果您对密文进行了一点修改,它仍然可以解密,但是最终的返回值将不是真实的,因为无法验证身份验证标签(或mac)。

OpenSSL is not responsible for authentication. OpenSSL不负责身份验证。 You should check the return value of EVP_DecryptFinal . 您应该检查EVP_DecryptFinal的返回值。 If it is 1 then the authentication TAG of the decrypted data, equals the TAG you provided. 如果为1,则解密数据的身份验证TAG等于您提供的TAG。

If the tags are different, then you should discard the decrypted data as forged. 如果标签不同,则应将伪造的解密数据丢弃。 If the tags are equal, then the data is ok. 如果标签相等,则数据正常。

Because the authentication is incremental, and can take several calls to Update, the data must be decrypted before the authentication can be completed. 因为身份验证是增量的,并且可以对Update进行多次调用,所以必须先解密数据,然后才能完成身份验证。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM