base64加解密c++實作
- Base64編碼原理
-
- 1、加密原理
- 2、c++實作加解密過程
- IDA中算法分析
-
- std::string的使用總結
- base64加密分析
- 小插曲-base64隐寫
- 源碼
- 實戰中的魔改例子
-
- 1、通過異或還原表
- 2、通過移位産生表
Base64編碼原理
目前單字元最大為3Byte(特殊字元),漢字(2Byte)、字母(1Byte)…
而base64則是通過位運算加索引表将3個字元的内容編碼為4個字元,也就是編碼目前所有的單字元是完全夠用的,base64能實作不可見字元和圖檔的傳遞,非常的便捷實用。
1、加密原理
将3個8bit的轉換為4個6bit的資料,再通過索引表轉換為4個可見字元,如下:

base64的正常索引表為:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
上述即明文為:"son"時 先将三個字元依次轉化為 83 111 110
對應二進制則為 01010011 01101111 01101110
将則24位拼在一起之後4等分(6個一組):
010100 110110 111101 101110
之後轉化為10進制,作為下标進行索引,如上圖所示。
對一個長為len的明文,重複進行len//3次,如果len為三的倍數那麼密文長度為(4/3)len,如果len的長度不為3的倍數,則需要額外的補充’\0’直到分組為3的倍數,補充幾次‘/0’則在密文尾部添加幾個‘=’,最終明文長度仍為4的倍數
2、c++實作加解密過程
加密函數主體:
核心為通過移位運算和&運算,将24bit劃分為4組6bit接着轉為10進制,進行索引,同時對明文不滿足3的倍數進行填充。
string base64_encode(char*m,int len){ //轉入的明文m 和 明文長度len
string cipher;
char tmp_3[3];//3個一組 臨時存放
char enbase_4[4];//3->4
int i=0;
//了解base64的原理後直到是3->4是以通過一個循環來實作過程
while(len){
tmp_3[i]= *(m++);//将明文内容3個一組引入到tmp臨時空間中去
i++;
if(i==3){ //如果得到3個字元,則進行位運算
enbase_4[0]=(tmp_3[0]&(0xfc))>>2; //0xfc == 0x11111100
enbase_4[1]=((tmp_3[0]&(0x03))<<4)+((tmp_3[1]&0xf0)>>4);
enbase_4[2]=((tmp_3[1]&0x0f)<<2)+((tmp_3[2]&0xc0)>>6);
enbase_4[3]=tmp_3[2]&0x3f;//通過位運算實作3個8bit一組 -> 4個6bit一組
for(int j=0;j<4;j++){
cipher+=base_table[enbase_4[j]]; //索引
}
i=0;
}
len--;
}
//上述循環将len//3 組資料轉為base64 ,最優情況 i=0&&len==0;其餘則是i=1 || i=2
if(i)
{
for(int j=i;j<3;j++)
tmp_3[j]='\0'; //補充到三位 每一個位元組補'\0' 即 0b00000000
enbase_4[0]=(tmp_3[0]&(0xfc)) >>2;
enbase_4[1]=((tmp_3[0]&(0x03))<<4) + ((tmp_3[1]&0xf0)>>4);
enbase_4[2]=((tmp_3[1]&0x0f)<<2) + ((tmp_3[2]&0xc0)>>6);
// 三次即可,如果i=2的話 最多隻有三組6為二進制的值不全為0,且最後一組的後兩位一定為0
for(int j=0;j<i+1; j++) //注意為j<i+1 1個8bit 分為2個6bit
{
cipher+=base_table[enbase_4[j]];
}
while(i!=3){
cipher+='='; //缺幾個位元組 就補幾個'='
i++;
}
}
return cipher;
}
解密函數主體:
解密則是加密的逆過程,實作4->3 ,并且是找出密文在base64表中的下标,将所有找出的下标8個一組轉ASCII,得到最初的明文,特殊是對于尾部有‘=’的處理
string base64_decode(char*c,int len){
string m;
char tmp_4[4];
char debase_3[3];//實作4B->3B
int i=0;
int index=0; //表示目前在密文中的下标
while(len--&&c[index]!='='){ //讀完密文或讀到‘=’退出循環
tmp_4[i]=base_table.find(c[index])&0x3f; //通過find函數 找到下标對應的值
i++;index++;
if(i==4){
debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2);
debase_3[2]=((tmp_4[2]&0x3)<<6) + (tmp_4[3]&0x3f);//注意位運算的優先級
for(int j =0;j<3;j++)
m+=(char)debase_3[j];
i=0;
}
}
if(i){ // 如果i不為0 則i表示 '=' 在4個字元中的下标
for(int j=i;j<4;j++)
tmp_4[j]='\0';//把所有等号的位置全補上0
debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2); //最多轉成兩個字元
for(int j=0;j<i-1;j++) //j<i-1 比如 i=3時出現'=' 那麼證明一個等号 故明文有兩位
m+=debase_3[j];
}
return m;
}
IDA中算法分析
補全主函數後生成EXE檔案,用IDA64對EXE檔案進行靜态分析。
用c++實作base64編碼的時候用到了string庫,分析看起來比較複雜,去了解了一下std::string庫的常見使用。
std::string的使用總結
std::string s1;
std::string s3(s2);
std::string s2(“this is a string”);
begin 得到指向字元串開頭的Iterator
end 得到指向字元串結尾的Iterator
rbegin 得到指向反向字元串開頭的Iterator
rend 得到指向反向字元串結尾的Iterator
size 得到字元串的大小
length() 和size函數功能相同
max_size 字元串可能的最大大小
capacity 在不重新配置設定記憶體的情況下,字元串可能的大小
empty 判斷是否為空
operator[] 取第幾個元素,相當于數組
c_str 取得C風格的const char* 字元串
data 取得字元串内容位址
operator= 指派操作符
reserve 預留白間
swap 交換函數
insert 插入字元
append 追加字元
push_back 追加字元
erase 删除字元串
clear 清空字元容器中所有内容
resize 重新配置設定空間
assign 和指派操作符一樣
replace 替代
copy 字元串到空間
find 查找,傳回基于0的索引号
rfind 反向查找
find_first_of 查找包含子串中的任何字元,傳回第一個位置
find_first_not_of 查找不包含子串中的任何字元,傳回第一個位置
find_last_of 查找包含子串中的任何字元,傳回最後一個位置
find_last_not_of 查找不包含子串中的任何字元,傳回最後一個位置
substr(n1,len) 得到字元串從n1開始的長度為len的子串
比較字元串(支援所有的關系運算符)
compare 比較字元串
operator+ 字元串連結
operator+= += 操作符
operator== 判斷是否相等
operator!= 判斷是否不等于
operator< 判斷是否小于
operator>> 從輸入流中讀入字元串
operator<< 字元串寫入輸出流
getline 從輸入流中讀入一
————————————————
版權聲明:本文為CSDN部落客「哀醬」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/u010821666/article/details/77934510
通過一定的C++基礎,std::string 大緻能辨識,string本身是打包好的一個類,是以有着構造和析構函數這種指派。
base64加密分析
程式流程是對輸入進行了base64加密,之後和一個字元串解密,一般題目中隻要識别出編碼方式便能簡單破解,為了深入學習base64編碼原理,下面主要分析base64的加密算法。
第一部分,對明文字元串進行3個一組分組,每一組進行分割和base索引。
從上圖也可以看出base64算法明顯的特征:分組進行位運算實作3變4,另外就是base64索引表。
源代碼将base64的表寫成了static,存放在全局變量區,内部檔案可見,外部檔案不可見,是以要獲得表需要進行動态調試,跟進v4便可得到表,不太清楚為什麼base_table顯示UNKNOW。
當然如果把static string base_table換成其他内容則變成了一道base64換表的題,并且需要動态調試找到索引表。
接下來則是對明文不是3的倍數的處理:
即如果2個字元則補一個’='且密文經過換表的内容為2+1=3
二進制位 xxxxxx xxxxxx xxxx00 000000 也就是隻要前3部分的索引值,剩餘一位補‘=’。
同理如果1個字元則要補兩個‘=’那麼分割的二進制位 xxxxxx xx0000 000000 000000 也就是隻有前兩部分的值為特殊值,剩餘兩位補‘=’。
小插曲-base64隐寫
通過上述補位也可以看出,補充的0有部分是參與base64表的索引的,則也為我們提供了一個思路,可不可以不用0來補,用其他相用的資料是否可能起到更好的效果,這就與MISC中的base64隐寫有了一定的聯系,即一個等号時,第三部分低二位的值用明文來填充,兩個等号時第二部分的低4位用明文來填充。
即:
xxxxxx xxxxxx xxxxab 000000
xxxxxx xxabcd 000000 000000
a,b,c,d…代表某一二進制資料。可見base64隐寫雖然可能對明文的最後一個字母有所影響,但是不影響解密内容,畢竟隻是改寫了填充部分。
附上解密代碼:
import base64
a=("")
mode=("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") #base64 mode碼表
c=("")
f = open("base64.txt","r") #讀取檔案
line = f.readline() #一行一行讀取 下标指針自動向後移動
while line:
if("=" in line):
a=line.replace('=','')[-2:] #去掉‘=’留下最後一個字元 檔案讀取最後一位為‘/n'
print(a,end="") #這裡用來檢測是否出錯了
num=(line.count('='))*2 #等号個數來決定截取二進制字元的個數 一個'='截取兩個 二個'='截取4個
c=c+(bin(mode.index(a[0])).replace("0b",'')[-(num):].zfill(num)) #這裡是關鍵,對其進行查表對号,再進行二進制轉換篩選
line = f.readline() #.zfill()是在左邊填0 補成num位
else:
line = f.readline() #沒有等号就跳過
continue
f.close()
for i in range (0,len(c),8):
print(chr(int(c[i:i+8],2)),end='') #輸出 8位二進制轉為 10進制 之後轉ascii碼
根據腳本和上述解釋,了解起來感覺更加輕松。
源碼
最終附上自己寫的源碼,可能在健壯性和效率等有些方面不足或存在錯誤,還請各位師傅們指正。
#include<iostream>
#include<cstring>
using namespace std;
string base64_encode(char*m,int len);
string base64_decode(char*c,int len);
static string base_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int main(){
char m[100];
string ss;
cout<<"pleas tell me your secret:";
cin>>m;
ss=base64_encode(m,strlen(m));
string secret="ZmxhZ3tUaGlzX2lzX215X2Jhc2V9";
if(secret==ss)
cout<<"you get it"<<endl;
else
cout<<"try a again!"<<endl;
return 0;
}
string base64_encode(char*m,int len){
string cipher;
char tmp_3[3];//3個一組 臨時存放
char enbase_4[4];//3->4
int i=0;
while(len){
//tmp_3[i] = m[i]; 錯誤的索引 i被改變了
tmp_3[i]= *(m++);
i++;
if(i==3){
enbase_4[0]=(tmp_3[0]&(0xfc))>>2; //0xfc == 0x11111100
enbase_4[1]=((tmp_3[0]&(0x03))<<4)+((tmp_3[1]&0xf0)>>4);
enbase_4[2]=((tmp_3[1]&0x0f)<<2)+((tmp_3[2]&0xc0)>>6);
enbase_4[3]=tmp_3[2]&0x3f;//通過位運算實作3個8bit一組 -> 4個6bit一組
for(int j=0;j<4;j++){
cipher+=base_table[enbase_4[j]]; //索引
}
i=0;
}
len--;
}
//上述循環将len//3 組資料轉為base64 ,最優情況 i=0&&len==0;其餘則是i=1 || i=2
if(i)
{
for(int j=i;j<3;j++)
tmp_3[j]='\0'; //補充到三位 每一個位元組補'\0' 即 0b00000000
enbase_4[0]=(tmp_3[0]&(0xfc)) >>2;
enbase_4[1]=((tmp_3[0]&(0x03))<<4) + ((tmp_3[1]&0xf0)>>4);
enbase_4[2]=((tmp_3[1]&0x0f)<<2) + ((tmp_3[2]&0xc0)>>6);
// 三次即可,如果i=2的話 最多隻有三組6為二進制的值不全為0,且最後一組的後兩位一定為0
for(int j=0;j<i+1; j++) //注意為j<i+1 1個8bit 分為2個6bit
{
cipher+=base_table[enbase_4[j]];
}
while(i!=3){
cipher+='='; //缺幾個位元組 就補幾個'='
i++;
}
}
return cipher;
}
string base64_decode(char*c,int len){
string m;
char tmp_4[4];
char debase_3[3];//實作4B->3B
int i=0;
int index=0; //表示目前在密文中的下标
while(len--&&c[index]!='='){
tmp_4[i]=base_table.find(c[index])&0x3f; //通過find函數 找到下标對應的值
i++;index++;
if(i==4){
debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2);
debase_3[2]=((tmp_4[2]&0x3)<<6) + (tmp_4[3]&0x3f);//注意位運算的優先級
for(int j =0;j<3;j++)
m+=(char)debase_3[j];
i=0;
}
}
if(i){ // 如果i不為0 則表示 '=' 在4個字元中的下标
for(int j=i;j<4;j++)
tmp_4[j]='\0';
debase_3[0]=((tmp_4[0]&0x3f)<<2) + ((tmp_4[1]&0x30)>>4);
debase_3[1]=((tmp_4[1]&0xf)<<4) + ((tmp_4[2]&0x3c)>>2); //最多轉成兩個字元
for(int j=0;j<i-1;j++) //j<i-1 比如 i=3時出現'=' 那麼證明一個等号 故明文有兩位
m+=debase_3[j];
}
return m;
}
實戰中的魔改例子
1、通過異或還原表
具體步驟不在過多叙述,主要看到在循環體的索引後進行了一步異或,跟進檢視表的内容。
确實,異或使base64表面目全飛。
dump出,接着跟0x76異或一次,發現是原始的base64表,正常base64解密即可。
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
2、通過移位産生表
附上執行個體:
對每一個轉換後的6bit數 + 61 也就是表的ASCII碼範圍是61 + i (i 從 0 到 63),直接python寫個腳本生成表,通過索引還原,再解密即可。
import base64
table=''
for i in range(64):
table+=chr(i+61)
c='@BdxRTbRBbjIVf`PEyqe^\^\|cc|[email protected][Myy]=='
base='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
cc=''
for i in range(len(c)-2):
cc+=base[table.find(c[i])]
cc+='=='
cipher=base64.b64decode(cc)
參考連結:
base64加密原理c++.
std::string使用總結.
為什麼要使用Base64?.