AES-GCM加密算法-程序员宅基地

技术标签: 安全  

AES是一种对称加密算法,它的相关概念在此不赘述。

GCM ( Galois/Counter Mode) 指的是该对称加密采用Counter模式,并带有GMAC消息认证码。

在详细介绍AES-GCM之前,我们先了解一些相关概念。

 

下文中出现的符号:

 

Ek 使用秘钥k对输入做对称加密运算
XOR 异或运算
Mh 将输入与秘钥h在有限域GF(2^128)上做乘法

 

 

ECB( Electronic Mode 电子密码本模式)

当我们有一段明文,需要对其进行AES加密时,需要对明文进行分组,分组长度可为128,256,或512bits。采用ECB模式的分组密码算法加密过程如下图:

由上图可以看出,明文中重复的排列会反映在密文中。

并且,当密文被篡改时,解密后对应的明文分组也会出错,且解密者察觉不到密文被篡改了。也就是说,ECB不能提供对密文的完整性校验。
因此,在任何情况下都不推荐使用ECB模式。

 

 

CTR ( CounTeR 计数器模式)

在计数器模式下,我们不再对密文进行加密,而是对一个逐次累加的计数器进行加密,用加密后的比特序列与明文分组进行 XOR得到密文。过程如下图:


计数器模式下,每次与明文分组进行XOR的比特序列是不同的,因此,计数器模式解决了ECB模式中,相同的明文会得到相同的密文的问题。CBC,CFB,OFB模式都能解决这个问题,但CTR的另两个优点是:1)支持加解密并行计算,可事先进行加解密准备;2)错误密文中的对应比特只会影响明文中的对应比特等优点。
但CTR仍然不能提供密文消息完整性校验的功能。
有的人可能会想到,如果将密文的hash值随密文一起发送,密文接收者对收到的密文计算hash值,与收到的hash值进行比对,这样是否就能校验消息的完整性呢?

再仔细想想,就能发现这其中的漏洞。当篡改者截获原始的密文消息时,先篡改密文,而后计算篡改后的密文hash, 替换掉原始消息中的密文hash。这样,消息接收者仍然没有办法发现对源密文的篡改。可见,使用单向散列函数计算hash值仍然不能解决消息完整性校验的问题。

MAC  ( Message Authentication Code, 消息验证码)

想要校验消息的完整性,必须引入另一个概念:消息验证码。消息验证码是一种与秘钥相关的单项散列函数。

密文的收发双发需要提前共享一个秘钥。密文发送者将密文的MAC值随密文一起发送,密文接收者通过共享秘钥计算收到密文的MAC值,这样就可以对收到的密文做完整性校验。当篡改者篡改密文后,没有共享秘钥,就无法计算出篡改后的密文的MAC值。

如果生成密文的加密模式是CTR,或者是其他有初始IV的加密模式,别忘了将初始的计时器或初始向量的值作为附加消息与密文一起计算MAC。

 

GMAC ( Galois message authentication code mode, 伽罗瓦消息验证码 )

对应到上图中的消息认证码,GMAC就是利用伽罗华域(Galois Field,GF,有限域)乘法运算来计算消息的MAC值。假设秘钥长度为128bits, 当密文大于128bits时,需要将密文按128bits进行分组。应用流程如下图:

 

 

GCM( Galois/Counter Mode ) 

GCM中的G就是指GMAC,C就是指CTR。

GCM可以提供对消息的加密和完整性校验,另外,它还可以提供附加消息的完整性校验。在实际应用场景中,有些信息是我们不需要保密,但信息的接收者需要确认它的真实性的,例如源IP,源端口,目的IP,IV,等等。因此,我们可以将这一部分作为附加消息加入到MAC值的计算当中。下图的Ek表示用对称秘钥k对输入做AES运算。最后,密文接收者会收到密文、IV(计数器CTR的初始值)、MAC值。

AES(Advanced Encryption Standard)即高级加密标准,由美国国家标准和技术协会(NIST)于2000年公布,它是一种对称加密算法。关于AES的更多介绍可以参考:https://blog.csdn.net/fengbingchun/article/details/100139524

