Base64
原理
Base64可以將ASCII字符串或者是二進制編碼成只包含A—Z,a—z,0—9,+,/ 這64個字符( 26個大寫字母,26個小寫字母,10個數字,1個+,一個 / 剛好64個字符)。這64個字符用6個bit位就可以全部表示出來,一個字節有8個bit 位,那麼還剩下兩個bit位,這兩個bit位用0來補充。其實,一個Base64字符仍然是8個bit位,但是有效部分只有右邊的6個 bit,左邊兩個永遠是0。Base64的編碼規則是將3個8位字節(3×8=24位)編碼成4個6位的字節(4×6=24位),之後在每個6位字節前面,補充兩個0,形成4個8位字節的形式,那麼取值範圍就變成了0~63。又因爲2的6次方等於64,所以每6個位組成一個單元。一般在CTF逆向題目中base64的加密過程主要是用自定義的索引表,所以如果能一眼能看出是base64加密就會節約很多時間。
加密過程
- base64的編碼都是按字符串長度,以每3個8bit的字符爲一組,
- 然後針對每組,首先獲取每個字符的ASCII編碼,
- 然後將ASCII編碼轉換成8bit的二進制,得到一組3*8=24bit的字節
- 然後再將這24bit劃分爲4個6bit的字節,並在每個6bit的字節前面都填兩個高位0,得到4個8bit的字節
- 然後將這4個8bit的字節轉換成10進制,對照Base64編碼表 ,得到對應編碼後的字符。
索引表如下
索引 | 對應字符 | 索引 | 對應字符 | 索引 | 對應字符 | 索引 | 對應字符 |
---|---|---|---|---|---|---|---|
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v | ||
14 | O | 31 | f | 48 | w | ||
15 | P | 32 | g | 49 | x | ||
16 | Q | 33 | h | 50 | y |
例子
第一個例子以base64加密SLF爲例子,過程如下
字符串 S L F
ASCII 83 80 76
二進制 01010011 01001100 01000110
合併 010100110100110001000110
6位 010100 110100 110001 000110
補零 00010100 00110100 00110001 00000110
進制 20 52 49 6
對照 U 0 x G
SLF -> U0xG
第二個例子以base64加密M爲例子,過程如下
字符串 M
ASCII 77
二進進 01001101
合併 01001101
6位 010011 01
補零 00010011 00010000
進制 19 16
對照 T Q = =
M -> TQ==
實現
最上面的base64char索引表可以自定義,這裏用c實現
#include <stdio.h>
#include <string.h>
// 全局常量定義
const char * base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char padding_char = '=';
/*編碼代碼
* const unsigned char * sourcedata, 源數組
* char * base64 ,碼字保存
*/
int base64_encode(const unsigned char * sourcedata, char * base64)
{
int i = 0, j = 0;
unsigned char trans_index = 0; // 索引是8位,但是高兩位都爲0
const int datalength = strlen((const char*)sourcedata);
for (; i < datalength; i += 3){
// 每三個一組,進行編碼
// 要編碼的數字的第一個
trans_index = ((sourcedata[i] >> 2) & 0x3f);
base64[j++] = base64char[(int)trans_index];
// 第二個
trans_index = ((sourcedata[i] << 4) & 0x30);
if (i + 1 < datalength){
trans_index |= ((sourcedata[i + 1] >> 4) & 0x0f);
base64[j++] = base64char[(int)trans_index];
}
else{
base64[j++] = base64char[(int)trans_index];
base64[j++] = padding_char;
base64[j++] = padding_char;
break; // 超出總長度,可以直接break
}
// 第三個
trans_index = ((sourcedata[i + 1] << 2) & 0x3c);
if (i + 2 < datalength){ // 有的話需要編碼2個
trans_index |= ((sourcedata[i + 2] >> 6) & 0x03);
base64[j++] = base64char[(int)trans_index];
trans_index = sourcedata[i + 2] & 0x3f;
base64[j++] = base64char[(int)trans_index];
}
else{
base64[j++] = base64char[(int)trans_index];
base64[j++] = padding_char;
break;
}
}
base64[j] = '\0';
return 0;
}
/** 在字符串中查詢特定字符位置索引
* const char *str ,字符串
* char c,要查找的字符
*/
int num_strchr(const char *str, char c) //
{
const char *pindex = strchr(str, c);
if (NULL == pindex){
return -1;
}
return pindex - str;
}
/* 解碼
* const char * base64 碼字
* unsigned char * dedata, 解碼恢復的數據
*/
int base64_decode(const char * base64, unsigned char * dedata)
{
int i = 0, j = 0;
int trans[4] = { 0, 0, 0, 0 };
for (; base64[i] != '\0'; i += 4){
// 每四個一組,譯碼成三個字符
trans[0] = num_strchr(base64char, base64[i]);
trans[1] = num_strchr(base64char, base64[i + 1]);
// 1/3
dedata[j++] = ((trans[0] << 2) & 0xfc) | ((trans[1] >> 4) & 0x03);
if (base64[i + 2] == '='){
continue;
}
else{
trans[2] = num_strchr(base64char, base64[i + 2]);
}
// 2/3
dedata[j++] = ((trans[1] << 4) & 0xf0) | ((trans[2] >> 2) & 0x0f);
if (base64[i + 3] == '='){
continue;
}
else{
trans[3] = num_strchr(base64char, base64[i + 3]);
}
// 3/3
dedata[j++] = ((trans[2] << 6) & 0xc0) | (trans[3] & 0x3f);
}
dedata[j] = '\0';
return 0;
}
// 測試
int main()
{
const unsigned char str[] = "a45rbcd";
const unsigned char *sourcedata = str;
char base64[128];
base64_encode(sourcedata, base64);
printf("編碼:%s\n", base64);
char dedata[128];
base64_decode(base64, (unsigned char*)dedata);
printf("譯碼:%s", dedata);
getchar();
getchar();
return 0;
}
輸入如下
C:\Users\thunder>"D:\AlgorithmTest.exe"
編碼:YTQ1cmJjZA==
譯碼:a45rbcd
C:\Users\thunder>
上面的代碼是base64加密和解密字符串a45rbcd
我們用IDA查看,base64char即是我們的索引表
int __cdecl base64_encode(const char *sourcedata, char *base64)
{
int v2; // STEC_4
int v3; // STEC_4
int v4; // STEC_4
signed int datalength; // [esp+D0h] [ebp-2Ch]
unsigned __int8 trans_index; // [esp+DFh] [ebp-1Dh]
unsigned __int8 trans_indexa; // [esp+DFh] [ebp-1Dh]
int j; // [esp+E8h] [ebp-14h]
int ja; // [esp+E8h] [ebp-14h]
int jb; // [esp+E8h] [ebp-14h]
int i; // [esp+F4h] [ebp-8h]
i = 0;
j = 0;
datalength = j__strlen(sourcedata);
while ( i < datalength )
{
base64[j] = base64char[((signed int)(unsigned __int8)sourcedata[i] >> 2) & 0x3F]; # 右移
ja = j + 1;
trans_index = 16 * sourcedata[i] & 0x30;
if ( i + 1 >= datalength )
{
base64[ja] = base64char[trans_index];
v2 = ja + 1;
base64[v2++] = padding_char;
base64[v2] = padding_char;
j = v2 + 1;
break;
}
base64[ja] = base64char[((signed int)(unsigned __int8)sourcedata[i + 1] >> 4) & 0xF | trans_index]; # 右移
jb = ja + 1;
trans_indexa = 4 * sourcedata[i + 1] & 0x3C;
if ( i + 2 >= datalength )
{
base64[jb] = base64char[trans_indexa];
v4 = jb + 1;
base64[v4] = padding_char;
j = v4 + 1;
break;
}
base64[jb] = base64char[((signed int)(unsigned __int8)sourcedata[i + 2] >> 6) & 3 | trans_indexa]; # 右移
v3 = jb + 1;
base64[v3] = base64char[sourcedata[i + 2] & 0x3F];
j = v3 + 1;
i += 3;
}
base64[j] = 0;
return 0;
}
辨別
其實辨別很簡單,有很多的方法,最簡單的方法就是動態調試,直接用OD或者IDA動態調試,多輸入幾組數據,觀察加密後的字符串,存在=
這種字符串多半都有base64加密。 如果不能動態調試那就用IDA靜態觀察,觀察索引表,觀察對輸入的操作,比如上面很明顯的三次右移操作。
解密
一般解密用python來實現
import base64
s = 'key' # 要加密的字符串
a = base64.b64encode(s) # 加密
print a
print base64.b64decode(a) # 解密
在線解密網站 : https://www.qqxiuzi.cn/bianma/base.php
Base32
原理
Base32編碼是使用32個可打印字符(字母A-Z和數字2-7)對任意字節數據進行編碼的方案,編碼後的字符串不用區分大小寫並排除了容易混淆的字符,可以方便地由人類使用並由計算機處理。
值 | 符號 | 值 | 符號 | 值 | 符號 | 值 | 符號 | |||
---|---|---|---|---|---|---|---|---|---|---|
0 | A | 8 | I | 16 | Q | 24 | Y | |||
1 | B | 9 | J | 17 | R | 25 | Z | |||
2 | C | 10 | K | 18 | S | 26 | 2 | |||
3 | D | 11 | L | 19 | T | 27 | 3 | |||
4 | E | 12 | M | 20 | U | 28 | 4 | |||
5 | F | 13 | N | 21 | V | 29 | 5 | |||
6 | G | 14 | O | 22 | W | 30 | 6 | |||
7 | H | 15 | P | 23 | X | 31 | 7 | |||
填充 | = |
Base32將任意字符串按照字節進行切分,並將每個字節對應的二進制值(不足8比特高位補0)串聯起來,按照5比特一組進行切分,並將每組二進制值轉換成十進制來對應32個可打印字符中的一個。
由於數據的二進制傳輸是按照8比特一組進行(即一個字節),因此Base32按5比特切分的二進制數據必須是40比特的倍數(5和8的最小公倍數)。例如輸入單字節字符“%”,它對應的二進制值是“100101”,前面補兩個0變成“00100101”(二進制值不足8比特的都要在高位加0直到8比特),從左側開始按照5比特切分成兩組:“00100”和“101”,後一組不足5比特,則在末尾填充0直到5比特,變成“00100”和“10100”,這兩組二進制數分別轉換成十進制數,通過上述表格即可找到其對應的可打印字符“E”和“U”,但是這裏只用到兩組共10比特,還差30比特達到40比特,按照5比特一組還需6組,則在末尾填充6個“=”。填充“=”符號的作用是方便一些程序的標準化運行,大多數情況下不添加也無關緊要,而且,在URL中使用時必須去掉“=”符號。
與Base64相比,Base32具有許多優點:
- 適合不區分大小寫的文件系統,更利於人類口語交流或記憶。
- 結果可以用作文件名,因爲它不包含路徑分隔符 “/”等符號。
- 排除了視覺上容易混淆的字符,因此可以準確的人工錄入。(例如,RFC4648符號集忽略了數字“1”、“8”和“0”,因爲它們可能與字母“I”,“B”和“O”混淆)。
- 排除填充符號“=”的結果可以包含在URL中,而不編碼任何字符。
Base32也比Base16有優勢:
- Base32比Base16佔用的空間更小。(1000比特數據Base32需要200個字符,而Base16則爲250個字符)
Base32的缺點:
- Base32比Base64多佔用大約20%的空間。因爲Base32使用8個ASCII字符去編碼原數據中的5個字節數據,而Base64是使用4個ASCII字符去編碼原數據中的3個字節數據。
解密
import base64
s = 'key' # 要加密的字符串
a = base64.b32encode(s) # 加密
print a
print base64.b32decode(a) # 解密
在線網站 : https://www.qqxiuzi.cn/bianma/base.php
Base16
原理
Base16編碼使用16個ASCII可打印字符(數字0-9和字母A-F)對任意字節數據進行編碼。Base16先獲取輸入字符串每個字節的二進制值(不足8比特在高位補0),然後將其串聯進來,再按照4比特一組進行切分,將每組二進制數分別轉換成十進制,在下述表格中找到對應的編碼串接起來就是Base16編碼。可以看到8比特數據按照4比特切分剛好是兩組,所以Base16不可能用到填充符號“=”。
Base16編碼後的數據量是原數據的兩倍:1000比特數據需要250個字符(即 250*8=2000 比特)。換句話說:Base16使用兩個ASCII字符去編碼原數據中的一個字節數據。
值 | 編碼 | 值 | 編碼 |
---|---|---|---|
0 | 0 | 8 | 8 |
1 | 1 | 9 | 9 |
2 | 2 | 10 | A |
3 | 3 | 11 | B |
4 | 4 | 12 | C |
5 | 5 | 13 | D |
6 | 6 | 14 | E |
7 | 7 | 15 | F |
Base16編碼是一個標準的十六進制字符串(注意是字符串而不是數值),更易被人類和計算機使用,因爲它並不包含任何控制字符,以及Base64和Base32中的“=”符號。輸入的非ASCII字符,使用UTF-8字符集。
解密
import base64
s = 'key' # 要加密的字符串
a = base64.b16encode(s) # 加密
print a
print base64.b16decode(a) # 解密
在線網站 : https://www.qqxiuzi.cn/bianma/base.php
參考鏈接:
http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html