
要想学习AES,首先要清楚三个基本的概念:密钥、填充、模式。
1、密钥
密钥是AES算法实现加密和解密的根本。对称加密算法之所以对称,是因为这类算法对明文的加密和解密需要使用同一个密钥。
AES支持三种长度的密钥: 128位,192位,256位
平时大家所说的AES128,AES192,AES256,实际上就是指AES算法对不同长度密钥的使用。
三种密钥的区别:
从安全性来看,AES256安全性最高。从性能看,AES128性能最高。本质原因是它们的加密处理轮数不同。
2、填充
要想了解填充的概念,我们先要了解AES的分组加密特性。
 什么是分组加密?

AES算法在对明文加密的时候,并不是把整个明文一股脑的加密成一整段密文,而是把明文拆分成一个个独立的明文块,每一个明文块长度128bit。
这些明文块经过AES加密器复杂处理,生成一个个独立的密文块,这些密文块拼接在一起,就是最终的AES加密的结果。
但这里涉及到一个问题,假如一段明文长度是196bit,如果按每128bit一个明文块来拆分的话,第二个明文块只有64bit,不足128bit。这时候怎么办呢?就需要对明文块进行填充(Padding) 。
几种典型的填充方式:
NoPadding: 不做任何填充,但是要求明文必须是16字节的整数倍。PKCS5Padding(默认): 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。 比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则补全为{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6 }ISO10126Padding:如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数。比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则可能补全为{1,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}PKCS7Padding原理与PKCS5Padding相似,区别是PKCS5Padding的blocksize为8字节,而PKCS7Padding的blocksize可以为1到255字节
需要注意的是,如果在AES加密的时候使用了某一种填充方式,解密的时候也必须采用同样的填充方式。
3、模式
AES的工作模式,体现在把明文块加密成密文块的处理过程中。AES加密算法提供了五种不同的工作模式:CBC,ECB,CTR,CFB,OFB
 模式之间的主题思想是近似的,在处理细节上有一些差别
AES加密算法 - 加密模式
ECB模式
   优点:
   1.简单;
   2.有利于并行计算;
   3.误差不会被传送;
   缺点:
   1.不能隐藏明文的模式;
   2.可能对明文进行主动攻击;
 CBC模式:
   优点:
   1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
   缺点:
   1.不利于并行计算;
   2.误差传递;
   3.需要初始化向量IV
 CFB模式:
   优点:
   1.隐藏了明文模式;
   2.分组密码转化为流模式;
   3.可以及时加密传送小于分组的数据;
   缺点:
   1.不利于并行计算;
   2.误差传送:一个明文单元损坏影响多个单元;
   3.唯一的IV;
 ofb模式:
   优点:
   1.隐藏了明文模式;
   2.分组密码转化为流模式;
   3.可以及时加密传送小于分组的数据;
   缺点:
   1.不利于并行计算;
   2.对明文的主动攻击是可能的;
   3.误差传送:一个明文单元损坏影响多个单元;
上面四个不同模式原理图链接:分组对称加密模式:ECB/CBC/CFB/OFB缺CTR - Ady Lee - 博客园
3.1、ECB模式简介
 2)、CBC模式简介
下图中,IV一般为16字节全0,数据块长度为16字节的整数倍,则在此数据块后附加一个8字节长的数据块,
附加的数据块为:16进制的“80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00”
cbc本质上和ecb差别不大,唯一区别是将前一次加密结果,与要加密的内容异或。因此,cbc的并行性较差,因为每次都要等待前一次的结果,而ecb则不用,速度较快。其主要区别仍然看文章开头,原理图看参考链接。
3.2、AES算法ECB模式
生成加密/解密的Key
- int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
- int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
参数说明:
| 参数名称 | 描述 | 
|---|---|
| userKey | 用户指定的密码。注意:只能是16、24、32字节。如果密码字符串长度不够,可以在字符串末尾追加一些特定的字符,或者重复密码字符串,直到满足最少的长度 | 
| bits | 密码位数。即 userKey的长度 * 8,只能是128、192、256位。 | 
| key | 向外输出参数。 | 
如果函数调用成功,返回0,否则是负数。
使用函数AES_ecb_encrypt对数据进行加解密
函数原型:
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key, const int enc);
函数说明:
AES加密/解密单个数据块(16个字节),ECB模式
参数说明:
| 参数名称 | 描述 | 
|---|---|
| in | 需要加密/解密的数据 | 
| out | 计算后输出的数据 | 
| key | 密钥 | 
| enc | AES_ENCRYPT代表加密,AES_DECRYPT代表解密 | 
3.3、AES算法CBC模式
生成加密/解密的Key
- int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
- int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
参数说明:
| 参数名称 | 描述 | 
|---|---|
| userKey | 用户指定的密码。注意:只能是16、24、32字节。如果密码字符串长度不够,可以在字符串末尾追加一些特定的字符,或者重复密码字符串,直到满足最少的长度 | 
| bits | 密码位数。即 userKey的长度 * 8,只能是128、192、256位。 | 
| key | 向外输出参数。 | 
如果函数调用成功,返回0,否则是负数。
使用AES_cbc_encrypt对数据进行加解密
 - void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