AES的GCM(Galois/Counter Mode)模式本质上是AES的CTR模式(计数器模式)加上GMAC(Galois Message Authentication Code, 伽罗华消息认证码)进行哈希计算的一种组合模式。GCM可以提供对消息的加密和完整性校验,另外,它还可以提供附加消息的完整性校验。

OpenSSL中的EVP接口支持执行经过身份验证的加密和解密的功能,以及将未加密的关联数据附加到消息的选项。这种带有关联数据的身份验证加密(AEAD, Authenticated-Encryption with Associated-Data)方案通过对数据进行加密来提供机密性,并通过在加密数据上创建MAC标签来提供真实性保证。

Key:对称密钥,它的长度可以为128、192、256bits,用来加密明文的密码。

IV(Initialisation Vector):初始向量,它的选取必须随机。通常以明文的形式和密文一起传送。它的作用和MD5的”加盐”有些类似,目的是防止同样的明文块,始终加密成同样的密文块。

AAD(Additional Authenticated Data):附加身份验证数据。AAD数据不需要加密,通常以明文形式与密文一起传递给接收者。

Mac tag(MAC标签):将确保数据在传输和存储过程中不会被意外更改或恶意篡改。该标签随后在解密操作期间使用,以确保密文和AAD未被篡改。在加密时,Mac tag由明文、密钥Key、IV、AAD共同产生。

以下为测试代码:

namespace {
 
static const unsigned char gcm_key[] = { // 32 bytes, Key
    0xee, 0xbc, 0x1f, 0x57, 0x48, 0x7f, 0x51, 0x92, 0x1c, 0x04, 0x65, 0x66,
    0x5f, 0x8a, 0xe6, 0xd1, 0x65, 0x8b, 0xb2, 0x6d, 0xe6, 0xf8, 0xa0, 0x69,
    0xa3, 0x52, 0x02, 0x93, 0xa5, 0x72, 0x07, 0x8f
};
 
static const unsigned char gcm_iv[] = { // 12 bytes, IV(Initialisation Vector)
    0x99, 0xaa, 0x3e, 0x68, 0xed, 0x81, 0x73, 0xa0, 0xee, 0xd0, 0x66, 0x84
};
 
// Additional Authenticated Data(AAD): it is not encrypted, and is typically passed to the recipient in plaintext along with the ciphertext
static const unsigned char gcm_aad[] = { // 16 bytes
    0x4d, 0x23, 0xc3, 0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43,
    0x7f, 0xec, 0x78, 0xde
};
 
std::unique_ptr<unsigned char[]> aes_gcm_encrypt(const char* plaintext, int& length, unsigned char* tag)
{
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    // Set cipher type and mode
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
    // Set IV length if default 96 bits is not appropriate
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(gcm_iv), nullptr);
    // Initialise key and IV
    EVP_EncryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);
    // Zero or more calls to specify any AAD
    int outlen;
    EVP_EncryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));
    unsigned char outbuf[1024];
    // Encrypt plaintext
    EVP_EncryptUpdate(ctx, outbuf, &outlen, (const unsigned char*)plaintext, strlen(plaintext));
    length = outlen;
    std::unique_ptr<unsigned char[]> ciphertext(new unsigned char[length]);
    memcpy(ciphertext.get(), outbuf, length);
    // Finalise: note get no output for GCM
    EVP_EncryptFinal_ex(ctx, outbuf, &outlen);
    // Get tag
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, outbuf);
    memcpy(tag, outbuf, 16);
    // Clean up
    EVP_CIPHER_CTX_free(ctx);
    return ciphertext;
}
 
