openssl原始碼方式安裝以及簡單的實驗
一、引言
這仍然是資訊保安課程的一次作業,老師的要求包括以原始碼方式來安裝openssl,瞭解AES和RSA加密演算法並去嘗試呼叫openssl庫中AES和RSA演算法的API,其實總體上安裝以及實驗還是比較容易的,不過還是踩了一些坑,這裡還是記錄一下,順便試試CSDN的Markdown編輯器。
本文演示使用的作業系統為Ubuntu 16.04
二、安裝
1.下載原始碼包
從官網上可以找到原始碼包的下載,這裡我直接給出下載連結:
openssl原始碼包下載
根據官網的描述,1.1.1版本將是目前他們長期支援的版本,一直到2023年,建議使用1.0.x版本的使用者也去安裝這個最新版本。
2.編譯安裝
解壓下載的原始碼包,可以看到如下的目錄結構
可以點開INSTALL檔案來檢視安裝說明,這裡我們按照它說的最簡說明方式來進行安裝:
on Unix (again, this includes Mac OS/X):
$ ./config
$ make
$ make test
$ sudo make install
其中最後一條命令如果不是root使用者執行,需要加上sudo,否則無法訪問系統根目錄的一些資料夾,至少我第一次沒加sudo執行時報了錯。
這四條命令執行完之後都沒有報錯的話,說明它的安裝指令碼中的所有任務都完成了,這時可以使用
openssl version
命令來檢視當前openssl的版本,不過我在這時遇到了問題,報錯是這樣的:
openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
通過查詢資料,這個問題也很容易就解決了,原因大概是因為libssl.so.1.1被安裝指令碼放置到了/usr/local/的lib下,而命令列呼叫的時候找的是/usr/的lib下的libssl.so.1.1,所以我們只需要用如下兩條命令在/usr/的lib下建立連結檔案即可:
ln -s /usr/local/lib/libssl.so.1.1 /usr/lib/libssl.so.1.1 ln -s /usr/local/lib/libcrypto.so.1.1 /usr/lib/libcrypto.so.1.1
值得一提的是,我百度到的命令的lib資料夾命名為lib64,這裡一定要結合自己的情況去檢視,不要直接複製命令去執行。
在解決這個問題之後,再執行之前的openssl version命令就會出現如下的顯示,也說明安裝成功了:
OpenSSL 1.1.1 11 Sep 2018
三、執行
1.MD5演算法的實驗
首先來實驗一下最簡單,也是最常用的MD5摘要演算法,說它簡單是因為它的API只有三個,用來試執行再好不過了,這裡我也是參照了一位博主的一個例子來實驗的,更詳細的講解可以看這篇部落格:
Linux下C語言使用openssl庫進行加密
用到的程式碼也是直接搬過來使用的,這裡貼一下吧:
#include <openssl/md5.h>
#include <stdio.h>
#include <string.h>
int main()
{
MD5_CTX ctx;
unsigned char outmd[16];
int i=0;
memset(outmd,0,sizeof(outmd));
MD5_Init(&ctx);
MD5_Update(&ctx,"hel",3);
MD5_Update(&ctx,"lo\n",3);
MD5_Final(outmd,&ctx);
for(int i=0;i<16;i++)
{
printf("%02X",outmd[i]);
}
printf("\n");
return 0;
}
這裡是我遇到的另外一個坑,如果用gcc編譯的話,會出現如下的情況:
$ gcc md5test.c
/tmp/ccP3aNqM.o:在函式‘main’中:
md5test.c:(.text+0x42):對‘MD5_Init’未定義的引用
md5test.c:(.text+0x58):對‘MD5_Update’未定義的引用
md5test.c:(.text+0x6e):對‘MD5_Update’未定義的引用
md5test.c:(.text+0x81):對‘MD5_Final’未定義的引用
collect2: error: ld returned 1 exit status
通過百度,這個問題也很好解決,只需要引用連結庫crypto即可,如下所示:
$ gcc md5test.c -lcrypto && ./a.out
B1946AC92492D2347C6235B4D2611184
2. AES對稱加密演算法API呼叫實驗
使用API之前,最好先簡單瞭解一下AES演算法的原理(雖然呼叫API的話並不需要搞清楚演算法實現),這裡我貼出一個博主的部落格,感覺講的還是挺好的
AES加密演算法的詳細介紹與實現
而實驗部分的API我也參考了另一位博主的部落格:
OPENSSL庫的使用-AES篇
#include <openssl/aes.h>
#include <stdio.h>
int main()
{
AES_KEY key; //新建一個AES_KEY
unsigned char userkey[]="test"; //金鑰字串
unsigned char in[]="this is data"; //要加密的資訊
unsigned char out[13]; //密文
int res=AES_set_encrypt_key(userkey,128,&key); //設定加密祕鑰
AES_ecb_encrypt(in,out,&key,AES_ENCRYPT); //設定解密祕鑰
puts(out); //列印密文
res=AES_set_decrypt_key(userkey,128,&key); //設定解密祕鑰
unsigned char out2[13]; //儲存解密後的字串
AES_ecb_encrypt(out,out2,&key,AES_DECRYPT); //解密
puts(out2); //列印解密後的資訊
return 0;
}
這裡的執行結果太鬼畜了,我貼一下圖吧:
3.RSA演算法API呼叫實驗
RSA演算法還是感覺挺神奇的,這裡也是貼出一篇學習的時候參考的部落格:
對稱加密與非對稱加密,以及RSA的原理
博主文尾舉的例子好像有錯誤,我也在評論中寫出來了,不過這篇文章依然極具參考價值。
實現部分,我參考了兩位博主的程式碼:
OPENSSL庫的使用-RSA篇
如何利用OpenSSL庫進行RSA加密和解密
程式碼如下:
#include <openssl/rsa.h>
#include <stdio.h>
#include <string.h>
RSA * get_rsa(long e,int bit)
{
RSA * rsa=RSA_new(); //新建RSA結構體指標
if(rsa==NULL)
return NULL;
BIGNUM *eNum=BN_new(); //新建一個大數結構體物件
if(!BN_set_word(eNum,e)) //設定演算法中的e
return NULL;
if(!RSA_generate_key_ex(rsa,bit,eNum,NULL)) //呼叫API生成RSA結構體
return NULL;
return rsa;
}
int main()
{
RSA *rsa=get_rsa(0x10001,1024); //利用e為0x10001(65537)生成一個模數n為1024的rsa指標
//RSA_print_fp(stdout,rsa,0); //執行這句話可以打印出生成的十六進位制格式的模數n、兩個質數p、q等
unsigned char in[]="Hello World"; //要加密的明文
unsigned char out[2048]; //儲存加密後的資訊
unsigned char res[120]; //儲存解密後的資訊,長度要小於RSA_size(rsa)
//用於加解密傳輸:
puts(in); //列印明文
printf("%d\n",RSA_public_encrypt(strlen(in)+1,in,out,rsa,RSA_PKCS1_PADDING)); //公鑰加密明文生成密文
puts(out); //列印密文
printf("%d\n",RSA_private_decrypt(128,out,res,rsa,RSA_PKCS1_PADDING)); //私鑰解密密文
puts(res); //列印解密後的密文
//用於簽名和驗籤:
printf("%d\n",RSA_private_encrypt(strlen(in)+1,in,out,rsa,RSA_PKCS1_PADDING)); //私鑰加密明文生成密文
puts(out);//列印密文
printf("%d\n",RSA_public_decrypt(128,out,res,rsa,RSA_PKCS1_PADDING)); //公鑰解密
puts(res); //列印解密後的密文
RSA_free(rsa); //釋放rsa結構體記憶體
return 0;
}
這裡我封裝了生成RSA結構體指標的過程,RSA指標中儲存著之前原理中提過的n、p、q、e等BIGNUM型別的資料,似乎在1.1.1版本之前還可以檢視值,但是我嘗試去獲取這些資料時會報錯:
rsatest.c:20:32: error: dereferencing pointer to incomplete type ‘RSA {aka struct rsa_st}’
printf("%d\n",BN_num_bytes(rsa->n));
所以如果想檢視rsa生成的東西的話,可以使用RSA_print_fp(stdout,rsa,0)函式,效果如下:
這裡呼叫RSA_generate_key_ex(rsa,bit,eNum,NULL)傳入的bit引數即為生成的rsa指標中的模數的位數,小於512的話會報段錯誤,其實我本來還想試試能不能生成原理中舉的那個n為143的例子來著,看來也是根本不可能。
此外注意,儲存解密後的資訊的陣列,長度要小於RSA_size(rsa)函式的返回值(違反這個規則的話是無法完成解密的,解密結果是亂碼)我這裡由於modulus(之前提到的bit引數)是1024,1024/8=128,所以長度設為了120,如果改變了modulus的大小,這裡也要對應的改變。
最後來測試效果,也是比較鬼畜,所以直接截圖了:
可見演算法執行正確。
最後通過查閱相關資料得知,RSA加密演算法有兩種用途:
第一種用法:公鑰加密,私鑰解密。---用於加解密
第二種用法:私鑰簽名,公鑰驗籤。---用於簽名