-                      size_t length, const AES_KEY *key,
-                      unsigned char *ivec, const int enc);
函数说明:
AES加密/解密单个数据块(16个字节),CBC模式
参数说明:
| 参数名称 | 描述 | 
|---|---|
| in | 输入数据。长度任意。 | 
| out | 输出数据。能够容纳下输入数据,且长度必须是16字节的倍数。 | 
| length | 输出数据的实际长度。 | 
| key | 使用 AES_set_encrypt/decrypt_key生成的Key。 | 
| ivec | 可读写的一块内存。长度必须是16字节。 | 
| enc | 是否是加密操作。 AES_ENCRYPT表示加密,AES_DECRYPT表示解密。 | 
 这个函数比AES_encrypt多了一个ivec参数,ivec的内容可以任意指定,但是加密和解密操作必须使用同样的数据。在AES_cbc_encrypt底层,实际上是每16个字节做一次处理,先和ivec做异或运算,然后调用AES_encrypt函数进行加密。AES_cbc_encrypt在加密的过程中会修改ivec的内容,因此ivec参数不能是一个常量,而且不能在传递给加密函数后再立马传递给解密函数,必须重新赋值之后再传递给解密函数。
关于输出数据的长度
 输出数据缓冲区的长度必须是16字节的倍数,加密完成后,比输入长度多出来的输出数据是不可以丢弃的。因此,存档的时候,需要记录原始数据的长度
关于输入数据的长度不必是16字节的倍数(做个备忘):
 下面是AES_cbc_encrypt函数的底层实现代码
- ... 
-     //处理16字节倍数的数据
- while (len >= 16) {
-         for (n = 0; n < 16; ++n)
- out[n] = in[n] ^ iv[n];
- (*block) (out, out, key); //调用AES_encrypt处理数据
- iv = out;
- len -= 16;
-         in += 16;
- out += 16;
- }
-     //当数据小于16字节的时候,进入下面的循环
- while (len) { 
-         for (n = 0; n < 16 && n < len; ++n)
- out[n] = in[n] ^ iv[n];
-         for (; n < 16; ++n)
- out[n] = iv[n]; //使用ivec补齐不足16字节的部分
- (*block) (out, out, key); //调用AES_encrypt处理数据
- iv = out;
-         if (len <= 16)
- break;
- len -= 16;
-         in += 16;
- out += 16;
- }
ECB与CBC都是封装下面的加密解密函数实现的。
使用AES加密/解密
 - void AES_encrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);
