对于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示例吗?
答案 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,因此必须先解密数据,然后才能完成身份验证。