這是我寫的第一篇online的技術文章,之前自己學習過程中的筆記都是寫在本地的,但是最近在找資料的時候,感覺到很多時候,自己都能在網上找到自己的所需,爲何不把自己學的也記錄到網上?閉門造車是愚蠢的,分享才能提高。
於是,在做了《信息安全》的課程實驗後,想把他作爲牛刀小使的第一步。好吧,進入正題:
首先,我們來看一下什麼是SPN,其中文是代換-置換網絡,其實就是將一個比特串主要經過兩種變換,分別是代換和置換,得到另一個比特串,從而實現加密的效果。但是前提是,可以經過類似的手段將這個比特串還原,這也就是解密了。
SPN的比較正式的定義:給定一個lm比特的串x=(x<1>,x<2>,...,x<m>),可以看成是m個長度爲l比特的子串的並聯,即x<1> || x<2> || ... || x<m>.對此串進行Nr輪變換,每一輪先用異或操作混入該輪的輪密鑰,然後對每個子串x<i>使用進行m次代換,然後使用進行一次置換。也叫S盒,爲P盒。
代換,實質是數值的代換,4個bit的二進制數對應的16進制範圍是0~f,下圖就是的一個示例:
Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
e | 4 | d | 1 | 2 | f | b | 8 | 3 | a | 6 | c | 5 | 9 | 0 | 7 |
置換,實際上是位置的置換,具體含義是Z=2時對應的5,是指原串的第5個bit放到新串的第2個bit的位置上,而不是指第原串的第2個bit放到新串的第5個bit的位置上,可以通過求該置換對應的逆置換來實現該操作,下圖是的一個示例:
Z | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
9 | 6 | 10 | 1 | 13 | 14 | 8 | 12 | 7 | 15 | 2 | 5 | 16 | 4 | 11 | 3 |
SPN的算法模型如下:
正常的SPN算法過程就如以上所示,代換的實現相對與置換來說要容易,置換對於“位”的操作要求比較熟悉,在我的實現中將做S盒的代換改成是一個置換,如下:
Z | 1 | 2 | 3 | 4 |
2 | 3 | 1 | 4 |
而置換 不變。
在實現中,我們取m=4,l=4,也就是一個加密單元爲16位。輪數Nr取16,當然,相同條件下,輪數越多越安全。對應的密鑰也要有16個,我們通過一個初始密鑰循環左移1,2...,16位獲得,並設該初始密鑰爲6A8E。實現對“華南吃飯大學”一字符串的加密以及解密。
在實現之前,有必要總結一下常見的位運算技巧以及在實現S、P盒的時候要用到這些技巧的簡單運用:
(1)幾個超級簡單的公式:
- a == (a & 1);//true
- a == (a | 0);//true
- 0 == (a & 0);//true
- 1 == (a | 1);//true
(2)基於(1)的運用
- //bit是一個指定位是1,其他是0的串
- //打開位,將bit中指定的位總設爲1
- a = a | bit;
- //關閉位,將bit中指定的位總設爲0
- a = a & ~bit;
- //切換位,將bit中指定的位取相反值,即是1變0,0變1
- a = a ^ bit;
(3) 將一個二進制串中的某幾位置換成指定的幾位,就是例如想將a串1010101010111011中的 第8位到第11位 置換爲1100,即是得到b串1010110010111011。
其實方法很簡單,只需要執行以下2個操作:
- a = a & 1111000011111111;//得到a=1010000010111011
- b = a | 0000110000000000;//得到b=1010110010111011
對與第一行代碼,首先把a的第8到第11位都置爲0,其他位不變,這通過和一個第8到11位是0,其他位是1的串作與運算獲得;然後和一個第8到第11爲指定的值1100,其他位是0的串作或運算來得到最終要的b串。其實,這裏就是基於(1)、(2)的靈活使用。
(4)實現“循環左移”操作
- //使unit循環左移nBit位
- unsigned short ROL(unsigned short unit,int nBit){
- unsigned short temp = getBitOne(nBit);
- temp &= unit;
- temp >>= (16-nBit);
- return (unit<<nBit)|temp;
- }
- //取得高nBIt位是1,其他位是0的二進制串對應的無符號短整形
- unsigned short getBitOne(int num){
- unsigned short base_value = 0xffff;
- return base_value <<= (16-num);
- }
過程是,先將unit的高nBit位截取下來,然後unit使用"<<"進行無循環左移,最後把之前保存好的高nBit位放回低nBit位。其實,這就是得到輪密鑰的過程。
以下主要的實現:
前提:
- typedef unsigned short UINT16;
- typedef unsigned char UINT8;
(1)如何求S、P盒置換的逆置換
- //pBox[4]={2,3,1,4},求得的逆是prBox[4]={3,1,2,4}
- //pBox是原S、P盒的指針,prBox是求逆後的指針,len是S、P盒的長度
- void getReverseBox(int * pBox,int * prBox,int len){
- for (int i=0; i<len; i++)
- {
- prBox[pBox[i]-1] = i+1;
- }
- }
則求逆得到:
Z | 1 | 2 | 3 | 4 |
|
3 | 1 | 2 | 4 |
(2)取得需加密字符串的加密單元,也就是取得一個16位的數,使用無符號短整形,在加密過程中的操作都是基於這樣的加密單元。
思路:因爲每個字符佔一個字節(8位),所以每兩個字符合併成一個加密單元,主要還是要用到之前的技巧
- a == (a | 0);//true
具體過程如下:
- //str指向需加密的字符串,方法執行後pUnits指向一組加密單元,len是加密單元數組長度
- void getUnit(const char * str,UINT16 * pUnits,int & len){
- int count=0;
- for (int i=0; i<strlen(str); i=i+2,count++)//每個循環獲得一個操作數,unit1做低位,unit2做高位
- {
- UINT8 unit1 = (UINT8)str[i];
- UINT8 unit2 = (UINT8)str[i+1];
- UINT16 temp = 0x0;
- temp |= unit2;
- temp <<= 8;
- temp |= unit1;
- pUnits[count] = temp;
- }
- len = count;
- }
(3)將加密單元還原爲字符串,一個加密單元可還原成兩個字符或者一個漢字。
思路:其實這是(2)中的逆過程,但是必須注意字符存放的位置順序,以及注意對動態內存的管理,注意釋放不再使用的內存。因爲C++中有時候沒有釋放一兩個字節的內存看似無關痛癢,main函數反正也是返回給操作系統的,但是正由於這種意識的存在使得在處理一些更復雜的內存問題時不知所措。
- //pUnit指向待還原的加密單元,len是其長度
- char * Unit2String(UINT16 * pUnit,int len){
- char * pStr = new char[2*len+1];
- pStr[2*len] = '\0';
- for (int i=0; i<len; i++)
- {
- UINT16 temp = 0x00ff;
- pStr[2*i]= char(pUnit[i] & temp);
- pStr[2*i+1]= char(pUnit[i] >> 8);
- }
- return pStr;
- }
(3)S盒置換
因爲我們使用的加密單元是16位,但是使用的置換是對於4位而言的,要進行分組處理,所以需要經過比較多的位操作來得到S盒置換的目的。
- //unit指向加密單元的引用,pBox指向S盒
- void SBoxReplacement(UINT16 & unit,int * pBox){
- for (int i=1; i<=4; i++)//逐步完成子盒的置換
- {
- subSBoxReplacement(unit,i,pBox);
- }
- }
- //完成第whichSubBox子盒的置換,1 <= whichSubBox <= 4
- void subSBoxReplacement(UINT16 & unit,int whichSubBox,int * pBox){
- //例:unit:1010101010111011 ,whichSubBox:3,1010->1100
- UINT16 temp = getBitOne(4);//1111000000000000
- temp >>= ((4-whichSubBox)*4);//0000111100000000
- UINT16 temp2 = ~temp;//1111000011111111
- temp &= unit;
- temp >>= ((whichSubBox-1)*4);//0000000000001010
- basicSBoxReplacement(temp,pBox);//temp:0000000000001100
- temp <<= ((whichSubBox-1)*4);//0000110000000000
- temp2 &= unit;//1010000010111011
- temp2 |= temp;
- unit = temp2;//1010110010111011
- }
- //最底層的s盒置換
- void basicSBoxReplacement(UINT16 & unit,int * pBox){
- UINT16 temps[4];
- for (int i=0; i<4; i++)
- {
- temps[i] = getBitSingle(i+1);
- temps[i] &= unit;
- temps[i] >>= i;
- }
- UINT16 result = 0x0;
- int prBox[4];
- getReverseBox(pBox,prBox,4); //求逆來更容易實現S盒置換
- for (int j=0; j<4; j++)
- {
- temps[j] <<= (prBox[j]-1);
- result |= temps[j];
- }
- unit = result;
- }
在此,使用了3個層次來完成該置換,第一層SBoxReplacement純碎是劃分了子盒,第一子盒是0到3位,第二子盒是4到7位,第三子盒是8到11位,第四子盒是12到15位。
第二層subSBoxReplacement是完成某個子盒的置換,也就是完成一個加密單元中某4個連續位的置換。過程是將第x組的四位推到低四位,即形如000000000000XXXX,然後再調用basicSBoxReplacement()進行最底層的置換,最後把低4位放回原來的位置。
第三層basicSBoxReplacement可以看成是一個工具函數,直接對一個16位數的低4位進行置換。思路是先把輸入參數000000000000XXXX(unit)產生4個16位的數,第i個數的最低位對應原來000000000000XXXX中的第i位,形如000000000000000X,然後再根據S盒來將每個000000000000000X中的X移動到其最後應處的位置,從而達到置換的目的。
(4)P盒置換,與S盒中的 basicSBoxReplacement的思路一致,只是實質操作的位數是16,而S盒中的是4位罷了。也就不貼出代碼了。
(5)加密
加密的過程是按照SPN加密算法的模型進行的,但加密的輪數略有減少。
- //str指向待加密的串,pSBox指向S盒,pPBox指向P盒
- //keys指向一組輪密鑰,p指向加密後的加密單元(可利用Unit2String進行顯示)
- void encrypt(const char * str,int * pSBox,int * pPBox,UINT16 * keys,UINT16 * & p){
- int len;
- UINT16 * pUnit = new UINT16[strlen(str)/2];
- getUnit(str,pUnit,len);
- for (int i=0; i<(strlen(str)/2); i++)//加密單元遍歷每個
- {
- UINT16 u = pUnit[i];
- for (int j=0; j<14; j++)//14輪
- {
- u = u^keys[j];
- SBoxReplacement(u,pSBox); //S盒置換
- PBoxReplacement(u,pPBox); //P盒置換
- }
- u ^= keys[14];
- SBoxReplacement(u,pSBox);
- u ^= keys[15];
- pUnit[i] = u;
- }
- p = pUnit;
- //輸出每個數的二進制形式,作調試之用
- for (int i=0; i<len; i++)
- {
- if(i%3==0){
- cout<<endl;
- }
- cout<<bitset<16>(pUnit[i])<<"\t";
- }
- cout<<endl;
- }
(6)解密
解密其實是加密的逆過程,只需按着加密的步驟到過來邊可解密,但是使用輪密鑰時順序與加密的時候相反。
- //pUnit指向待解密的加密單元,pSBox應指向S盒的逆,pPBox應指向P盒的逆
- //keys指向輪密鑰,len是該組加密單元的長度
- void decrypt(UINT16 * & pUnit,int * pSBox,int * pPBox,UINT16 * keys,int len){
- for (int i=0; i<len; i++)
- {
- UINT16 y;
- y = pUnit[i]^keys[15];
- SBoxReplacement(y,pSBox);
- y ^= keys[14];
- for (int j=13; j>=0; j--)
- {
- PBoxReplacement(y,pPBox);
- SBoxReplacement(y,pSBox);
- y ^=keys[j];
- }
- pUnit[i] = y;
- }
- // 輸出每個數的二進制形式,作調試之用
- for (int i=0; i<len; i++)
- {
- if(i%3==0){
- cout<<endl;
- }
- cout<<bitset<16>(pUnit[i])<<"\t";
- }
- cout<<endl;
- }
(7)運行
附件有整個程序的源代碼,可以下載下來看看。還有歡迎大家拍磚指正。