- void AES_decrypt(const unsigned char *in, unsigned char *out, const AES_KEY *key);
参数说明:
| 参数名称 | 描述 | 
|---|---|
| in | 输入数据。必须是16字节 | 
| out | 输出数据。必须是16字节 | 
| key | 使用 AES_set_encrypt/decrypt_key生成的Key。 | 
AES_encrypt/AES_decrypt一次只处理16个字节。如果输入数据较长,你需要使用循环语句,每16个字节处理一次,直到所有数据处理完毕。如果数据不足16字节,可以用0填充至16字节。
4、加密解密后的长度
AES有几种扩展算法,其中ecb和cbc需要填充,即加密后长度可能会不一样,cfb和ofb不需要填充,密文长度和明文长度一样
5、实战代码
/usr/include/openssl/aes.h
- #ifndef HEADER_AES_H
- # define HEADER_AES_H
-  
- # include <openssl/opensslconf.h>
-  
- # ifdef OPENSSL_NO_AES
- #  error AES is disabled.
- # endif
-  
- # include <stddef.h>
-  
- # define AES_ENCRYPT     1
- # define AES_DECRYPT     0
-  
- /*
-  * Because array size can't be a const in C, the following two are macros.
-  * Both sizes are in bytes.
-  */
- # define AES_MAXNR 14
- # define AES_BLOCK_SIZE 16
-  
- #ifdef  __cplusplus
- extern "C" {
- #endif
-  
- /* This should be a hidden type, but EVP requires that the size be known */
- struct aes_key_st {
- # ifdef AES_LONG
- unsigned long rd_key[4 * (AES_MAXNR + 1)];
- # else
- unsigned int rd_key[4 * (AES_MAXNR + 1)];
- # endif
- int rounds;
- };
- typedef struct aes_key_st AES_KEY;
-  
- const char *AES_options(void);
-  
- int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
- AES_KEY *key);
- int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
- AES_KEY *key);
-  
- int private_AES_set_encrypt_key(const unsigned char *userKey, const int bits,
- AES_KEY *key);
- int private_AES_set_decrypt_key(const unsigned char *userKey, const int bits,
- AES_KEY *key);
-  
- void AES_encrypt(const unsigned char *in, unsigned char *out,
- const AES_KEY *key);
- void AES_decrypt(const unsigned char *in, unsigned char *out,
- const AES_KEY *key);
-  
- void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
- const AES_KEY *key, const int enc);
- void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
-                      size_t length, const AES_KEY *key,
- unsigned char *ivec, const int enc);
- void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
-                         size_t length, const AES_KEY *key,
- unsigned char *ivec, int *num, const int enc);
- void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
-                       size_t length, const AES_KEY *key,
- unsigned char *ivec, int *num, const int enc);
- void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
-                       size_t length, const AES_KEY *key,
- unsigned char *ivec, int *num, const int enc);
- void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
-                         size_t length, const AES_KEY *key,
- unsigned char *ivec, int *num);
- void AES_ctr128_encrypt(const unsigned char *in, unsigned char *out,
-                         size_t length, const AES_KEY *key,
- unsigned char ivec[AES_BLOCK_SIZE],
- unsigned char ecount_buf[AES_BLOCK_SIZE],
- unsigned int *num);
- /* NB: the IV is _two_ blocks long */
- void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
-                      size_t length, const AES_KEY *key,
- unsigned char *ivec, const int enc);
- /* NB: the IV is _four_ blocks long */
- void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
-                         size_t length, const AES_KEY *key,
- const AES_KEY *key2, const unsigned char *ivec,
- const int enc);
-  
- int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
- unsigned char *out,
- const unsigned char *in, unsigned int inlen);
- int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
- unsigned char *out,
- const unsigned char *in, unsigned int inlen);
-  
-  
- #ifdef  __cplusplus
- }
- #endif
-  
- #endif                          /* !HEADER_AES_H */
-  
5.1、CBC模式的实战代码
头文件:AES_CBC256.h
 - #ifndef _AES_CBC256_H_
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <openssl/aes.h>
- #include <stddef.h>
-  
- #define _AES_CBC256_H_
- #define USER_KEY_LENGTH 32
- #define IVEC_LENGTH     16
- #define AES_BLOCK_SIZE  16
- #define BITS_LENGTH   (USER_KEY_LENGTH * 8)
- class AES_CBC256 {
-  
- public:
- AES_CBC256();
- virtual ~AES_CBC256();
-      // CBC Mode Encrypt
- bool AES_CBC256_Encrypt(const unsigned char *in, unsigned char *out, size_t length);
-      // CBC Mode Decrypt
- bool AES_CBC256_Decrypt(const unsigned char *in, unsigned char *out, size_t length);
-  
- unsigned char m_userKey [USER_KEY_LENGTH];
- unsigned char m_ivec [IVEC_LENGTH];  // Default value is all 0 of 16
-  
- };
- #endif   // _AES_CBC256_H_
源文件:AES_CBC256.cpp
 - #ifndef _AES_CBC256_H_
- #   include "AES_CBC256.h"
- #endif
-  
- AES_CBC256::AES_CBC256() {
- memcpy(m_userKey, "XZJE151628AED2A6ABF7158809CF4F3C2B7E151628AED2A6ABF7158809CF4FTP", USER_KEY_LENGTH);
- memcpy(m_ivec, "XZJ2030405060708090A0B0C0D0E0FTP", IVEC_LENGTH);  // Vector initialization
-  
- }
- AES_CBC256::~AES_CBC256() {
-  
- }
-  
- bool AES_CBC256::AES_CBC256_Encrypt(const unsigned char *in, unsigned char *out, size_t length) {
-  
-     if (0 != (length % AES_BLOCK_SIZE)) {
- printf("%s\n", "the length is not multiple of AES_BLOCK_SIZE(16bytes)");
-         return false;
- }
- unsigned char ivec [IVEC_LENGTH];
- memcpy(ivec, m_ivec, IVEC_LENGTH);
- AES_KEY key;
-     // get the key with userkey
-     if (AES_set_encrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
- printf("%s\n", "get the key error");
-     	return false;
- } else {
- printf("%s\n", "get the key successful");
- }
-  
- AES_cbc_encrypt(in, out, length, &key, ivec, AES_ENCRYPT);
-     return true;
- }
-  
- bool AES_CBC256::AES_CBC256_Decrypt(const unsigned char *in, unsigned char *out, size_t length) {
-  
-     if (0 != (length % AES_BLOCK_SIZE)) {
- printf("%s\n", "the length is not multiple of AES_BLOCK_SIZE(16bytes)");
-         return false;
- }
- unsigned char ivec [IVEC_LENGTH];
- memcpy(ivec, m_ivec, IVEC_LENGTH);
- AES_KEY key;
-     // get the key with userkey
-     if (AES_set_decrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
- printf("%s\n", "get the key error");
-     	return false;
- } else {
- printf("%s\n", "get the key successful");
- }
-  
- AES_cbc_encrypt(in, out, length, &key, ivec, AES_DECRYPT);
-     return true;
- }
入口测试文件:AES_main.cpp
 - #ifndef _AES_CBC256_H_
