密码学 January 03, 2020

Base64加密算法小研究

Words count 28k Reading time 25 mins. Read count 0

1、先了解一波什么是Base64编码的加密与解密:

1、Base64使用A–Z,a–z,0–9,+,/ 这64个字符.

2、编码原理:将3个字节转换成4个字节( (3 X 8) = 24 = (4 X 6) )先读入3个字节,每读一个字节,左移8位,再右移四次,每次6位,这样就有4个字节了.

3、解码原理:将4个字节转换成3个字节.先读入4个6位(用或运算),每次左移6位,再右移3次,每次8位.这样就还原了.

Base64是一种很常见的编码规范,其作用是将二进制序列转换为人类可读的ASCII字符序列,常用在需用通过文本协议(比如HTTP和SMTP)来传输二进制数据的情况下。

先来看看base64的表:

image.png

由于base64编码是将编码前的3*8位数据,分解成4个6位的数据,所以经过base64编码后的字符串长度是4的倍数。但往往我们进行编码的数据长度并不是3的倍数,这就造成了“编码”后的位数不为4的倍数,比如Brisk共5×8=40位,以6位为一组可以分为7组,这样“编码”后就有7个字符,但base64编码后的字符长度应该是4的倍数,显然这里就出问题了,那么怎么办呢?前面的不可以抛弃掉,所以就只有“追加”了,所以Brisk经过base64编码后的长度应该是8个字符,而第8个编码后的字符是’=’,再比如对单个字符a进行base64编码,由于它的长度不是3的倍数,以3个字节为一组它只能分一组,再以6位为一位它只能分两组,所以经过“编码”后它的长度是2,但base64编码后的个数应该是4的倍数,所以它的长度应该是4,所以在后面补上两个‘=’,由于一个数求余3后有三个不同的结果,0、1、2,所以在对一个数据进行base64进行编码后它的长度为:

接着我们以brisk为例子进行一次演算去还原这个过程:

(1)当进行编码的数据长度是3的倍数时,len=strlen(str_in)/34;
(2)当进行编码的数据长度不是3的倍数时,len=(strlen(str_in)/3+1)
4;

我们以Brisk这个例子来说明一下base64编码的过程。首先我们以3个字符为一组将Brisk进行分组,Brisk被分为两组:Bri 和 sk;然后我们取出这两个分组中每个字节的ASCII码:

B:66 r:114 i:105 s:115 k:107。它们对应的二进制数为

B:01000010 r:01110010 i:01101001 s:01110011 k:01101011;

第一组,我们以6位为一组对每一个3字节分组进行再分组就变成了010000 100111 001001 101001。所对应的十进制数是16 39 9 41,对应base64表中的结果是 Q n J p;

第二组,011100 110110 101100(不够补0),所以对应的十进制数是 28 54 44,对应base64表中的结果是 c 2 s,最终结果为QnJpc2s=(因为第二组“编码”后只有三个字节)。

解码的过程是一个逆过程,我们将经过编码后的字符按4个字符为一组,然后对照base64表得到相应的十进制数,再将其通过拆分和组合,组成3个8位数据,这个数据就是解码后的数据。

Q: 16 n:39 J:9 p:41 ——> 010000 100111 001001 101001

6转8:01000010 01110010 01101001 —–>Bri

c: 28 2:54 s:44 —–> 011100 110110 101100

6转8:01110011 01101011 —–>sk(末尾的0本来就是用来补足的,所以可省略)

所以如果有=,在逆运算时是可以忽略=的。

2、好了接下来搞个C的源代码分析:

首先是我们的.h头文件:

1
2
3
4
5
6
7
8
9
10
11
12
/*base64.h*/  
#ifndef _BASE64_H
#define _BASE64_H

#include <stdlib.h>
#include <string.h>

unsigned char *base64_encode(unsigned char *str);

unsigned char *bae64_decode(unsigned char *code);

#endif

其次是我们的.c文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*base64.c*/  
#include "base64.h"

unsigned char *base64_encode(unsigned char *str)
{
long len;
long str_len;
unsigned char *res;
int i,j;
//定义base64编码表
unsigned char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

//计算经过base64编码后的字符串长度
str_len=strlen(str);
if(str_len % 3 == 0)
len=str_len/3*4;
else
len=(str_len/3+1)*4;

res=malloc(sizeof(unsigned char)*len+1);
res[len]='\0';

//以3个8位字符为一组进行编码
for(i=0,j=0;i<len-2;j+=3,i+=4)
{
res[i]=base64_table[str[j]>>2]; //取出第一个字符的前6位并找出对应的结果字符
res[i+1]=base64_table[(str[j]&0x3)<<4 | (str[j+1]>>4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符
res[i+2]=base64_table[(str[j+1]&0xf)<<2 | (str[j+2]>>6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符
res[i+3]=base64_table[str[j+2]&0x3f]; //取出第三个字符的后6位并找出结果字符
}

switch(str_len % 3)
{
case 1:
res[i-2]='=';
res[i-1]='=';
break;
case 2:
res[i-1]='=';
break;
}

return res;
}

unsigned char *base64_decode(unsigned char *code)
{
//根据base64表,以字符找到对应的十进制数据
int table[]={0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long len;
long str_len;
unsigned char *res;
int i,j;

//计算解码后的字符串长度
len=strlen(code);
//判断编码后的字符串后是否有=
if(strstr(code,"=="))
str_len=len/4*3-2;
else if(strstr(code,"="))
str_len=len/4*3-1;
else
str_len=len/4*3;

res=malloc(sizeof(unsigned char)*str_len+1);
res[str_len]='\0';

//以4个字符为一位进行解码
for(i=0,j=0;i < len-2;j+=3,i+=4)
{
res[j]=((unsigned char)table[code[i]])<<2 | (((unsigned char)table[code[i+1]])>>4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j+1]=(((unsigned char)table[code[i+1]])<<4) | (((unsigned char)table[code[i+2]])>>2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j+2]=(((unsigned char)table[code[i+2]])<<6) | ((unsigned char)table[code[i+3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}

return res;

}

这样就完成了base64算法的解析了。

最后就是python实现的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-
from Crypto.Cipher import ARC4, AES, DES
from Crypto.Util.Padding import unpad
from binascii import *
import base64
import re
import hashlib
from z3 import *
from gmpy2 import *
import libnum
from Crypto.Util.number import *
import base64

table1 = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/','=']
table2 = [0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3E, 0x3F, 0x3C, 0x3D, 0x3A, 0x3B, 0x38, 0x39, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2E, 0x2F, 0x2C, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1E, 0x1F, 0x1C, 0x1D, 0x1A, 0x1B, 0x18, 0x19, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, 0x0E, 0x0F, 0x0C, 0x46, 0x47, 0x44, 0x45, 0x42, 0x43, 0x40, 0x41, 0x4E, 0x4F, 0x5D,0x59]

# Base64
c = [4, 19, 0, 19, 4, 5, 19, 93]
for i in c:
code += table1[table2.index(i)]
print base64.b64decode(code).encode("hex")

这是一个关于换表后的操作,其实也很简单,本质是来说base64是类似于单字节加密模式,也就是点对点,所以爆破是可行的操作,也就是字符串之间是互不影响的,之前做过手动爆破的题目~

0%