当前位置:   article > 正文

CBC和CTR模式下的AES

c# aes ctr模式 实现

实验内容:

在本次实验中,需要实现两个加密/解密系统,一个在密文分组链接模式(CBC)下使用AES,另一个在计数器模式(CTR)中使用AES。

实验环境:

VS2019、C++、 Crypto++

实验过程:

1、安装Crypto++

1.1官网下载Crypto++

官网地址:https://www.cryptopp.com/

1.2解压编译,生成.lib文件

解压后,用vs打开里面的.sln工程文件,会得到四个工程。

将cryptlib项目设为启动项,选中cryptlib,选择Debug x64模式,按下ctrl + B生成cryptlib。

1.3配置工程环境

新建工程,右键工程选择属性,选择VC++目录,设置包含目录和库目录。库目录就是头文件所在目录,库目录就是刚生成的.lib所在目录。
选择链接器->输入->附加依赖项,输入刚才的生成的.lib文件完整名字。

选择c/c++ ->代码生成 -> 运行库,选择多线程调试(/MTd)

2、CBC模式下的AES原理

EBC和CBC模式都是分块加密,经常要对plaintext进行填充,使之满足16字节的整数倍。一般EBC模式下,如果采用相同的内容和相同的秘钥,结果密文是相同的,这样是不安全的。CBC引入向量IV的概念,加密过程除了提供key和plaintext还需要提供IV,这个IV大小为16个字节。密文中前16字节,就是IV。IV会参与第一块的加密,之后就使用上一块加密的结果代替IV。这样做的好处就是使得相同的内容相同的秘钥,加密的结果可能不同。

加密过程:

先将plaintext填充为16字节的整数倍,然后将plaintext等分为n份。第一次加密时,将IV与P1进行异或操作得到结果,然后将这个结果进行AES加密,得到C1。将C1链接到密文中去,并将C1代替IV,参与下一次的异或操作。然后重复上述操作,直到所有的block都进行加密。

解密过程:

选读取密文的前16个字节,这16个字节就是IV。然后将去除IV的plaintext等分为n份。
先取出C1(第一块密文),将C1通过AES解密得到结果T1, 然后将T1与IV异或的得到第一块明文。再用C1替换IV参与下一次解密运算,重复上述操作。

单独处理最后一块,这里最后一块采用的PKCS7的填充方式,这种方式填充最后几个字节表示填充了多少个字节,获取最后一个字节的数字,只需要在明文结果后面删除这个长度的字节就得到真正的明文。

3、CBC模式下AES加密解密实现

3.1 CBC_AES解密代码

void CBCdecrypto(const string& key,const string& ciphertext, string& plaintext)
{
string vi = ciphertext.substr(0, AES::BLOCKSIZE);//AES::BLOCKSIZE =16
string text = ciphertext.substr(AES::BLOCKSIZE, ciphertext.size() - AES::BLOCKSIZE);//前16为vi后16为填充

  1. size_t groupNumber = text.size() / AES::BLOCKSIZE;
  2. AESDecryption cryptor;
  3. cryptor.SetKey((byte*)key.c_str(),key.size());
  4. for (size_t i = 0 ; i<groupNumber;i++)
  5. {
  6. //获取每一次的分组密文
  7. string block = text.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
  8. byte temp[AES::BLOCKSIZE];
  9. memset(temp, 0x30, AES::BLOCKSIZE);
  10. cryptor.ProcessBlock((byte*)block.c_str(), temp);
  11. for (int j = 0; j < AES::BLOCKSIZE; j++) {
  12. plaintext.push_back((byte)vi[j] ^ temp[j]);
  13. }
  14. vi = block;
  15. }
  16. string paddingBlock = plaintext.substr((groupNumber - 1) * AES::BLOCKSIZE, AES::BLOCKSIZE);
  17. int paddingNum = (byte)paddingBlock[AES::BLOCKSIZE - 1];
  18. for (int i = 0; i < paddingNum; i++) {
  19. if (plaintext.back() != paddingNum) {
  20. cout << "密文出错" << endl;
  21. exit(0);
  22. }
  23. plaintext.pop_back();
  24. }

}

3.2 CBC_AES加密代码