- #   include "AES_CBC256.h"
- #endif
- #define NAME_SIZE 34
-  
- struct persion
- {
- int age;
- unsigned char name [NAME_SIZE];
- };
-  
-  
- int main(int argc, char const *argv[])
- {
- AES_CBC256 m_pcAES_CBC256;
- persion m_persion = {28,"xzj8023tp"};
-     size_t length = 0;
-     if (0 == (sizeof(persion) % AES_BLOCK_SIZE)) {
-     	length = sizeof(persion);
- } else {
- 	length = sizeof(persion) + (AES_BLOCK_SIZE - sizeof(persion) % AES_BLOCK_SIZE);
- }
- unsigned char *encrypt_in_data  = new unsigned char[sizeof(persion)];
- unsigned char *encrypt_out_data  = new unsigned char[length];
- memcpy(encrypt_in_data, &m_persion, sizeof(persion));
-     // encrypt
- bool encrypt_ret = m_pcAES_CBC256.AES_CBC256_Encrypt(encrypt_in_data, encrypt_out_data, length);
-     if (false == encrypt_ret) {
- printf("encrypt error!\n");
- } else {
- printf("encrypt successful!\n");
- }
-  
-     //decrypt
- unsigned char *decrypt_out_data  = new unsigned char[length];
- bool decrypt_ret = m_pcAES_CBC256.AES_CBC256_Decrypt(encrypt_out_data, decrypt_out_data, length);
-     if (false == decrypt_ret) {
- printf("decrypt error!\n");
- } else {
- printf("decrypt successful!\n");
- persion showData;
- memcpy(&showData, decrypt_out_data, sizeof(persion));
- printf("my name is [%s] and I am [%d] years old\n", showData.name, showData.age);
- }
-     return 0;
- }
当出现下面错误的时候
- xzj@xzj-virtual-machine:~/Code/C++Code/AES$ g++ AES_main.cpp  AES_CBC256.cpp  -o main.run
- /tmp/ccsNmQln.o:在函数‘AES_CBC256::AES_CBC256_Encrypt(unsigned char const*, unsigned char*, unsigned long)’中:
- AES_CBC256.cpp:(.text+0x131):对‘AES_set_encrypt_key’未定义的引用
- AES_CBC256.cpp:(.text+0x184):对‘AES_cbc_encrypt’未定义的引用
- /tmp/ccsNmQln.o:在函数‘AES_CBC256::AES_CBC256_Decrypt(unsigned char const*, unsigned char*, unsigned long)’中:
- AES_CBC256.cpp:(.text+0x235):对‘AES_set_decrypt_key’未定义的引用
- AES_CBC256.cpp:(.text+0x288):对‘AES_cbc_encrypt’未定义的引用
- collect2: error: ld returned 1 exit status
-  
链接到openssl库 – 将其添加到您的命令行:-lssl -lcrypto
在Linux里的编译命令:
g++ AES_main.cpp  AES_CBC256.cpp  -o main.run -lssl -lcrypto
当出现下面错误openssl/aes.h: 没有那个文件或目录
- xzj@xzj-virtual-machine:~/Code/C++Code/AES$ g++ AES_main.cpp  AES_CBC256.cpp  -o main.run
- In file included from AES_main.cpp:2:0:
- AES_CBC256.h:5:25: fatal error: openssl/aes.h: 没有那个文件或目录
- #include <openssl/aes.h>
- ^
- compilation terminated.
- In file included from AES_CBC256.cpp:2:0:
- AES_CBC256.h:5:25: fatal error: openssl/aes.h: 没有那个文件或目录
- #include <openssl/aes.h>
- ^
- compilation terminated.
-  
解决办法:
- sudo apt-get install openssl
- sudo apt-get install libssl-dev
最后执行结果:

