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

对于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); 

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

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

以下是每次更新调用时加密和解密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。

为现代性编辑的答案:

您必须检查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引用 )

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

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

 #include  #include  #include  #include  #include  #include  #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有一个关于使用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()返回的值。 如果您的密码文本被修改了一点,它仍然可以被解密,但最终的返回值将不是真的,因为无法validation身份validation标记(或mac)。

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

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

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