string CBCencrypto(string hexKey, string hexVI, string plaintext) {
string key;
hex_to_str(hexKey, key);

  1. string VI;
  2. hex_to_str(hexVI, VI);
  3. string outstr;
  4. outstr.clear();
  5. outstr += VI;
  6. //填充plaintext 使之成为16字节的整数倍
  7. int paddingNum = AES::BLOCKSIZE - (plaintext.size() % AES::BLOCKSIZE);
  8. for (int i = 0; i < paddingNum; i++) {
  9. if (i == paddingNum - 1)
  10. {
  11. plaintext += (char)paddingNum;//最后一个byte表示填充多少个字节
  12. }
  13. else {
  14. plaintext += (char)(15);//其他填充为0x0F
  15. }
  16. }
  17. //获取多少个组
  18. int groupNumber = plaintext.size() / AES::BLOCKSIZE;
  19. AESEncryption encryptor((byte*)key.c_str(), AES::MIN_KEYLENGTH);
  20. for (int i = 0; i < groupNumber;i++)
  21. {
  22. string Pi = plaintext.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
  23. //想让IV 与 Pi异或
  24. byte temp[AES::BLOCKSIZE];
  25. memset(temp, 0x30, AES::BLOCKSIZE);
  26. for (int j = 0; j < AES::BLOCKSIZE; j++)
  27. {
  28. temp[j] = (byte)Pi[j] ^ VI[j];
  29. }
  30. //然后将temp进行aes
  31. byte tempr[AES::BLOCKSIZE];
  32. encryptor.ProcessBlock(temp, tempr);
  33. //将tempr添加到outstr上
  34. string PiResult;
  35. PiResult.clear();
  36. for (int j = 0; j < AES::BLOCKSIZE;j++) {
  37. PiResult += tempr[j];
  38. }
  39. outstr += PiResult;
  40. //更新IV,参与下次运算
  41. VI = PiResult;
  42. }
  43. //还需要将outstr 转成十六进制的情况
  44. string hexOutstr;
  45. hexOutstr.clear();
  46. for (int i = 0; i < outstr.size(); i++)
  47. {
  48. string stemp;
  49. char2hexs(outstr[i], stemp);
  50. hexOutstr += stemp;
  51. }
  52. return hexOutstr;

}

4、CTR模式下的AES原理

CTR有一个计数器counter,一般为16字节,前后两次的加密与加密结果无关。每次加密counter加一,所以加密速度更快,但是安全性比CBC模式稍低点。而且CTR加密不需要填充,类似流模式。密文的前16个字节为counter。

加密过程:

先选取counter,如果没有16字节就填充,现将counter通过AES进行加密,得到结果T1,
然后将T1与明文的第一分组进行异或得到结果C1,将C1链接到密文上,然后将counter+1进行下一轮加密。

对最后一个非整块的明文单独处理,处理方法与上面类似,只是长度按照剩余块的长度处理。

解密过程:

选读取密文中前16个字节作为counter,然后将去除counter的密文按照16个字节等分,最后一个非整16字节的单独处理。

现将counter进行AES解密得到Ti然后,将Ti与密文块Ci进行异或得到明文Pi,将Pi链接到输出明文上,counter+1进行下一轮解密。
对后面非整16的块单独处理,处理方法类似。

5、CTR模式下AES加密解密实现

5.1 CTR_AES 解密代码

void CTRdecrypto(const string& key, const string& ciphertext, string& plaintext)
{

  1. // 密文的前 16 个字节为计数器的初始值
  2. string counter = ciphertext.substr(0, AES::BLOCKSIZE);
  3. string text = ciphertext.substr(AES::BLOCKSIZE, ciphertext.length() - AES::BLOCKSIZE);
  4. int groupNumber = text.length() / AES::BLOCKSIZE;
  5. AESEncryption aesEncryptor;
  6. aesEncryptor.SetKey((byte*)key.c_str(), key.length());
  7. byte aesResult[AES::BLOCKSIZE];
  8. for (int i = 0; i <groupNumber; i++) {
  9. string ciphertextBlock = text.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
  10. memset(aesResult, 0x30, AES::BLOCKSIZE);
  11. aesEncryptor.ProcessBlock((byte*)counter.c_str(), aesResult);
  12. // 密文和 AES 加密结果异或,得到明文
  13. for (int j = 0; j < AES::BLOCKSIZE; j++) {
  14. plaintext.push_back(aesResult[j] ^ (byte)ciphertextBlock[j]);
  15. }
  16. // 计数器自增
  17. counter = counterIncrement(counter, 1);
  18. }
  19. int residueLen = text.length() - groupNumber * AES::BLOCKSIZE;
  20. string residueCiphertext = text.substr(groupNumber * AES::BLOCKSIZE, residueLen);
  21. memset(aesResult, 0, AES::BLOCKSIZE);
  22. aesEncryptor.ProcessBlock((byte*)counter.c_str(), aesResult);
  23. for (int j = 0; j < residueLen; j++) {
  24. plaintext.push_back(aesResult[j] ^ (byte)residueCiphertext[j]);
  25. }

}

