OpenSSL 密码软件库学习
说明:本次的密码算法采用C++编写,使用clion开发平台,Cmake编译配置工具;通过集成OpenSSL密码软件库,实现加解密功能。
1 对称加解密(AES)
1.1 AES简介:
AES(Advanced Encryption Standard)是分组密码,每组的长度相同,为128位,即16个字节。密钥长度可以使用128位,192位或256位。密钥长度不同,加密轮数也不同。
AES的处理单位是字节,128位的明文分组P和密钥K都分为16个字节,其中P=(p0,```,p15),K=(k0,```,k15)。明文分组使用以节为单位的4*4矩阵描述,即状态矩阵;状态矩阵中字节排列顺序是从上到下,从左到右;每轮加密,状态矩阵发生变化,最终输出为密文。
同样,128位密钥也使用方阵表示,矩阵的每一列称为1个32位比特字。通过密钥扩展,该密钥矩阵被排列成44个字组成的序列W=(W0,```,W43),其中W[0-3]为原始密钥,后面的密钥分成10组,每组的4个字作为轮密钥,用于10轮的加密。
图1.1 明文矩阵 图1.2密钥矩阵
1.2 AES加解密规则:
AES加密操作分为10轮,第1轮到第9轮加密的轮函数一样,包括4个操作:字节代换,行移位,列混淆和轮密钥加。第一轮开始时,先将原文和原密钥进行一次异或;最后一轮加密,不包括列混淆。AES解密过程与加密类似,每一轮的操作是加密操作的逆。
1.2.1字节代换
AES中定义了一个S盒和逆S盒,即映射表,分别对应加密和解密。字节代换操作就是查表的过程,将状态矩阵中的元素按照一定规则映射成新的一个字节。即,将原有元素(16进制数)的高4位作为行,低4位作为列,选中行列相交的元素,然后替代原来的元素。例如,原来输出的元素Si是0x7f,将其拆分为0x7,0xf;选择0x7行,0xf列,挑选出相应的元素0x68。在新的状态表中,用0x68代替0x7f。
1.2.2 行移位
行移位是对状态矩阵的每行进行相关的移位操作。加密过程,状态矩阵的第1行循环左移0位,第2行循环左移1位,第3行循环左移2位,第4行循环左移3位。解密过程,将状态矩阵的每行循环右移,位数与左移相同。
1.2.3 列混合
列混合操作是通过矩阵的乘法来实现的,即S’=PS。其中P为固定的矩阵,S为变换前的矩阵,S’为变换后的矩阵。矩阵元素的乘法和加法都是定义在基于GF(2^8)上的二元运算。 列混合的逆操作,相当于将矩阵P变为P的逆。
1.2.4 密钥轮加
将轮密钥W=(W[4i],W[4i+1],W[4i+2],W[4i+3])同状态矩阵S(S[0-3],S[4-7],s[8-11],s[12-15])进行异或操作。其中状态矩阵的每列可组成一个32位的字,与W[i]大小相同。
1.3 AES加解密的源代码(OpenSSL实现)
1 #include <cstdio> 2 #include <openssl/aes.h> 3 #include <cstdlib> 4 #include <cstring> 5 6 #define AES_KEY_SIZE 128 7 #define AES_BLOCK_SIZE 16 8 #define N 3600 9 10 using namespace std; 11 12 void aes_encrypt(unsigned char *plaintext, unsigned char *ciphertext, const unsigned char *key) { 13 AES_KEY aesKey; 14 AES_set_encrypt_key(key, AES_KEY_SIZE, &aesKey); 15 AES_encrypt(plaintext, ciphertext, &aesKey); 16 } 17 18 void aes_decrypt(unsigned char *ciphertext, unsigned char *plaintext, const unsigned char *key) { 19 AES_KEY aesKey; 20 AES_set_decrypt_key(key, AES_KEY_SIZE, &aesKey); 21 AES_decrypt(ciphertext, plaintext, &aesKey); 22 } 23 24 void inputPlain(char *&plainText, size_t &length) { 25 char str[N],ch; 26 length = 0; 27 while (true) { 28 ch = getchar(); 29 if (ch == '\n') break; 30 str[length++] = ch; 31 } 32 str[length]='\0'; 33 plainText=str; 34 } 35 36 int main() { 37 char *plainText; 38 unsigned char *cipherText, *decryptedText; 39 size_t plainTextLen, cipherTextLen; 40 const unsigned char aesKey[] = { 41 0x7b, 0xf3, 0x5c, 0xd6, 0x9c, 0x47, 0x5d, 0x5e, 42 0x6f, 0x1d, 0x7a, 0x23, 0x18, 0x7b, 0xf9, 0x34 43 }; 44 45 //输入 46 printf("请输入明文(换行结束):\n"); 47 fflush(stdout); 48 inputPlain(plainText, plainTextLen); 49 50 //分配密文空间 51 cipherTextLen = (plainTextLen + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE; 52 cipherTextLen *= AES_BLOCK_SIZE; 53 cipherText = new unsigned char[cipherTextLen]; 54 55 //对明文进行AES加密 56 aes_encrypt((unsigned char*)plainText, cipherText, aesKey); 57 *(cipherText+cipherTextLen)='\0'; 58 59 printf("加密后的密码:"); 60 for (size_t i = 0; i < cipherTextLen; i++) { 61 printf("%02x", cipherText[i]); 62 } 63 printf("\n"); 64 65 decryptedText=new unsigned char[plainTextLen]; 66 67 aes_decrypt(cipherText,decryptedText,aesKey); 68 69 printf("解密后的密码:%s\n",decryptedText); 70 71 free(cipherText); 72 free(decryptedText); 73 74 return 0; 75 }
备注:代码参考 https://blog.csdn.net/buhuidage/article/details/129192727
2 非对称加密(RSA)
RSA建立在大数分解的困难性之上。RSA 加密包括:密钥生成,加解密和签名验证。
2.1密钥生成
(1)选择两个素数p和q,保密的。
(2)计算整数n,n=p*q,公开的。
(3)选择整数e,e与θ(n)互素(θ(n)=(p-1)(q-1)),1<e<θ(n),公开的。
(4)计算d,d为e的乘法逆(modθ(n)),e*d=k*θ(n)+1
则公钥pubKey={e,n},私钥priKey={d,p,q}
2.2 RSA算法的加解密
RSA使用公钥加密,私钥解密。
加密:C=Me mod n ,其中M为明文分组,C为密文分组,M<n。
解密:M=Cd mod n = Med mod n = M(mod n)
情景:收发双方都知道n,发方知道e,收方知道d。
此时破解者Oscar,知道n,需要找到e,d,使得M=Med mod n。
2.3 RSA算法的签名模式
RSA使用私钥签名,公钥验签签名:已有消息m,私钥(d,p,q),计算签名s。 ——s = md mod n
验签:已有消息m,公钥(e,n),验证s是不是有效的签名。 ——验证:se = ? m mod n
2.4 RSA加解密源代码(OpenSSL实现)
1 #include <stdio.h> 2 #include <string> 3 #include <openssl/rsa.h> 4 #include <openssl/pem.h> 5 #include <openssl/evp.h> 6 7 using namespace std; 8 9 10 /** 11 * 生成密钥对,并保存为文件 12 * @param pubFile 13 * @param priFile 14 * @param bits 15 */ 16 void genKeyFile(const string &pubFile,const string &priFile,int bits){ 17 //生成公钥和私钥 18 RSA *rsa= RSA_generate_key(bits,RSA_F4, nullptr, nullptr); 19 BIO *bp= BIO_new(BIO_s_file()); 20 BIO_write_filename(bp,(void *) pubFile.c_str()); 21 PEM_write_bio_RSAPublicKey(bp,rsa); 22 BIO_free_all(bp); 23 24 bp=BIO_new(BIO_s_file()); 25 BIO_write_filename(bp,(void *) priFile.c_str()); 26 PEM_write_bio_RSAPrivateKey(bp,rsa,nullptr,nullptr,0,nullptr,nullptr); 27 CRYPTO_cleanup_all_ex_data(); 28 BIO_free_all(bp); 29 RSA_free(rsa); 30 } 31 32 //获取公钥和私钥 33 void genKeyPair(string &pubKey,string &priKey,int bits){ 34 RSA *rsa= RSA_generate_key(bits,RSA_F4,nullptr,nullptr); 35 BIO *kPub=BIO_new(BIO_s_mem()); 36 BIO *kPri=BIO_new(BIO_s_mem()); 37 int priKeyLen,pubKeyLen; 38 39 PEM_write_bio_RSAPrivateKey(kPri,rsa,nullptr,nullptr,0,nullptr, nullptr); 40 PEM_write_bio_RSA_PUBKEY(kPub,rsa); 41 42 priKeyLen= BIO_pending(kPri); 43 pubKeyLen= BIO_pending(kPub); 44 45 priKey.resize(priKeyLen); 46 pubKey.resize(pubKeyLen); 47 48 BIO_read(kPri,priKey.c_str(),priKeyLen); 49 BIO_read(kPub, pubKey.c_str(), pubKeyLen); 50 51 RSA_free(rsa); 52 BIO_free_all(kPri); 53 BIO_free_all(kPub); 54 } 55 56 //读取公钥 57 RSA* readPublicKey(const string &pubKey){ 58 BIO *bio= BIO_new_mem_buf(pubKey.data(),pubKey.length()); 59 RSA *rsa= PEM_read_bio_RSA_PUBKEY(bio,nullptr,nullptr,nullptr); 60 BIO_free_all(bio); 61 return rsa; 62 } 63 64 //读取私钥 65 RSA* readPrivateKey(const string &priKey){ 66 BIO *bio= BIO_new_mem_buf(priKey.data(),priKey.length()); 67 RSA *rsa= PEM_read_bio_RSAPrivateKey(bio,nullptr, nullptr, nullptr); 68 BIO_free_all(bio); 69 return rsa; 70 } 71 72 73 /** 74 * 公钥加密 75 * @param in 76 * @param out 77 * @param pubKey 78 */ 79 bool encrypt(const string &in,string &out,const string &pubKey){ 80 int keySize,inLen,readLen=0,len; 81 const unsigned char *from; 82 string to; 83 RSA *rsa= readPublicKey(pubKey); 84 if(rsa==nullptr) return false; 85 86 //分段加密 87 keySize= RSA_size(rsa); 88 inLen=in.length(); 89 from=(const unsigned char*)in.data(); 90 to.resize(keySize); 91 92 do{ 93 len=(keySize-11)<inLen? (keySize-11):inLen; 94 RSA_public_encrypt(len,(from+readLen),(unsigned char*)to.c_str(),rsa,RSA_PKCS1_PADDING); 95 inLen-=len; 96 readLen+=len; 97 out.append(to); 98 }while(inLen>0); 99 RSA_free(rsa); 100 101 return true; 102 } 103 104 105 /** 106 * 私钥解密 107 * @param in 108 * @param out 109 * @param priKey 110 */ 111 bool decrypt(const string &in,string &out,const string &priKey){ 112 unsigned char *from; 113 string to; 114 int keySize,inLen,readLen=0,len; 115 RSA *rsa= readPrivateKey(priKey); 116 if(rsa==nullptr) return false; 117 118 keySize= RSA_size(rsa); 119 from=(unsigned char*)in.data(); 120 inLen=in.length(); 121 to.resize(keySize); 122 123 do{ 124 len=RSA_private_decrypt(keySize,(from+readLen),(unsigned char*)to.c_str(),rsa,RSA_PKCS1_PADDING); 125 inLen-=keySize; 126 readLen+=keySize; 127 out.append(to.data(),len); 128 }while(inLen>0); 129 RSA_free(rsa); 130 return true; 131 } 132 133 134 /** 135 * 签名 136 * @param digest 137 * @param sign 138 * @param priKey 139 */ 140 bool signa(const string &digest,string &sign,string &priKey){ 141 string out; 142 unsigned int signLen=0; 143 RSA* rsa= readPrivateKey(priKey); 144 if(rsa==nullptr) return false; 145 out.resize(RSA_size(rsa)); 146 147 RSA_sign(NID_sha1, (const unsigned char*)digest.c_str(), digest.length(), 148 (unsigned char*)out.c_str(), &signLen, rsa); 149 150 sign.clear(); 151 sign.append(out); 152 RSA_free(rsa); 153 return true; 154 } 155 156 157 /** 158 * 验签 159 * @param digest 160 * @param sign 161 * @param pubKey 162 */ 163 bool verify(const string &digest,string &sign,string &pubKey){ 164 RSA *rsa= readPublicKey(pubKey); 165 if(rsa== nullptr) 166 return false; 167 168 int res=RSA_verify(NID_sha1,(const unsigned char*)digest.c_str(),digest.length(), 169 (const unsigned char*)sign.c_str(),sign.length(),rsa); 170 RSA_free(rsa); 171 172 if(res==1) 173 return true; 174 return false; 175 } 176 177 //准备数据 178 void prepareData(string &str,int size){ 179 for(int i=0;i<size;i++){ 180 str+=to_string((i%128)); 181 } 182 } 183 184 //哈希摘要 185 string hashDigests(string name,string data){ 186 unsigned char mdValue[EVP_MAX_MD_SIZE]={0}; 187 unsigned int mdLen=0; 188 const EVP_MD *md= nullptr; 189 string out; 190 191 OpenSSL_add_all_digests(); 192 md= EVP_get_digestbyname(name.c_str()); 193 if(!md){ 194 return NULL; 195 } 196 197 EVP_MD_CTX *mdCtx=EVP_MD_CTX_new(); 198 EVP_MD_CTX_init(mdCtx); 199 EVP_DigestInit_ex(mdCtx,md,nullptr); 200 EVP_DigestUpdate(mdCtx,data.data(),data.length()); 201 EVP_DigestFinal_ex(mdCtx,mdValue,&mdLen); 202 EVP_MD_CTX_free(mdCtx); 203 204 for(int i=0;mdValue[i]!=0;i++){ 205 out+=mdValue[i]; 206 } 207 return out; 208 } 209 210 void testRSA(string data){ 211 string priKey,pubKey; 212 string plainText=data,encryptedText,decryptedText; 213 string digest,sign; 214 int flag=1; 215 216 genKeyFile("./pub.pem","./pri.pem",1024); 217 genKeyPair(pubKey, priKey, 1024); 218 if(!encrypt(plainText,encryptedText,pubKey)){ 219 flag=0; 220 printf("RSA加密失败"); 221 } 222 if(!decrypt(encryptedText,decryptedText,priKey)){ 223 flag=0; 224 printf("RSA解密失败"); 225 } 226 if(!flag) return; 227 228 if(decryptedText==plainText){ 229 printf("加解密验证成功\n"); 230 } 231 232 digest=hashDigests("SHA256","i lost love"); 233 signa(digest,sign,priKey); 234 bool res=verify(digest,sign,pubKey); 235 if(res){ 236 printf("RSA 签名验证成功"); 237 } 238 else{ 239 printf("RSA签名验证失败"); 240 } 241 } 242 243 int main(){ 244 string str; 245 prepareData(str,1024*512+9); 246 testRSA(str); 247 248 return 0; 249 }
备注:参考 https://blog.csdn.net/zyhse/article/details/113844114?
3 哈希计算
3.1 哈希函数的定义和特点
哈希函数可将任意长度的消息压缩成固定长度的消息摘要。用于数字签名和消息鉴别码的构造。哈希函数表示为h=H(M)。
哈希函数输入M为变长的消息,输出为定长的hash值h。
单向性:从M计算h容易,从h计算M不可能。
抗碰撞性>
①抗弱碰撞:对任何给定的消息x,找到满足y!=x且H(x)=H(y)的y,在计算上不可行。
②抗强碰撞:找到任何满足H(x)=H(y)的偶对(x,y)在计算上不可行。
输入的微小变化,会引起输出的巨大变化。
3.2 MD5加密过程
MD5以512位分组来处理输入的信息,且每一分组又被划分为16个子分组。算法的输出由4个32位分组组成,将这4个分组级联后生成一个128位散列值。
(1)根据消息x构造M:M = x | 1 | 0…0 | Length。
填充方法:信息的后面填充一个1和无数个0,满足其长度len mod512=448。接着在其后附加64位,表示填充前的长度。
图3.1明文填充
(2)哈希值计算
①设置链接变量A=67452301, B=efcdab89, C=98badcfe, D=10325476
②对每个消息分组做压缩计算
图3.2 哈希计算过程
3.3 MD5摘要算法源码(OpenSSL实现)
1 #include <cstdio> 2 #include <string> 3 #include <openssl/evp.h> 4 5 using namespace std; 6 //准备数据 7 void prepareData(string &str,int size){ 8 for(int i=0;i<size;i++){ 9 str+=to_string((i%128)); 10 } 11 } 12 //摘要算法 13 string hashDigests(string name,string data){ 14 unsigned char mdValue[EVP_MAX_MD_SIZE]={0}; 15 unsigned int mdLen=0; 16 const EVP_MD *md= nullptr; 17 string out; 18 19 OpenSSL_add_all_digests(); 20 md= EVP_get_digestbyname(name.c_str()); 21 if(!md){ 22 return NULL; 23 } 24 25 EVP_MD_CTX *mdCtx=EVP_MD_CTX_new(); 26 EVP_MD_CTX_init(mdCtx); 27 EVP_DigestInit_ex(mdCtx,md,nullptr); 28 EVP_DigestUpdate(mdCtx,data.data(),data.length()); 29 EVP_DigestFinal_ex(mdCtx,mdValue,&mdLen); 30 EVP_MD_CTX_free(mdCtx); 31 32 for(int i=0;mdValue[i]!=0;i++){ 33 out+=mdValue[i]; 34 } 35 return out; 36 } 37 38 int main(){ 39 string str; 40 prepareData(str,1*1024*1024+7); 41 42 string sha256=hashDigests("SHA256",str); 43 string sha1=hashDigests("SHA1",str); 44 string md5=hashDigests("MD5",str); 45 46 char *chSha256=(char*)sha1.c_str(); 47 char *chSha1=(char*)sha256.c_str(); 48 char *chMd5=(char*)md5.c_str(); 49 50 for(int i=0;chSha1[i]!='\0';i++){ 51 printf("%x",chSha1[i]); 52 } 53 printf("\n"); 54 55 for(int i=0;chSha256[i]!='\0';i++){ 56 printf("%x",chSha256[i]); 57 } 58 printf("\n"); 59 60 for(int i=0;chMd5[i]!='\0';i++){ 61 printf("%x",chMd5[i]); 62 } 63 printf("\n"); 64 65 return 0; 66 }
备注:参考 https://blog.csdn.net/zyhse/article/details/113844114?
4 C++模拟RSA加解密
使用vs2022编写 程序模拟RSA的加解密。
(1)RSA.h
1 #pragma once 2 #include <cstdio> 3 #include <iostream> 4 #include <stdlib.h> 5 #include <cstring> 6 #define N 1024 7 8 class RSA { 9 int p, q, len; 10 long n, r, e, d; 11 long long* pText, * cText, * dText; 12 public: 13 void init(); 14 RSA(); 15 bool isPrime(long); 16 long gcd(long, long); 17 void inputPQ(); 18 void genKeyPair(); 19 long quickPower(long long, long, long); 20 char* encrypt(char*); 21 char* decrypt(); 22 };
(2)RSA.cpp
1. #include "RSA.h" 2. 3. using namespace std; 4. string res = "\0"; 5. char ds[N] = { 0 }; 6. 7. void mLtoa(long long num, char* str, int radix) 8. { 9. int i = 0; 10. int j = 0; 11. long long sum; 12. unsigned long long num1 = num; //如果是负数求补码,必须将他的绝对值放在无符号位中在进行求反码 13. char str1[33] = { 0 }; 14. if (num < 0) { //求出负数的补码 15. num = -num; 16. num1 = ~num; 17. num1 += 1; 18. } 19. if (num == 0) { 20. str1[i] = '0'; 21. 22. i++; 23. } 24. while (num1 != 0) { //进行进制运算 25. sum = num1 % radix; 26. str1[i] = (sum > 9) ? (char)((sum - 10) + 'a') : (char)(sum + '0'); 27. num1 = num1 / radix; 28. i++; 29. } 30. i--; 31. 32. for (i; i >= 0; i--) { //逆序输出 33. str[i] = str1[j]; 34. j++; 35. } 36. 37. } 38. 39. void RSA::init() { 40. this->p = 0; 41. this->q = 0; 42. this->n = 0; 43. this->d = 0; 44. this->e = 0; 45. this->r = 0; 46. this->len = 0; 47. cText = new long long[N]; 48. pText = new long long[N]; 49. dText = new long long[N]; 50. } 51. 52. RSA::RSA() { 53. init(); 54. } 55. 56. bool RSA::isPrime(long t) { 57. if (t == 1) return false; 58. for (int i = 2; i <= t / i; i++) { 59. if (t % i == 0) { 60. return false; 61. } 62. } 63. return true; 64. } 65. 66. long RSA::gcd(long a, long b) { 67. return b == 0 ? a : gcd(b, a % b); 68. } 69. 70. void RSA::inputPQ() { 71. int p, q; 72. while (1) { 73. printf("请输入两个大于200的素数(p,q):"); 74. cin >> p >> q; 75. 76. if (p <= 200 || q <= 200) { 77. printf("输入的素数太小,请重新输入\n"); 78. continue; 79. } 80. if (!isPrime(p)) { 81. printf("p不是素数,请重新输入!\n"); 82. continue; 83. } 84. if (!isPrime(q)) { 85. printf("q不是素数,请重新输入!\n"); 86. continue; 87. } 88. 89. this->p = p; 90. this->q = q; 91. this->n = p * q; 92. this->r = (p - 1) * (q - 1); 93. break; 94. } 95. } 96. 97. void RSA::genKeyPair() { 98. long value = 1; 99. inputPQ(); 100. 101. for (int i = 2; i < r; i++) { 102. if (gcd(r, i) == 1) { 103. this->e = i; 104. break; 105. } 106. } 107. 108. for (long j = 1;; j++) { 109. value = j * this->r + 1; 110. if (value % this->e == 0 && value / this->e < this->r) { 111. this->d = value / this->e; 112. break; 113. } 114. } 115. 116. printf("设定的公钥为(%ld,%ld)\n", this->e, this->n); 117. printf("生成的私钥为(%ld,%d,%d)\n", this->d, this->p, this->q); 118. } 119. 120. long RSA::quickPower(long long a, long b, long n) { 121. long res = 1; 122. while (b > 0) { 123. if ((b & 1) != 0) 124. res = (res * a) % n; 125. b = b >> 1; 126. a = (a * a) % n; 127. } 128. return res; 129. } 130. 131. char* RSA::encrypt(char* str) { 132. unsigned char ch; 133. char* es, cipher[N]; 134. 135. for (int i = 0; str[i] != 0; i++) { 136. if (i > N) { 137. realloc(cText, N * ((i / 1024) + 1) * sizeof(int64_t)); 138. realloc(pText, N * ((i / 1024) + 1) * sizeof(int64_t)); 139. realloc(dText, N * ((i / 1024) + 1) * sizeof(int64_t)); 140. } 141. 142. ch = (unsigned char)str[i]; 143. pText[i] = ch; 144. cText[i] = quickPower(pText[i], this->e, this->n); 145. memset(cipher, 0, sizeof(cipher)); 146. mLtoa(cText[i], cipher, 16); 147. res.append(cipher); 148. this->len++; 149. } 150. es = (char*)res.c_str(); 151. 152. return es; 153. } 154. 155. char* RSA::decrypt() { 156. for (int i = 0; i < len; i++) { 157. dText[i] = quickPower(cText[i], this->d, this->n); 158. ds[i] = (char)dText[i]; 159. } 160. return ds; 161. }
(3)demo.cpp
1. #include "RSA.h" 2. 3. using namespace std; 4. char* cipherText, * decryptedText; 5. char plainText[N], ch; 6. int len = 0; 7. RSA rsa; 8. 9. int main() { 10. 11. memset(plainText, 0, sizeof(plainText)); 12. 13. printf("请输入待加密的明文(以换行结束):\n"); 14. while (true) { 15. ch = getchar(); 16. if (ch == '\n') break; 17. plainText[len++] = ch; 18. } 19. fflush(stdout); 20. 21. rsa.genKeyPair(); 22. 23. cipherText = rsa.encrypt(plainText); 24. decryptedText = rsa.decrypt(); 25. 26. printf("加密密文:"); 27. printf("%s\n", cipherText); 28. 29. printf("解密原文:"); 30. printf("%s\n", decryptedText); 31. 32. return 0; 33. }