std::unique_ptr<unsigned char[]> aes_gcm_decrypt(const unsigned char* ciphertext, int& length, const unsigned char* tag)
{
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    // Select cipher
    EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
    // Set IV length, omit for 96 bits
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(gcm_iv), nullptr);
    // Specify key and IV
    EVP_DecryptInit_ex(ctx, nullptr, nullptr, gcm_key, gcm_iv);
    int outlen;
    // Zero or more calls to specify any AAD
    EVP_DecryptUpdate(ctx, nullptr, &outlen, gcm_aad, sizeof(gcm_aad));
    unsigned char outbuf[1024];
    // Decrypt plaintext
    EVP_DecryptUpdate(ctx, outbuf, &outlen, ciphertext, length);
    // Output decrypted block
    length = outlen;
    std::unique_ptr<unsigned char[]> plaintext(new unsigned char[length]);
    memcpy(plaintext.get(), outbuf, length);
    // Set expected tag value
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (void*)tag);
    // Finalise: note get no output for GCM
    int rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
    // Print out return value. If this is not successful authentication failed and plaintext is not trustworthy.
    fprintf(stdout, "Tag Verify %s\n", rv > 0 ? "Successful!" : "Failed!");
    EVP_CIPHER_CTX_free(ctx);
    return plaintext;
}
 
} // namespace
 
int test_openssl_aes_gcm()
{
    /* reference:
        https://github.com/openssl/openssl/blob/master/demos/evp/aesgcm.c
        https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption
    */
    fprintf(stdout, "Start AES GCM 256 Encrypt:\n");
    const char* plaintext = "1234567890ABCDEFG!@#$%^&*()_+[]{};':,.<>/?|";
    fprintf(stdout, "src plaintext: %s, length: %d\n", plaintext, strlen(plaintext));
    int length = 0;
    std::unique_ptr<unsigned char[]> tag(new unsigned char[16]);
    std::unique_ptr<unsigned char[]> ciphertext = aes_gcm_encrypt(plaintext, length, tag.get());
    fprintf(stdout, "length: %d, ciphertext: ", length);
    for (int i = 0; i < length; ++i)
        fprintf(stdout, "%02x ", ciphertext.get()[i]);
    fprintf(stdout, "\nTag: ");
    for (int i = 0; i < 16; ++i)
        fprintf(stdout, "%02x ", tag.get()[i]);
    fprintf(stdout, "\n");
 
    fprintf(stdout, "\nStart AES GCM 256 Decrypt:\n");
    std::unique_ptr<unsigned char[]> result = aes_gcm_decrypt(ciphertext.get(), length, tag.get());
    fprintf(stdout, "length: %d, decrypted plaintext: ", length);
    for (int i = 0; i < length; ++i)
        fprintf(stdout, "%c", result.get()[i]);
    fprintf(stdout, "\n");
 
    if (strncmp(plaintext, (const char*)result.get(), length) == 0) {
        fprintf(stdout, "decrypt success\n");
        return 0;
    } else {
        fprintf(stderr, "decrypt fail\n");
        return -1;
    }
}
在Windows上执行结果如下:

以上是在OpenSSL较老的版本1.0.1g上执行的,在最新版1.1.1g上以上代码也同样可以执行。

在Windows上编译1.1.1g版本源码执行命令如下:no-asm选项为不开启汇编模式,编译release则将debug-VC-WIN64A改为VC-WIN64即可。

perl Configure debug-VC-WIN64A no-asm --prefix=D:\DownLoad\openssl\win64_debug
nmake
nmake install
在Linux上编译1.1.1g版本源码执行命令如下:

./config --prefix=/home/sensetime/Downloads/openssl/install
make
make insall
在1.1.1g源码的demos/evp目录下给出了gcm的示例aesgcm.c,这里将以上测试代码新加为aesgcm2.cpp,编译脚本如下:

#! /bin/bash
 
g++ -o test_aesgcm aesgcm.c -L ../../ -lcrypto -lssl -I../../include
g++ -o test_aesgcm2 aesgcm2.cpp -std=c++11 -L ../../ -lcrypto -lssl -I../../include
 
g++ -o test_aesccm aesccm.c -L ../../ -lcrypto -lssl -I../../include
执行结果如下:注意如果在Windows上执行aesgcm2.cpp,需要额外的 #include <openssl/applink.c> 