5.2 CTR_AES加密代码

string CTRencrypto(string hexKey, string counter, string plaintext)
{
string key;
hex_to_str(hexKey, key);

  1. string outstr;
  2. outstr.clear();
  3. outstr += counter;
  4. //CTR获取多少个整数 的16bytes
  5. int num = plaintext.size() / AES::BLOCKSIZE;
  6. AESEncryption encryptor((byte*)key.c_str(), AES::MIN_KEYLENGTH);
  7. byte temp[AES::BLOCKSIZE];
  8. for (int i = 0; i < num; i++) {
  9. memset(temp, 0x30, AES::BLOCKSIZE);
  10. encryptor.ProcessBlock((byte*)counter.c_str(), temp);
  11. string block = plaintext.substr(i * AES::BLOCKSIZE, AES::BLOCKSIZE);
  12. for (int j = 0; j < AES::BLOCKSIZE; j++) {
  13. outstr.push_back(temp[j]^block[j]);
  14. }
  15. counter = counterIncrement(counter, 1);
  16. }
  17. /*
  18. *处理最后一个非整块的block
  19. */
  20. int len = plaintext.size() - (num * AES::BLOCKSIZE);
  21. string lastBlock = plaintext.substr(num * AES::BLOCKSIZE - 1, len);
  22. memset(temp, 0x30, AES::BLOCKSIZE);
  23. encryptor.ProcessBlock((byte*)counter.c_str(), temp);
  24. for (int i = 0; i < len; i++) {
  25. outstr.push_back(lastBlock[i] ^ temp[i]);
  26. }
  27. /*
  28. *将输出转换正十六进制
  29. */
  30. string hexOutstr;
  31. hexOutstr.clear();
  32. for (int i = 0; i < outstr.size(); i++)
  33. {
  34. string stemp;
  35. char2hexs(outstr[i], stemp);
  36. hexOutstr += stemp;
  37. }
  38. return hexOutstr;

}

6、实验结果

对老师给出的test.txt的解密结果如下:

附:

1、其他代码解释

Class Exercise_3
hexKeys 和hexCiphertexts为十六进制的秘钥和密文组。
keys和ciphertexts为转换成byte数组的秘钥和密文组。
plaintexts 是解密后的原文组。
key_path和cipher_path分别为秘钥和密文的存放路径。
modeVec是存放加载的秘钥密文需要解码的方式,这里有定义

方法解释:
bool decrypto() 统一对读取的秘钥密文处理,得到所有的明文。
void printPlaintexts() 打印所有的结果信息。
bool init() 主要实现加载秘钥和密文并转换数据格式。
bool load_keys()加载秘钥。
bool load_ciphers() 加载密文。
void changeDataFormal() 改变秘钥密文格式。
其他顶层函数
void hex_to_str(const string& stringData, string& str)将十六进制字符串转成byte字符串。
void char2hexs(char ch, string& s) 将一个char类型转成字符串类型。
string counterIncrement(string counter, int n) counter的自增操作。
string CBC_AESEncryptStr(string sKey, string sIV, const char* plainText) CBC模式的调库实现。

2、文件目录格式

keys.txt与ciphertexts.txt中数据以空格分隔。

完整代码地址

  • 上述代码有个错误的地方,单独的 byte表示范围是在-128 到127 ,不能表示我们要的范围0 - 255 ,应该换成 unsigned char

最后一个问题,在由字符串转十六进制的那里有错

对于一个字符转十六进制 直接用 int temp = (int)ch这种方式转会有正有负
这时候可以将它与oxff 相与,就为正了。
还有就是用 stringstream这种方式转,比如0x03,他会转成0x3.所以这个时候可以先设置长度为二 比如 ss<<hex<<setfill('0'); ss<<setw(2),也可以像我这样实现,效果是一样的

  1. void char2hexs(char ch, string& s)
  2. {
  3. s.clear();
  4. stringstream ss;
  5. ss.clear();
  6. int temp = (int)ch;
  7. temp = temp & 0xff;
  8. ss << hex << temp;
  9. s = ss.str();
  10. if (s.size() == 1) {
  11. s = '0' + s;
  12. }
  13. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/176620
推荐阅读
相关标签
  

闽ICP备14008679号