OpenSSL C example of AES-GCM using EVP interfaces

后端 未结 4 1451
名媛妹妹
名媛妹妹 2021-02-01 23:09

For AES-GCM encryption/decryption, I tried this, but it has a problem.

ctx     = EVP_CIPHER_CTX_new();

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


#define         


        
相关标签:
4条回答
  • 2021-02-01 23:21

    Here is an example to encrypt and decrypt 128 bytes every call to update for example:

      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.

    0 讨论(0)
  • 2021-02-01 23:24

    OpenSSL is not responsible for authentication. You should check the return value of EVP_DecryptFinal. If it is 1 then the authentication TAG of the decrypted data, equals the TAG you provided.

    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.

    0 讨论(0)
  • 2021-02-01 23:31

    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.

    OpenSSL now provides a perfectly functional example of AES GCM, written in C. It even includes test vectors. You can find it here https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c or search for "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. 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." (openssl citation)

    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. This could have changed in newer versions of OpenSSL but as of 2013 it caused encryption failure.

    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.

    #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
    }
    
    0 讨论(0)
  • 2021-02-01 23:43

    OpenSSL has a nice wiki page on using AES-GCM ciphers. Code examples are also provided. The link to the page is Authenticated_Decryption_using_GCM_mode

    I followed this wiki and worked out decryption for 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(). 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.

    0 讨论(0)
提交回复
热议问题