GitHub:https://github.com//fengbingchun/OpenSSL_Test
----------------------------------------------------------------------------------------------------------------

转自:http://www.thinkemb.com/wordpress/?p=18

参考:http://blog.csdn.net/shuanyancao/article/details/8985963

http://stackoverflow.com/questions/18152913/aes-aes-cbc-128-aes-cbc-192-aes-cbc-256-encryption-decryption-with-openssl-c

 

Openssl是很常见的C接口的库,个人觉得易用。以下是AES加密的使用备忘。如果你有一定的密码学基础,那么就很好理解。代码是从网上弄下来的(原始地址已经忘记了),然后在尝试的过程中改了一点东西。其它的cbc、cfb、ecb加密方式的用法都是类似的,只是函数名有点区别,就不一一列举了。

【yasi】IV: Initialization Vector,即初始化向量

一、接口简介

 

 
  1. //设置加密密钥,使用字符缓冲区

  2. int AES_set_encrypt_key(

  3. const unsigned char *userKey,

  4. const int bits,

  5. AES_KEY *key);

  6.  
  7. //设置解密密钥,同样适用字符缓冲区

  8. int AES_set_decrypt_key(

  9. const unsigned char *userKey,

  10. const int bits,

  11. AES_KEY *key);

  12.  
  13. //加解密的接口,通过最后的enc来区分是加密还是解密操作

  14. //每次执行AES_cbc_encrypt后,iv(向量)会被更新,

  15. //所以需要自己保存它。

  16. void AES_cbc_encrypt(

  17. const unsigned char *in,

  18. unsigned char *out,

  19. const unsigned long length,

  20. const AES_KEY *key,

  21. unsigned char *ivec,

  22. const int enc);

?

 

二、一个简单的Makefile

【yasi】针对CentOS环境,做了修改

 

LNK_OPT = -g -L/usr/lib64/ -lssl

all:
        rm -f codec
        g++ -g aes_codec.cpp -o codec $(LNK_OPT)

clean:
        rm -f codec


三、示例代码

