原理见:
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
Openssl库函数:http://blog.chinaunix.net/uid-23069658-id-4282969.html
今天心血来潮突然想搞搞openssl了,趁着端午小假,刚好有空可以鼓捣孤岛自己喜欢的东西,出去东奔西跑的实在太造孽了,还是宅起来给自己充充电吧。下载openssl最新代码1.0.1g,修复了“心血漏洞”那个版本。编译安装那些小儿科的东西就不再浪费笔墨了,如果出现头文件或者库文件之类的错误,请在本人博客里寻找相关文章,应该主要集中在动态库那几篇博文。反正我在自己虚拟机里安装的时候是妥妥滴。
因为我主要对非对称加密的RSA算法比较感兴趣,网上最多的就是这么用的:
生成私钥文件(其中已经包含了公钥):
|
然后再从这个私钥文件里将公钥提取出来,保存到文件里:
|
RSA一般有两种应用场景:
1、公钥加密、私钥解密:这是数据安全通信领域最常见情形;
2、私钥加密、公钥解密:这主要用于数字签名。
两种方式,一通百通,本文只看第一种场景。
关于测试代码,网上到处都是,也都基本能用,我就先不摘抄了。大家问的最多的问题就是在读取公钥文件时,PEM_read_RSA_PUBKEY()函数和PEM_read_RSAPublicKEY()的疑惑。为什么读取私钥文件用的PEM_read_RSAPrivateKey(),针对上述openssl命令生成的公钥文件,在读取其内容时用对称的PEM_read_RSAPublicKEY()接口却会报错,必须要用PEM_read_RSA_PUBKEY()才可以。
其实,我们要是看看一两个文件内容就明白了:
|
当我们在用PEM_read_RSAPublicKEY()读取公钥文件plainPub.key时报的错误是酱紫滴:
|
所以我就天真地将公钥文件头和尾分别改成“-----BEGIN RSA PUBLIC KEY-----”和“-----BEGIN RSA PUBLIC KEY-----”,理想很丰满,显示很骨感。实践证明openssl是不能那么轻易就被忽悠过去的。没办法,查看openssl源码发现,提取公钥文件时除了-pubout参数可以设置外,还有有个参数叫做-RSAPublicKey_out,但是命令行提示和man手册里居然没有任何提及。幸好我还会读C代码,所以提取公钥时我改用下面的命令:
|
这样做完的结果是,首先公钥文件的内容有点变化:
|
其次,当我再用PEM_read_RSAPublicKEY()接口来读取公钥文件plainPub2.key时,居然成功了。说明RSA PUBLIC KEY和PUBLIC KEY的两种公钥文件其存储方式是不一样的,PEM_read_RSAPublicKEY()只能读取RSA PUBLIC KEY形式的公钥文件;而PEM_read_RSA_PUBKEY()只能读取PUBLIC KEY格式的公钥文件。由于本人密码学基础较薄弱,现在还不能说出两者的区别,请各位见谅,还望密码方面的大牛们予以点拨。演示代码如下:
点击(此处)折叠或打开
1. /* filename: tmp.c
2. */
3. #include<stdio.h>
4. #include<stdlib.h>
5. #include<string.h>
6. #include<openssl/rsa.h>
7. #include<openssl/pem.h>
8. #include<openssl/err.h>
9.
10. void hexprint(char *str,int len)
11. {
12. int i=0;
13. for(i=0;i<len;i++){
14. printf("%s%02x%s",((i%16==0?"|":"")),*((unsigned char*)str+i),(((i+1)%16==0)?"|\n":" "));
15. }
16. if(i%16!=0)
17. printf("|\n");
18. }
19.
20. static int do_operation(RSA* rsa_ctx,char *instr,char* path_key,int inlen,char** outstr,int type)
21. {
22. if(rsa_ctx == NULL || instr == NULL || path_key == NULL)
23. {
24. perror("input elems error,please check them!");
25. return -1;
26. }
27. int rsa_len,num;
28. rsa_len=RSA_size(rsa_ctx);
29. *outstr=(unsigned char *)malloc(rsa_len+1);
30. memset(*outstr,0,rsa_len+1);
31. switch(type){
32. case 1: //pub enc
33. if(inlen == 0){
34. perror("input str len is zero!");
35. goto err;
36. }
37. num = RSA_public_encrypt(inlen,(unsigned char *)instr,(unsigned char*)*outstr,rsa_ctx,RSA_PKCS1_OAEP_PADDING);
38. break;
39. case 2: //prv dec
40. num = RSA_private_decrypt(inlen,(unsigned char *)instr,(unsigned char*)*outstr,rsa_ctx,RSA_PKCS1_OAEP_PADDING);
41. default:
42. break;
43. }
44.
45. if(num == -1)
46. {
47. printf("Got error on enc/dec!\n");
48. err:
49. free(*outstr);
50. *outstr = NULL;
51. num = -1;
52. }
53. return num;
54. }
55.
56. int rsa_pub_encrypt(char *str,char *path_key,char** outstr){
57. RSA *p_rsa;
58. FILE *file;
59. int flen,rsa_len,num;
60. if((file=fopen(path_key,"r"))==NULL){
61. perror("open key file error");
62. return -1;
63. }
64. #ifdef RSAPUBKEY
65. if((p_rsa=PEM_read_RSA_PUBKEY(file,NULL,NULL,NULL))==NULL){
66. #else
67. if((p_rsa=PEM_read_RSAPublicKey(file,NULL,NULL,NULL))==NULL){
68. #endif
69. ERR_print_errors_fp(stdout);
70. return -1;
71. }
72.
73. num = do_operation(p_rsa,str,path_key,strlen(str),outstr,1);
74. RSA_free(p_rsa);
75. fclose(file);
76. return num;
77. }
78.
79. int rsa_prv_decrypt(char *str,char *path_key,int inlen,char** outstr){
80. RSA *p_rsa;
81. FILE *file;
82. int rsa_len,num;
83.
84. if((file=fopen(path_key,"r"))==NULL){
85. perror("open key file error");
86. return -1;
87. }
88. if((p_rsa=PEM_read_RSAPrivateKey(file,NULL,NULL,NULL))==NULL){
89. ERR_print_errors_fp(stdout);
90. return -1;
91. }
92. num = do_operation(p_rsa,str,path_key,inlen,outstr,2);
93. RSA_free(p_rsa);
94. fclose(file);
95. return num;
96. }
97.
98. int main(int argc,char** argv){
99. char *ptr_en,*ptr_de;
100. int len;
101.
102. printf("source is :%s\n",argv[1]);
103. len=rsa_pub_encrypt(argv[1],argv[2],&ptr_en);
104. printf("pubkey encrypt:\n");
105. hexprint(ptr_en,len);
106.
107. rsa_prv_decrypt(ptr_en,argv[3],len,&ptr_de);
108. printf("prvkey decrypt:%s\n",ptr_de==NULL?"NULL":ptr_de);
109.
110. if(ptr_en!=NULL){
111. free(ptr_en);
112. }
113. if(ptr_de!=NULL){
114. free(ptr_de);
115. }
116.
117. return 0;
118. }
如果开启RSAPUBKEY宏,则用PEM_read_RSA_PUBKEY()来读取公钥文件;否则用PEM_read_RSAPublicKey()读取:
|
测试结果如下:
实际应用中,出于安全考虑我们一般会对私钥文件加密。我们可以用如下的方式来重新生成经3DES加密后私钥文件:
|
这样生成的私钥文件使用3DES加过密的,看看内容就晓得和之前的有什么不同了。头部多了一些信息:Proc-Type和DEK-Info,猜想这肯定是某种加密信息(这TM不废话么),但是我看不懂,现阶段“会用”是首要问题:
上述加密私钥文件的口令是123456,分别提取RSA PUBLIC KEY和PUBLIC KEY格式的公钥文件:
|
在代码中我们需要通过下面的方式来读取经3DES加密处理后的私钥文件:
点击(此处)折叠或打开
1. RSA* getPRV(char *path_key_fullname,char* pwd)
2. {
3. RSA *rsaK=RSA_new();
4. OpenSSL_add_all_algorithms();
5. BIO *BP = BIO_new_file(path_key_fullname,"rb");
6. if(NULL == BP)
7. return NULL;
8.
9. rsaK=PEM_read_bio_RSAPrivateKey(BP,NULL,NULL,pwd);
10. return rsaK;
11. }
然后将tmp.c中第85行从:
if((p_rsa=PEM_read_RSAPrivateKey(file,NULL,NULL,NULL))==NULL){
替换成:
if((p_rsa=getPRV(path_key,"123456"))==NULL){
重新编译,运行后结果如下:
关于openssl有很多值得学习的地方,空了再慢慢研究。