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加密就会节约很多时间。
索引表如下
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
---|---|---|---|---|---|---|---|
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
#include
// 全局常量定义
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编码是使用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具有许多优点:
Base32也比Base16有优势:
Base32的缺点:
import base64
s = 'key' # 要加密的字符串
a = base64.b32encode(s) # 加密
print a
print base64.b32decode(a) # 解密
在线网站 : https://www.qqxiuzi.cn/bianma/base.php
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
https://blog.csdn.net/u011491972/article/details/52800177