5.2、ECB模式的实战代码
 头文件:AES_ECB256.h
 - #ifndef _AES_ECB256_H_
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <openssl/aes.h>
- #include <stddef.h>
-  
- #define _AES_ECB256_H_
- #define USER_KEY_LENGTH 32
- #define AES_BLOCK_SIZE  16
- #define BITS_LENGTH   (USER_KEY_LENGTH * 8)
- class AES_ECB256 {
-  
- public:
-      AES_ECB256();
-      virtual ~AES_ECB256();
-      // CBC Mode Encrypt
-      bool AES_ECB256_Encrypt(const unsigned char *in, unsigned char *out);
-      // CBC Mode Decrypt
-      bool AES_ECB256_Decrypt(const unsigned char *in, unsigned char *out);
-  
-      unsigned char m_userKey [USER_KEY_LENGTH];
-  
- };
- #endif   // _AES_ECB256_H_
源文件:AES_ECB256.cpp
 - #ifndef _AES_ECB256_H_
- #   include "AES_ECB256.h"
- #endif
-  
- AES_ECB256::AES_ECB256() {
- memcpy(m_userKey, "XZJE151628AED2A6ABF7158809CF4F3C2B7E151628AED2A6ABF7158809CF4FTP", USER_KEY_LENGTH);
-  
- }
- AES_ECB256::~AES_ECB256() {
-  
- }
-  
- bool AES_ECB256::AES_ECB256_Encrypt(const unsigned char *in, unsigned char *out) {
-  
- AES_KEY key;
-     // get the key with userkey
-     if (AES_set_encrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
- printf("%s\n", "get the key error");
-     	return false;
- } else {
- printf("%s\n", "get the key successful");
- }
-  
- AES_ecb_encrypt(in, out, &key, AES_ENCRYPT);
-     return true;
- }
-  
- bool AES_ECB256::AES_ECB256_Decrypt(const unsigned char *in, unsigned char *out) {
-  
- AES_KEY key;
-     // get the key with userkey
-     if (AES_set_decrypt_key(m_userKey, BITS_LENGTH, &key) < 0) {
- printf("%s\n", "get the key error");
-     	return false;
- } else {
- printf("%s\n", "get the key successful");
- }
-  
- AES_ecb_encrypt(in, out, &key, AES_DECRYPT);
-     return true;
- }
入口测试文件:ECB_main.cpp
 - #ifndef _AES_ECB256_H_
- #   include "AES_ECB256.h"
- #endif
- #define NAME_SIZE 34
-  
- struct persion
- {
-     int age;
-     unsigned char name [NAME_SIZE];
- };
-  
-  
- int main(int argc, char const *argv[])
- {
- AES_ECB256 m_pcAES_ECB256;
- persion m_persion = {28,"xzj8023tp"};
-     unsigned char *encrypt_in_data  = new unsigned char[sizeof(persion)];
-     unsigned char *encrypt_out_data  = new unsigned char[sizeof(persion)];
-     memcpy(encrypt_in_data, &m_persion, sizeof(persion));
-     // encrypt
-     bool encrypt_ret = m_pcAES_ECB256.AES_ECB256_Encrypt(encrypt_in_data, encrypt_out_data);
-     if (false == encrypt_ret) {
- 	printf("encrypt error!\n");
- } else {
- 	printf("encrypt successful!\n");
- }
-  
-     //decrypt
-     unsigned char *decrypt_out_data  = new unsigned char[sizeof(persion)];
-     bool decrypt_ret = m_pcAES_ECB256.AES_ECB256_Decrypt(encrypt_out_data, decrypt_out_data);
-     if (false == decrypt_ret) {
- 	printf("decrypt error!\n");
- } else {
- 	printf("decrypt successful!\n");
- persion showData;
- 	memcpy(&showData, decrypt_out_data, sizeof(persion));
-         printf("my name is [%s] and I am [%d] years old\n", showData.name, showData.age);
- }
-     return 0;
- }
最后执行结果:
- xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ g++ AES_ECB256.cpp  ECB_main.cpp  -o main_run -lssl -lcrypto
- xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ ls
- AES_ECB256.cpp  AES_ECB256.h  ECB_main.cpp  main_run
- xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ ./main_run 
- get the key successful
- encrypt successful!
- get the key successful
- decrypt successful!
- my name is [xzj8023tp] and I am [28] years old
- xzj@xzj-virtual-machine:~/Code/C++Code/AES/AES_ECB$ 
-  