【yasi】对源链接的代码做了点修改:随机明码 改 静态明码

 

 
  1. #include <stdio.h>

  2. #include <string.h>

  3. #include <openssl/aes.h>

  4. #include <openssl/rand.h>

  5.  
  6. /* file testaes.cpp */

  7.  
  8. static void hexdump(

  9. FILE *f,

  10. const char *title,

  11. const unsigned char *s,

  12. int l)

  13. {

  14. int n = 0;

  15.  
  16. fprintf(f, "%s", title);

  17. for (; n < l; ++n) {

  18. if ((n % 16) == 0) {

  19. fprintf(f, "\n%04x", n);

  20. }

  21. fprintf(f, " %02x", s[n]);

  22. }

  23.  
  24. fprintf(f, "\n");

  25. }

  26.  
  27. int main(int argc, char **argv)

  28. {

  29. //128bits key.

  30. unsigned char rkey[16];

  31. //Internal key.

  32. AES_KEY key;

  33.  
  34. //Testdata.

  35. // [yasi] Make static content instead of random text

  36. unsigned char plaintext[AES_BLOCK_SIZE * 4] =

  37. {

  38. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i',

  39. '0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',

  40. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'i',

  41. '0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7'

  42. };

  43. unsigned char ciphertext[AES_BLOCK_SIZE * 4];

  44. unsigned char checktext[AES_BLOCK_SIZE * 4];

  45.  
  46. //Init vector.

  47. unsigned char iv[AES_BLOCK_SIZE * 4];

  48. //Save vector.

  49. unsigned char saved_iv[AES_BLOCK_SIZE * 4];

  50.  
  51. int nr_of_bits = 0;

  52. int nr_of_bytes = 0;

  53.  
  54. //Zeror buffer.

  55. memset(ciphertext, 0, sizeof ciphertext);

  56. memset(checktext, 0, sizeof checktext);

  57.  
  58. //Generate random

  59. RAND_pseudo_bytes(rkey, sizeof rkey);

  60. RAND_pseudo_bytes(saved_iv, sizeof saved_iv);

  61.  
  62. hexdump(stdout, "== rkey ==",

  63. rkey,

  64. sizeof(rkey));

  65. hexdump(stdout, "== iv ==",

  66. saved_iv,

  67. sizeof(saved_iv));

  68. printf("\n");

  69.  
  70. hexdump(stdout, "== plaintext ==",

  71. plaintext,

  72. sizeof(plaintext));

  73. printf("\n");

  74.  
  75. //Entrypt

  76. memcpy(iv, saved_iv, sizeof(iv));

  77. nr_of_bits = 8 * sizeof(rkey);

  78. AES_set_encrypt_key(rkey, nr_of_bits, &key);

  79. nr_of_bytes = sizeof(plaintext);

  80. AES_cbc_encrypt(plaintext,

  81. ciphertext,

  82. nr_of_bytes,

  83. &key,

  84. iv,

  85. AES_ENCRYPT);

  86.  
  87. hexdump(stdout, "== ciphertext ==",

  88. ciphertext,

  89. sizeof(ciphertext));

  90. printf("\n");

  91. // [yasi] iv is changed in encryption

  92. hexdump(stdout, "== iv changed ==",

  93. iv,

  94. sizeof(iv));

  95. printf("\n");

  96.  
  97. //Decrypt

  98. memcpy(iv, saved_iv, sizeof(iv)); // [yasi] without this line, decrypt will fail because iv is changed in encryption

  99. nr_of_bits = 8 * sizeof(rkey);

  100. AES_set_decrypt_key(rkey, nr_of_bits, &key);

  101. nr_of_bytes = sizeof(ciphertext);

  102.  
  103. AES_cbc_encrypt(ciphertext,

  104. checktext,

  105. nr_of_bytes,

  106. &key, iv,

  107. AES_DECRYPT);

  108. hexdump(stdout, "== checktext ==",

  109. checktext,

  110. sizeof(checktext));

  111. printf("\n");

  112.  
  113. return 0;

  114. }

 

【yasi 运行结果】

[root@ampcommons02 aes-codec]# ./codec
== rkey ==
0000 81 ac 9b 38 1c 02 c5 c8 1d 7c a0 3f 87 be f2 c6
== iv ==
0000 34 a2 f1 f5 f3 93 76 32 cd 77 ad fb c8 82 f2 1b
0010 f3 cc 51 f6 35 f3 49 8d 44 97 8c 2c 89 50 0d d7
0020 68 21 d7 2f 0a 90 29 c1 dd c9 39 bc 7c 4f 18 2f
0030 04 cc 42 5e 84 8e fe a9 c5 49 00 9f 30 55 94 c0

== plaintext ==
0000 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

== ciphertext ==
0000 c4 63 72 29 21 28 7b a2 27 24 4a e4 bb 95 1a d1
0010 b8 13 0e 77 0c 8a a4 09 2f ca 85 43 41 b5 5b d5
0020 a3 60 92 58 5b dd 45 0c e2 62 af f9 43 81 d7 06
0030 41 8e 85 28 3e eb 72 b5 ee 84 8c 27 7e 67 20 f6

== iv changed ==
0000 41 8e 85 28 3e eb 72 b5 ee 84 8c 27 7e 67 20 f6
0010 f3 cc 51 f6 35 f3 49 8d 44 97 8c 2c 89 50 0d d7
0020 68 21 d7 2f 0a 90 29 c1 dd c9 39 bc 7c 4f 18 2f
0030 04 cc 42 5e 84 8e fe a9 c5 49 00 9f 30 55 94 c0

== checktext ==
0000 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

可见,encryption之后,IV立即被修改了。所以,为了能正确decrypt,在decrypt时,必须使用先前encrypt时的IV,即代码第98行。

 

【第二次运行的结果】

