本文主要记录base64原理及base64隐写的原理。

base64加密

base64 是一种编码方式, 是一种可逆的编码方式

编码后的数据使用64个可打印ASCII字符(A-Z、a-z、0-9、+、/)将任意字节序列数据编码成ASCII字符串

一共64个字符:26+26+10+1+1=64,外加“=”符号用作后缀用途。

因为2的6次方等于64,所以需要6位二进制来表示,表示数值从0到63,对应上表的64个数值。

加密过程
1
2
3
4
5
1.首先将待编码的内容转换成8位二进制,每3个字符为一组
2.如果编码前的长度是3n+1,编码后的内容最后面补上2个 ‘=’,如果编码前的长度是3n+2,编码后的内容最后面补上1个 ‘=’
3.再将每一组的二进制内容拆分成6位的二进制,不足6位的后面补足0
4.每个6进制的数字前面补足0,保证变成8位二进制
5.将补足后的内容根据base64编码表转换成base64内容输出

首先是将明文字符串转换为二进制的形式,此处建议明文字符串使用ASCII字符,防止后期界面的时候不可打印

在加密过程中有两种情况:

  • 明文长度是三的倍数
  • 明文不是三的倍数
明文字符串长度是三的倍数

用len作为输入的明文

首先需要将len字符串转换为二进制的形式(此处需要在不足8位的前面补充0,以便凑满8个二进制位)

1
2
3
4
ASCII字符		   对应的二进制值
l 01101100
e 01100101
n 01101110

将二进制组合成一个字符串:011011000110010101101110,可以看到正好是3*8=24位

然后按照6位一分可以正好分成24/6=4份,所以最后的密文的长度是4

将4个字符整理出对应的10进制,然后根据10进制的值去表格中找对应的字符的值,然后拼接形成密文bGVu

1
2
3
4
5
6
011011+000110+010101+101110
二进制 十进制 base64表中对应的字符
011011 27 b
000110 6 G
010101 21 V
101110 46 u

即len经过base64加密之后的密文是bGVu

根据站长工具验证,上述结论正确

明文不是三的倍数

当明文不是三个倍数时,明文转成二进制会导致后面不能够被6整除有可能,于是需要涉及填充字符

将Valen作为明文,首先将明文转成二进制

1
2
3
4
5
6
ASCII字符		   对应的二进制值
V 01010110
a 01100001
l 01101100
e 01100101
n 01101110

将二进制组合成一个字符串:0101011001100001011011000110010101101110,可以看到长度是5*8=24位

按照6位是不能全部分开,可以看到最后一个不足6位,但是有4位,所以我们需要补两个0上去

1
2
3
4
5
6
7
8
9
010101+100110+000101+101100+011001+010110+1110
二进制 十进制 base64表中对应的字符
010101 21 V
100110 38 m
000101 5 F
101100 44 s
011001 25 Z
010110 22 W
111000 56 4

可以看到最后一位在后面填充了两个0,此时的密文信息为:VmFsZW4

因为填充了两个0所以需要补充一个等号在密文后面,要是补充了四个0则需要补充两个等号,所以密文最后最多只有两个等号。

即Valen经过base64加密之后的密文是VmFsZW4=

根据站长工具验证,上述结论正确

结论

1.编码后的数据比原始数据略长,为原来的4/3

2.只含有65种字符,大写的A至Z,小写的a至z,数字0到9,以及3种符号+/ =,=最多两个,且在末尾

代码实现

转换步骤

1
2
3
4
5
6
7
第一步		将待转换的字符串分割成一个个字符
第二步 计算每一个字符对应的ASCII码对应的二进制,若不足8位,在前面添加0进行补全
第三步 将得到的所有字符的二进制值,按照6个6个一组划分,若不能整除6,在最末添加0补足6位
第四步 计算6个划分后的二进制值的十进制值,不足八位补0
第五步 按照base64表,查看对应的字符
第六步 根据补0的个数在后面填充=号,两个0对应一个=,四个0对应两个=
第七步 得到最终结果

下面是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
26
27
28
29
30
31
32
33
34
35
import math

