OpenSSL C example of AES-GCM using EVP interfaces
对于AES-GCM加密/解密,我尝试过这样做,但是有问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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的任何合适示例吗?
这是一个示例,该示例在每次调用更新时加密和解密128个字节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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现在提供了用C编写的AES GCM的功能完善的示例。它甚至包括测试向量。您可以在这里找到它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和AAD的0调用EVP_EncryptUpdate_ex()。在更新版本的OpenSSL中可能已更改,但从2013年开始,它导致加密失败。
对于这个问题,这已经远远超出了范围,但是如果它可以帮助任何人,这是使用OpenSSL API的有效的iOS / Objective C实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | #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在使用AES-GCM密码方面有一个不错的Wiki页面。还提供了代码示例。该页面的链接为Authenticated_Decryption_using_GCM_mode
我按照此Wiki进行了AES-GCM的解密。代码段复制到下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 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)。
OpenSSL不负责身份验证。您应该检查返回值
如果标签不同,则应将伪造的解密数据丢弃。
如果标签相等,则数据正常。
由于身份验证是增量式的,并且可以对Update进行多次调用,因此必须先解密数据,然后才能完成身份验证。