[root@ampcommons02 aes-codec]# ./codec
== rkey ==
0000 29 68 75 4d a5 9e 83 9a ed f8 ec bc 2e b8 09 7e
== iv ==
0000 b8 21 09 de 8f 58 6e be 73 be a7 10 fb 91 87 65
0010 65 9c d7 0e 4c 88 d2 65 ae de 0b 49 40 c7 75 df
0020 19 69 53 0b 11 5d ac e7 08 f6 ae df 16 66 e0 13
0030 75 41 f7 bb be 56 a1 dd a7 3e fb 4e 5d 9e e4 a2

== plaintext ==
0000 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

== ciphertext ==
0000 5e 85 9c e8 65 d6 3b f9 03 9a a0 b5 78 bd f6 d4
0010 11 70 94 c1 c3 78 9a 1d 12 9a 84 48 3a 70 88 13
0020 d6 b5 bf c6 e8 e1 76 dc 3c b9 b0 4e b9 bb c4 74
0030 35 3d ac fc 29 e3 a0 64 d7 76 ab 76 c7 af dd 39

== iv changed ==
0000 35 3d ac fc 29 e3 a0 64 d7 76 ab 76 c7 af dd 39
0010 65 9c d7 0e 4c 88 d2 65 ae de 0b 49 40 c7 75 df
0020 19 69 53 0b 11 5d ac e7 08 f6 ae df 16 66 e0 13
0030 75 41 f7 bb be 56 a1 dd a7 3e fb 4e 5d 9e e4 a2

== checktext ==
0000 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

可见,两次运行,虽然encrypt的明码是一样的,但encrypt出来的结果是不同的。随机明码 改 静态明码 的目的就是为了比较两次encrypt的结果。即,相同的明码用不同的IV做encrypt,结果是不同的。

 

【屏蔽第98行重新编译后的执行结果】

[root@ampcommons02 aes-codec]# ./codec
== rkey ==
0000 69 ef eb 49 25 5a c2 5e 0d 77 8a cb e6 fe ad 1d
== iv ==
0000 8e 05 8c 50 2f 69 9d fb 64 3e cd e6 2d 38 26 1c
0010 6e 21 00 e6 32 3f c6 ca 93 8b c1 e3 47 9a bd 81
0020 d7 e5 0b 63 dc f8 9d 1f 13 49 35 25 70 4e 64 c2
0030 ea 0c d0 78 e7 6c 65 64 41 4d db 2b 50 4d b4 06

== plaintext ==
0000 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

== ciphertext ==
0000 a4 ed ba 4b 9f e9 74 bd 6d f6 03 76 79 9f 17 4f
0010 0c cd f3 b8 da 69 44 81 c9 f2 8b 03 83 0d 9d 77
0020 12 48 ea 46 3f eb 58 fd 48 c5 cc 2d 74 6c 99 4f
0030 93 bd 0d 06 f3 55 40 11 cb e7 d4 29 3b 8f 15 76

== iv changed ==
0000 93 bd 0d 06 f3 55 40 11 cb e7 d4 29 3b 8f 15 76
0010 6e 21 00 e6 32 3f c6 ca 93 8b c1 e3 47 9a bd 81
0020 d7 e5 0b 63 dc f8 9d 1f 13 49 35 25 70 4e 64 c2
0030 ea 0c d0 78 e7 6c 65 64 41 4d db 2b 50 4d b4 06

== checktext ==
0000 7c da e2 32 b9 5a ba 83 ce bb 7a ab 73 d1 54 03
0010 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37
0020 61 62 63 64 65 66 67 69 61 62 63 64 65 66 67 69
0030 30 31 32 33 34 35 36 37 30 31 32 33 34 35 36 37

可见,decrypt时使用和encrypt时不同的IV,不能正确decrypt出先前的明码。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

【yasi】 Openssl官方wiki, 《EVP Symmetric Encryption and Decryption》 中,有个的简单例子,整理如下(Linux下运行): (代码下载)

algo_aes.h

 
  1. #ifndef ALGO_AES_H

  2. #define ALGO_AES_H

  3.  
  4. int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,

  5. unsigned char *iv, unsigned char *ciphertext);

  6.  
  7. int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,

  8. unsigned char *iv, unsigned char *plaintext);

  9.  
  10. #endif