# base64所有的字典信息
str_dict = {'0': 'A', '1': 'B', '2': 'C', '3': 'D', '4': 'E', '5': 'F', '6': 'G', '7': 'H', '8': 'I', '9': 'J','10': 'K', '11': 'L', '12': 'M', '13': 'N', '14': 'O', '15': 'P', '16': 'Q', '17': 'R', '18': 'S','19': 'T', '20': 'U', '21': 'V', '22': 'W', '23': 'X', '24': 'Y', '25': 'Z', '26': 'a', '27': 'b','28': 'c', '29': 'd', '30': 'e', '31': 'f', '32': 'g', '33': 'h', '34': 'i', '35': 'j', '36': 'k','37': 'l', '38': 'm', '39': 'n', '40': 'o', '41': 'p', '42': 'q', '43': 'r', '44': 's', '45': 't','46': 'u', '47': 'v', '48': 'w', '49': 'x', '50': 'y', '51': 'z', '52': '0', '53': '1', '54': '2','55': '3', '56': '4', '57': '5', '58': '6', '59': '7', '60': '8', '61': '9', '62': '+', '63': '/'}
mingwen = 'valen' # 此处十要加密的字符串内容
print('明文:' + mingwen)
two = ''
for m in mingwen:
# two.join(bin(ord(m)).replace('0b',''))
mingwen_two = bin(ord(m)).replace('0b', '') # 将字符转成二进制的形式
if len(mingwen_two) < 8: # 8位二进制形成一个accii码值
mingwen_two = '0' * (8 - len(mingwen_two)) + mingwen_two # 位数不足的前面补0,补到8位
two = two + mingwen_two # 把所有的二进制都放在一个字符串里面,以便后期切割
print(mingwen_two)
print(m)
print('明文的二进制形式:' + two) # 字符对应ASCII转换成八位二进制的合并
two_len = (len(two) / 6) # 将明文的二进制形式按6位一分统计可以分成多少分,后期需要使用者这个数字
# print(two_len)
two_len = math.ceil(two_len) # math.ceil用于向上取整小数
print('向上取整个数:{0}'.format(two_len))
miwen = ''
add_num = 0
for i in range(two_len): # base64的基础单位是 3*8bit的二进制,若是不够3*8bit则在后面添加0字节(padding)直至满足
check = two[6 * i:6 + 6 * i]
if len(check) < 6:#如果不是6位,就在后面补充多个0
add_num = 6 - len(check)#记录补充多少个0
# print('填充字符数量{0}'.format(6-len(check)))
check = check + (str(0) * (6 - len(check)))#补0操作
print(check)
# print(int(check, 2))#二进制转成10进制
print(str_dict['{0}'.format(int(check, 2))]) # 从字典中取出对应的值
miwen += str_dict['{0}'.format(int(check, 2))]#字典中的值对应组成密文信息
if add_num != 0:
miwen += '=' * int(add_num / 2)#根据前面补充0的数量除以2的整数,去补充若干个=,最多不超过两个=
print('密文' + miwen)

base64解密

解密过程

就是加密过程的逆序

1
2
3
4
5
1.根据密文信息从字典中取出对应的键
2.将字符信息转成二进制形式,需要填满6位,等号因为没有就不会被转成二进制
3.根据等号切割二进制位,将所有的二进制分成8位一份的十进制
4.根据得到的十进制去找对应的ASCII字符
5.拼接ASCII字符完成解密

首先密文是dmFsZW4=,切割后得到dmFsZW4,然后得到每个字符在字典中的键,根据键转为二进制

1
2
3
4
5
6
7
8
密文中的字符		字典对应的键值			将键值转成二进制的形式
d 29 011101
m 38 100110
F 5 000101
s 44 101100
Z 25 011001
W 22 010110
4 56 111000

然后将二进制形式合并成一个串:011101100110000101101100011001010110111000,此时为长度: 6*7=42

根据密文可以知道有一个等号于是删除末尾的两个0,得到二进制串0111011001100001011011000110010101101110,长度40正好是8的倍数

1
2
3
4
5
6
7
01110110+01100001+01101100+01100101+01101110
8位二进制 二进制对应的十进制值 十进制对应ASCII值
01110110 118 v
01100001 97 a
01101100 108 l
01100101 101 e
01101110 110 n

拼接得到解密后的字符串valen

根据站长工具验证,上述结论正确

