新上映的電影《解密》看的有點上頭。整體來說,這是一部很不錯的電影。
言歸正傳,電影中講到了數(shù)據(jù)加密,
彭老師之前玩過對稱/非對稱加密,
于是我借助linux的庫函數(shù)radom(),實現(xiàn)了一個簡單的加密算法demo,
并且我發(fā)給了專業(yè)搞加密的朋友,得到了對方的認可,
當時他還強烈要求我加入他們的團隊(* ̄︶ ̄)。
下面就帶著大家了解我是如何實現(xiàn)這個簡單但卻很實用的加密方法。
一、一種常見的網(wǎng)絡通信的加密流程
關(guān)于加密的算法很多,實際實現(xiàn)過程千差萬別,
下圖是一個常見的網(wǎng)絡通信加密的應用場景。
密碼機的一些說明:
- 如上圖所示,基于C/S架構(gòu)的服務器和客戶端通信模型,
下面以客戶端如果要發(fā)送一段加密的密文給服務器,C/S需要交互的流程。
1 服務器端發(fā)送密鑰密文
- 首先服務器端、客戶端都保存了一個默認的密鑰服務器端隨機生成密鑰keygen,并使用該默認密鑰對keygen加密,生成密鑰密文客戶端可以通過命令定期請求該密鑰密文或者服務器定時下發(fā)客戶端收到密鑰密文后,也可以通過默認密鑰進行解密得到明文的keygen
2. 客戶端對數(shù)據(jù)加密
- 客戶端在發(fā)送數(shù)據(jù)之前,首先生成一個同步碼將同步碼和keygen設(shè)置給密碼機,然后向密碼機申請一定長度的密鑰將明文和密鑰通過一定的算法進行加密(通常是異或),生成數(shù)據(jù)密文
3. 客戶端發(fā)送同步碼和數(shù)據(jù)密文
- 客戶端將數(shù)據(jù)密文和同步碼明文一起發(fā)送給服務器服務器提取出同步碼
4. 服務器端接收數(shù)據(jù)并解密
- 服務器將keygen和同步碼設(shè)置給密碼機,同時申請一定數(shù)量的密鑰服務器根據(jù)密鑰對密文進行解密,即得到對應的明文
因為服務器和客戶端此時都使用了相同的keygen,和同步碼,所以雙方申請的密鑰序列一定是一樣的。
二、函數(shù)實現(xiàn)
下面是一口君實現(xiàn)的加密算法的一些函數(shù)原型以及功能說明,這些函數(shù)基本實現(xiàn)了第一節(jié)的功能。
1. 申請加密密鑰函數(shù)request_key
int?request_key(int?sync,int?key_num,char?key[])
功能:
?向密碼機申請一定數(shù)量的用于加密數(shù)據(jù)的密鑰,如果不設(shè)置新的keygen,那么生成的密碼會順序產(chǎn)生下去,每次申請密鑰都會記錄上次生成的密鑰的偏移,下次在申請的時候,都會從上一位置繼續(xù)分配密鑰
參數(shù):
?sync:同步碼,密碼機依據(jù)此同步產(chǎn)生隨機序列的密鑰
?key_num:申請的密鑰個數(shù)
?key:申請的密鑰存儲的緩存
返回值:
?實際返回密鑰個數(shù)
2. 設(shè)置密鑰序列函數(shù)set_keygen
void?set_keygen(int?key)
功能:
?向密碼機設(shè)置keygen,設(shè)置后會影響產(chǎn)生的隨機密鑰序列
參數(shù):
?key:密鑰
返回值:
?無
3. 產(chǎn)生隨機數(shù)born_seed
int?born_seed(int?sync,int?key)
功能:
?根據(jù)同步碼和keygen生成隨機密鑰種子
參數(shù):
??? sync:同步碼?
?key:密鑰
返回值:
?種子
4. 重置keygen reset_keygen()
void?reset_keygen()
功能:
?重置keygen,會影響生成的隨機數(shù)序列
三、測試代碼實例
最終文件如下:
key.c??key.h??main.c
示例1 檢測產(chǎn)生的隨機序列
int?main(int?argc,?char?*argv[])
{
?int?i;
?unsigned?int?len;
?int?j,?r,?key_num;
?unsigned?int?sync?=?0;
?unsigned?char?key[MAX_KEY_REQUEST];
?key_num?=?10;
?printf("n--------------采用默認keygen?同步碼=0?產(chǎn)生密文----------------n");
?reset_keygen();
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰0-9:",key,len);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰10-19:",key,len);
?printf("n--------------采用keygen=1234?同步碼=0?產(chǎn)生密文----------------n");
?set_keygen(1234);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰0-9:",key,len);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰10-19:",key,len);
}
執(zhí)行結(jié)果:
--------------采用默認keygen?同步碼=0?產(chǎn)生密文----------------
密鑰0-9:?----[10]
a5?52?c8?14?5d?f7?46?5b?89?42?
密鑰10-19:?----[10]
38?69?6f?a6?08?d2?69?39?cd?29?
--------------采用keygen=1234?同步碼=0?產(chǎn)生密文----------------
密鑰0-9:?----[10]
0e?83?0b?73?ec?f5?4b?4a?74?35?
密鑰10-19:?----[10]
e7?f1?06?41?c8?6b?aa?df?0c?3d?
可以看到采用不同的keygen產(chǎn)生的隨機序列是不一樣的。
如果設(shè)置不同的同步碼,仍然序列還會不一樣。
示例2 用默認keygen,加解密
char?data0[10]={
?0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0x10,
};
int?main(int?argc,?char?*argv[])
{
?int?i;
?unsigned?int?len;
?int?j,?r,?key_num;
?unsigned?int?sync?=?0;
?unsigned?char?key[MAX_KEY_REQUEST];
?char?buf[120]={0};
?key_num?=?10;
?printf("n--------------采用默認keygen開始加密----------------n");
?reset_keygen();
?print_array("n明文:",data0,key_num);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰:",key,len);
?for(i=0;i<len;i++)
?{
??buf[i]?=?data0[i]^key[i];
?}
?print_array("n密文:",buf,len);
?
?printf("n--------------------開始解密--------------------n");
?reset_keygen();
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?
?for(i=0;i<len;i++)
?{
??buf[i]?=?buf[i]^key[i];
?}
?print_array("n明文:",buf,len);
}
測試結(jié)果
--------------采用默認keygen開始加密----------------
明文:?----[10]
01?02?03?04?05?06?07?08?09?10?
密鑰:?----[10]
a5?52?c8?14?5d?f7?46?5b?89?42?
密文:?----[10]
a4?50?cb?10?58?f1?41?53?80?52?
--------------------開始解密--------------------
明文:?----[10]
01?02?03?04?05?06?07?08?09?10?
示例3 用不同的keygen和同步碼加解密
int?main(int?argc,?char?*argv[])
{
?int?i;
?unsigned?int?len;
?int?j,?r,?key_num;
?unsigned?int?sync?=?0;
?unsigned?char?key[MAX_KEY_REQUEST];
?char?buf[120]={0};
?unsigned?int?mykeygen;
?if?(argc?!=?4)?{
??fprintf(stderr,?"Usage:?%s?<seed>?<key?num>?<keygen>n",?argv[0]);
??exit(EXIT_FAILURE);
?}
?sync?=?atoi(argv[1]);
?key_num?=?atoi(argv[2]);
?mykeygen?=?atoi(argv[3]);
?printf("n--------------采用自定義的keygen、同步碼開始加密----------------n");
?set_keygen(mykeygen);
?print_array("n明文:",data0,key_num);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?print_array("密鑰:",key,len);
?for(i=0;i<len;i++)
?{
??buf[i]?=?data0[i]^key[i];
?}
?print_array("n密文:",buf,len);
?
?printf("n--------------------開始解密--------------------n");
?set_keygen(mykeygen);
?memset(key,0,sizeof(key));
?len?=?request_key(sync,key_num,key);
?for(i=0;i<len;i++)
?{
??buf[i]?=?buf[i]^key[i];
?}
?print_array("n明文:",buf,len);
?exit(EXIT_SUCCESS);
}
執(zhí)行結(jié)果如下:
--------------采用自定義的keygen、同步碼開始加密----------------
明文:?----[10]
01?02?03?04?05?06?07?08?09?10?
密鑰:?----[10]
53?00?29?cd?27?eb?cc?80?1a?d7?
密文:?----[10]
52?02?2a?c9?22?ed?cb?88?13?c7?
--------------------開始解密--------------------
明文:?----[10]
01?02?03?04?05?06?07?08?09?10?
可見我們的確實現(xiàn)了數(shù)據(jù)的加密和解密。
四、數(shù)據(jù)加密的實際使用
假定我們使用上述實例代碼,把對應的功能移植到C/S兩端,
那么一次完整的數(shù)據(jù)加密以及數(shù)據(jù)的傳輸參考流程如下:
記住一點,只要雙方設(shè)置相同的keygen和同步碼,那么密碼機吐出來的密鑰就是相同序列,
客戶端發(fā)送每發(fā)送一個報文,就把自己的明文同步碼一起發(fā)送給服務器,
服務器根據(jù)提前發(fā)送給客戶端的keygen和同步碼就可以實現(xiàn)解密操作,
雖然你可以看到明文的同步碼,
但是還需要破解密碼機算法、服務器下發(fā)的keygen密文。
五、 原理
實現(xiàn)加密算法的主要問題是如何產(chǎn)生隨機序列作為密鑰。
本例是借用庫函數(shù)rand() 原型如下:
#include?<stdlib.h>
int?rand(void);
函數(shù)rand() 雖然可以產(chǎn)生隨機序列,但是每次產(chǎn)生的序列其實順序是一樣的。
#include?<stdio.h>
main()
{
?int?i?=?0;
?for(i=0;i<10;i++)
?{
??printf("%d?",rand());
?}
?putchar('n');
}
運行結(jié)果如下:
peng@peng-virtual-machine:/mnt/hgfs/peng/rand/code$?./a.out?
1804289383?846930886?1681692777?1714636915?1957747793?424238335?719885386?1649760492?596516649?1189641421?
peng@peng-virtual-machine:/mnt/hgfs/peng/rand/code$?./a.out?
1804289383?846930886?1681692777?1714636915?1957747793?424238335?719885386?1649760492?596516649?1189641421?
要想每次都產(chǎn)生不一樣的隨機序列應該怎么辦呢?需要借助srand()函數(shù)
void?srand(unsigned?int?seed);
只需要通過該函數(shù)設(shè)置一個種子,那么產(chǎn)生的序列,就會完全不一樣,
通常我們用time()返回值作為種子,
在此我們隨便寫入幾個數(shù)據(jù),來測試下該函數(shù)
#include?<stdio.h>
main()
{
?int?i?=?0;
?srand(111);
?for(i=0;i<10;i++)
?{
??printf("%d?",rand());
?}
?putchar('n');
?srand(1111);
?for(i=0;i<10;i++)
?{
??printf("%d?",rand());
?}
?putchar('n');
}
執(zhí)行結(jié)果如下:
peng@peng-virtual-machine:/mnt/hgfs/peng/rand/code$?./a.out?
1629905861?708017477?1225010071?14444113?324837614?2112273117?1166384513?1539134273?1883039818?779189906?
1383711924?882432674?1555165704?1334863495?1474679554?676796645?154721979?534868285?1892754119?100411878?
可見輸入不同的種子就會產(chǎn)生不同的序列。
函數(shù)原型如下:
本例原理比較簡單,沒有考慮太復雜的應用(比如多路密鑰的管理)和數(shù)據(jù)安全性,
只闡述加解密的流程,僅作為學習理解加解密流程用,此種加密算法屬于對稱加密,相對比較簡單,還是比較容易破解。
目前市場上都是由專業(yè)的公司和團隊實現(xiàn)加解密功能。
一口君之前曾寫過聊天室的一個小項目,
《聊天室》
從0實現(xiàn)基于Linux socket聊天室-多線程服務器模型-1
從0實現(xiàn)基于Linux socket聊天室-多線程服務器一個很隱晦的錯誤-2
從0實現(xiàn)基于Linux socket聊天室-實現(xiàn)聊天室的登錄、注冊功能-3
從0實現(xiàn)基于Linux socket聊天室-增加公聊、私聊-4??必看
從0實現(xiàn)基于Linux socket聊天室-增加數(shù)據(jù)庫sqlite功能-5?必看
從0實現(xiàn)基于Linux socket聊天室-增加數(shù)據(jù)加密功能-6
彭老師基于該加密機制,將聊天室所有客戶端與服務器所有交互數(shù)據(jù)進行加密處理,有興趣的老鐵可以下載學習下。
本文完整代碼下載地址:
鏈接:https://pan.baidu.com/s/1VvGNlNGEUWWZHQZ1_gYU7A 提取碼:o9se
公眾號【一口Linux】后臺回復:數(shù)據(jù)加密,即可獲得全部源碼