algo_aes.c

 
  1. #include <stdlib.h>

  2. #include <stdio.h>

  3. #include "algo_aes.h"

  4. #include <openssl/evp.h>

  5.  
  6. void handleErrors(void)

  7. {

  8. ERR_print_errors_fp(stderr);

  9. abort();

  10. }

  11.  
  12. int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,

  13. unsigned char *iv, unsigned char *ciphertext)

  14. {

  15. EVP_CIPHER_CTX *ctx;

  16.  
  17. int len;

  18.  
  19. int ciphertext_len;

  20.  
  21. /* Create and initialise the context */

  22. if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  23.  
  24. /* Initialise the encryption operation. IMPORTANT - ensure you use a key

  25. * and IV size appropriate for your cipher

  26. * In this example we are using 256 bit AES (i.e. a 256 bit key). The

  27. * IV size for *most* modes is the same as the block size. For AES this

  28. * is 128 bits */

  29. if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))

  30. handleErrors();

  31.  
  32. /* Provide the message to be encrypted, and obtain the encrypted output.

  33. * EVP_EncryptUpdate can be called multiple times if necessary

  34. */

  35. if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))

  36. handleErrors();

  37. ciphertext_len = len;

  38.  
  39. /* Finalise the encryption. Further ciphertext bytes may be written at

  40. * this stage.

  41. */

  42. if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();

  43. ciphertext_len += len;

  44.  
  45. /* Clean up */

  46. EVP_CIPHER_CTX_free(ctx);

  47.  
  48. return ciphertext_len;

  49. }

  50.  
  51. int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,

  52. unsigned char *iv, unsigned char *plaintext)

  53. {

  54. EVP_CIPHER_CTX *ctx;

  55.  
  56. int len;

  57.  
  58. int plaintext_len;

  59.  
  60. /* Create and initialise the context */

  61. if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  62.  
  63. /* Initialise the decryption operation. IMPORTANT - ensure you use a key

  64. * and IV size appropriate for your cipher

  65. * In this example we are using 256 bit AES (i.e. a 256 bit key). The

  66. * IV size for *most* modes is the same as the block size. For AES this

  67. * is 128 bits */

  68. if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv))

  69. handleErrors();

  70.  
  71. /* Provide the message to be decrypted, and obtain the plaintext output.

  72. * EVP_DecryptUpdate can be called multiple times if necessary

  73. */

  74. if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))

  75. handleErrors();

  76. plaintext_len = len;

  77.  
  78. /* Finalise the decryption. Further plaintext bytes may be written at

  79. * this stage.

  80. */

  81. if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) handleErrors();

  82. plaintext_len += len;

  83.  
  84. /* Clean up */

  85. EVP_CIPHER_CTX_free(ctx);

  86.  
  87. return plaintext_len;

  88. }