代码实现
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
str_dict = {'0': 'A', '1': 'B', '2': 'C', '3': 'D', '4': 'E', '5': 'F', '6': 'G', '7': 'H', '8': 'I', '9': 'J',
'10': 'K', '11': 'L', '12': 'M', '13': 'N', '14': 'O', '15': 'P', '16': 'Q', '17': 'R', '18': 'S',
'19': 'T', '20': 'U', '21': 'V', '22': 'W', '23': 'X', '24': 'Y', '25': 'Z', '26': 'a', '27': 'b',
'28': 'c', '29': 'd', '30': 'e', '31': 'f', '32': 'g', '33': 'h', '34': 'i', '35': 'j', '36': 'k',
'37': 'l', '38': 'm', '39': 'n', '40': 'o', '41': 'p', '42': 'q', '43': 'r', '44': 's', '45': 't',
'46': 'u', '47': 'v', '48': 'w', '49': 'x', '50': 'y', '51': 'z', '52': '0', '53': '1', '54': '2',
'55': '3', '56': '4', '57': '5', '58': '6', '59': '7', '60': '8', '61': '9', '62': '+', '63': '/'}
two=''
miwen='dmFsZW4='
print('密文:'+miwen)
#根据密文中的等号信息切割补充的0的数量
if '==' in miwen:
miwen=miwen[:-2]
elif '=' in miwen:
miwen=miwen[:-1]
for char in miwen:
for k, v in str_dict.items():
if v == char:#根据密文信息从字典中取出对应的键
mingwen_two = bin(int(k,10)).replace('0b', '') # 将字符转成二进制的形式
print(mingwen_two.zfill(6))#填充满6位字符
print(k)#键
print(char)
two=two+mingwen_two.zfill(6)
print('密文的二进制:'+two)
print('密码目前长度:',int(len(two)))#要弄成8的倍数
ten=''
yuanwen=''
for i in range(int(len(two)/8)):#8位一分,挨个处理对应的字符串的值,并将其转成十进制,然后根据十进制转成字符信息
print(two[8*i:8+8*i])
ten=int(two[8*i:8+8*i], 2) # 二进制转成10进制
print(ten)
print(chr(ten))
yuanwen+=chr(ten)
print('明文'+yuanwen)#输出解码后的信息

base64隐写

根据上面的信息可以知道valen对应的base64密文是dmFsZW4=

运行下面的程序

1
2
3
4
5
6
import base64
b = ''
for i in range(4):
b = 'dmFsZW4' + chr(52 + i) + '='
print(b)
print(base64.b64decode(b))

运行结果

1
2
3
4
dmFsZW4=		b'valen'
dmFsZW5= b'valen'
dmFsZW6= b'valen'
dmFsZW7= b'valen'

可以看到多个密文能够对应同一个字符串,这是因为在解密的时候会删除后面几个跟等号数量有关的二进制位,所以会导致不同的密文也能对应同一个明文。

并且删除的二进制位指挥在最后一个字符,并且base最多只有两个等号,所以一共可以有2*2个隐写位可以写,而且并不会影响真实的加密信息。

利用这个想法于是就有了base64隐写

实现过程

读取出一行被加密后二进制信息,目前是base64加密的状态,保存为steg_line

然后将steg_line进行base64解密,然后再去进行加密,得到norm_line,这样是消除伪加密

然后根据steg_line和norm_line进行对比,返回两个字符串的绝对值

然后根据等号的个数pads_num和绝对值diff来对bin_str进行不同的操作

  • 绝对值为0,等号个数为2,此时需要加两个00在bin_str后面
  • 绝对值为0,等号个数为1,此时需要加一个00在bin_str后面
  • 绝对值不为0,需要将绝对值diff转成二进制,同时在前面补0需要补到2*等号个数pads_num的数量上

然后将所有的bin_str进行二进制转成ASCII值得到真的密文

代码实现

base隐写解密脚本

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
#base64隐写
import base64

def get_base64_diff_value(s1, s2):
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
res = 0
for i in range(len(s2)):
if s1[i] != s2[i]:
return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))
return res


def solve_stego():
with open('1.txt') as f:
file_lines = f.readlines()
bin_str = ''
for line in file_lines:
steg_line = line.replace('\n', '')
# norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n','')
norm_line = line.replace('\n', '').encode('utf-8')
norm_line = base64.b64decode(norm_line).decode('utf-8')

norm_line = base64.b64encode(norm_line.encode('utf-8')).decode('utf-8')
norm_line = norm_line.replace('\n', '')

diff = get_base64_diff_value(steg_line, norm_line)
print(diff)
pads_num = steg_line.count('=')
if diff:
bin_str += bin(diff)[2:].zfill(pads_num * 2)
else:
bin_str += '0' * pads_num * 2
print(goflag(bin_str))


def goflag(bin_str):
res_str = ''
for i in range(0, len(bin_str), 8):
res_str += chr(int(bin_str[i:i + 8], 2))
return res_str


if __name__ == '__main__':
solve_stego()

实现过程

加密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import base64

flag = 'flag{valen}' #flag
bin_str = ''.join([bin(ord(c)).replace('0b', '').zfill(8) for c in flag])#字符串转成二进制
base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

with open('0.txt', 'r') as f0, open('1.txt', 'w') as f1: #'0.txt'是明文, '1.txt'用于存放隐写后的 base64
for line in f0.readlines():
rowstr = base64.b64encode(line.encode('utf-8')).decode('utf-8')#然后加密
equalnum = rowstr.count('=')

if equalnum and len(bin_str):
offset = int('0b'+bin_str[:equalnum * 2], 2)
char = rowstr[len(rowstr) - equalnum - 1]
rowstr = rowstr.replace(char, base64chars[base64chars.index(char) + offset])
bin_str = bin_str[equalnum*2:]

f1.write(rowstr + '\n')