main.c

 
  1. #include "algo_aes.h"

  2. #include <stdio.h>

  3. #include <string.h>

  4. //#include <openssl/evp.h>

  5.  
  6. int main(int arc, char *argv[])

  7. {

  8. /* Set up the key and iv. Do I need to say to not hard code these in a

  9. * real application? :-)

  10. */

  11.  
  12. /* A 256 bit key */

  13. unsigned char *key = "01234567890123456789012345678901";

  14.  
  15. /* A 128 bit IV */

  16. unsigned char *iv = "01234567890123456";

  17.  
  18. /* Message to be encrypted */

  19. unsigned char *plaintext =

  20. "The quick brown fox jumps over the lazy dog1234";

  21.  
  22. /* Buffer for ciphertext. Ensure the buffer is long enough for the

  23. * ciphertext which may be longer than the plaintext, dependant on the

  24. * algorithm and mode

  25. */

  26. unsigned char ciphertext[64];

  27.  
  28. /* Buffer for the decrypted text */

  29. unsigned char decryptedtext[64];

  30.  
  31. int decryptedtext_len, ciphertext_len;

  32.  
  33. /* Initialise the library */

  34. /* ERR_load_crypto_strings();

  35. OpenSSL_add_all_algorithms();

  36. OPENSSL_config(NULL);*/

  37.  
  38. printf("Plaintext is:\n%s~\n", plaintext);

  39.  
  40. /* Encrypt the plaintext */

  41. ciphertext_len = encrypt(plaintext, strlen(plaintext), key, iv,

  42. ciphertext);

  43.  
  44. /* Do something useful with the ciphertext here */

  45. printf("Ciphertext is %d bytes long:\n", ciphertext_len);

  46. BIO_dump_fp(stdout, ciphertext, ciphertext_len);

  47.  
  48. /* Decrypt the ciphertext */

  49. decryptedtext_len = decrypt(ciphertext, ciphertext_len, key, iv,

  50. decryptedtext);

  51.  
  52. /* Add a NULL terminator. We are expecting printable text */

  53. decryptedtext[decryptedtext_len] = '\0';

  54.  
  55. /* Show the decrypted text */

  56. printf("Decrypted text is:\n");

  57. printf("%s~\n", decryptedtext);

  58.  
  59. /* Clean up */

  60. EVP_cleanup();

  61. ERR_free_strings();

  62.  
  63. return 0;

  64. }

Mekefile:

OBJ_DIR = ./obj
BIN_DIR = ./bin
SRC_DIR = ./
OBJS = \
	$(OBJ_DIR)/algo_aes.o \
	$(OBJ_DIR)/main.o
TARGET = $(BIN_DIR)/main
INC_OPT = -I./
LNK_OPT = -lssl 

$(TARGET) : clean chkobjdir chkbindir $(OBJS)
	gcc -g -o $@ $(OBJS) $(LNK_OPT)

$(OBJ_DIR)/%.o : $(SRC_DIR)/%.c
	gcc -g $(INC_OPT) -c -o $@ $<

chkobjdir :
	@if test ! -d $(OBJ_DIR) ; \
	then \
		mkdir $(OBJ_DIR) ; \
	fi

chkbindir :
	@if test ! -d $(BIN_DIR) ; \
	then \
		mkdir $(BIN_DIR) ; \
	fi

clean :
	-rm -f $(TARGET)
	-rm -rf $(OBJ_DIR)

运行结果:

Plaintext is:
The quick brown fox jumps over the lazy dog1234~
Ciphertext is 48 bytes long:
0000 - e0 6f 63 a7 11 e8 b7 aa-9f 94 40 10 7d 46 80 a1   .oc.......@.}F..
0010 - 17 99 43 80 ea 31 d2 a2-99 b9 53 02 d4 39 b9 70   ..C..1....S..9.p
0020 - e9 29 d5 a8 03 bd 71 31-b8 c3 6f 3d 39 3a 3d 3d   .)....q1..o=9:==
Decrypted text is:
The quick brown fox jumps over the lazy dog1234~

注意:参考

AES算法的块(block)的长度固定为16字节。假设一个字符串在AES加密前的长度为cleanLen,加密后的长度为cipherLen,则二者有下面的关系,其中的“/”是整除。

cipherLen = (clearLen/16 + 1) * 16

比如:(注意第二行,即使48刚好能被16整除,也要额外追加一个16字节的块)

clearLen cipherLen 
47 48
48 64
49 64

 

可见,对于AES算法:

1)加密后的长度>=加密前的长度

2)解密后的长度<=解密前的长度

这对于写代码时的指导意义在于:

1)假如我们要做AES加密的字符串为“The quick brown fox jumps over the lazy dog”,它的长度(不带/0)为43字节,则我们可以计算出加密后的串的长度为 (43 / 16 + 1) * 16 = 48 字节,于是就可以申明加密后的数组如下

unsigned char ciphertextp[48];

2)假如我们知道AES加密后的串的长度为64字节,那么就可以申明解密后的数组如下

unsigned char decryptedtext[64];

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/armlinuxww/article/details/115478717

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文