Chinaunix
标题:
[转]奇妙的Base64编码
[打印本页]
作者:
huaihe0410
时间:
2009-11-26 12:55
标题:
[转]奇妙的Base64编码
各位看官应该都是资深的网虫了,小弟斗胆在此问问大家,平时上网时,除了泡MM、到论坛灌水、扔版砖……之外,进行的最多的是什么活动?对了,你一定会说:是收发电子邮件!(谁敢说自己没收/发过电子邮件的?拉出去枪毙了!!)
收/发E-mail的时候有一个安全性的问题——假想一下,你花了一整天时间给系花写的情书,在发送的过程中被隔壁宿舍张三那小子截获了(难道他是黑客??),更糟的是他是你的情敌啊……天,后果不堪设想!!因此,我们必须有一种比较可靠的加密方法,能够对电子邮件的明文进行转换,至少要得出一个无法被别人一眼就看出内容来的东西,而且编码/解码的速度还要足够快。(这时你可以再假想一下啦,张三那家伙截获了你的肉麻情书,可是他一看:“咦?怎么乱七八糟的?垃圾邮件!!”——这样一来你不就逃过大难了?!)
Base64就是在这种背景下产生的加密方法。它的特点是:1、速度非常快。2、能够将字符串A转换成字符串B,而且如果你光看字符串B,是绝对猜不出字符串A的内容来的。不信吗?让我们来看看下面这串东西:
注:如果是基于以上两点,那么我们使用最简单的恺撒法即可,为什么Base64看起来要比恺撒法复杂呢?这是因为在Email的传送过程中,由于历史原因,Email只被允许传送ASCII字符,即一个8位字节的低7位。因此,如果您发送了一封带有非ASCII字符(即字节的最高位是1)的Email通过有“历史问题”的网关时就可能会出现问题。网关可能会把最高位置为0!很明显,问题就这样产生了!因此,为了能够正常的传送Email,这个问题就必须考虑!所以,单单靠改变字母的位置的恺撒之类的方案也就不行了。关于这一点可以参考RFC2046。
基于以上的一些主要原因产生了Base64编码。
xOO6w6Osu7bTrbniwdnAz8LetcTnzbfXzOy12KOh
呵呵,是什么啊?猜出来了吗?其实它就是下面这段文字经过Base64编码产生的东东:
你好,欢迎光临老罗的缤纷天地!
介绍说完啦,让我们开始探讨实质性的东西。
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。
这样说会不会太抽象了?不怕,我们来看一个例子:
转换前
aaaaaabb
ccccdddd
eeffffff
转换后
00aaaaaa
00bbcccc
00ddddee
00ffffff
应该很清楚了吧?上面的三个字节是原文,下面的四个字节是转换后的Base64编码,其前两位均为0。
转换后,我们用一个码表来得到我们想要的字符串(也就是最终的Base64编码),这个表是这样的:(摘自RFC2045)
Table 1: The Base64 Alphabet
Value Encoding Value Encoding Value Encoding Value Encoding
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 (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
让我们再来看一个实际的例子,加深印象!
转换前
10101101
10111010
01110110
转换后
00101011
00011011
00101001
00110110
十进制
43
27
41
54
对应码表中的值
r
b
p
2
所以上面的24位编码,编码后的Base64值为 rbp2
解码同理,把 rbq2 的二进制位连接上再重组得到三个8位值,得出原码。
(解码只是编码的逆过程,在此我就不多说了,另外有关MIME的RFC还是有很多的,如果需要详细情况请自行查找。)
用更接近于编程的思维来说,编码的过程是这样的:
第一个字符通过右移2位获得第一个目标字符的Base64表位置,根据这个数值取到表上相应的字符,就是第一个目标字符。
然后将第一个字符左移4位加上第二个字符右移4位,即获得第二个目标字符。
再将第二个字符左移2位加上第三个字符右移6位,获得第三个目标字符。
最后取第三个字符的右6位即获得第四个目标字符。
在以上的每一个步骤之后,再把结果与 0x3F 进行 AND 位操作,就可以得到编码后的字符了。
(感谢 Athena
指出以上描述中原有的一些错误!^_^)
So easy! That’s all!!!
可是等等……聪明的你可能会问到,原文的字节数量应该是3的倍数啊,如果这个条件不能满足的话,那该怎么办呢?
我们的解决办法是这样的:原文的字节不够的地方可以用全0来补足,转换时Base64编码用=号来代替。这就是为什么有些Base64编码会以一个或两个等号结束的原因,但等号最多只有两个。因为:
余数 = 原文字节数 MOD 3
所以余数任何情况下都只可能是0,1,2这三个数中的一个。如果余数是0的话,就表示原文字节数正好是3的倍数(最理想的情况啦)。如果是1的话,为了让Base64编码是4的倍数,就要补2个等号;同理,如果是2的话,就要补1个等号。
讲到这里,大伙儿应该全明白了吧?如果还有不清楚的话就返回去再仔细看看,其实不难理解的。
下面我给出一个演示Base64编码/解码的程序,希望能对您有用。同时也希望您帮我完善它,利用它做出更多的用途,到时别忘了通知我一声啊!(我现在太忙了)
DLL的源代码:Base64Dll.asm
;***********************************************
;程序名称:演示Base64编码/解码原理
;作者:罗聪
;日期:2002-9-14
;出处:http://laoluoc.yeah.net(老罗的缤纷天地)
;注意事项:如欲转载,请保持本程序的完整,并注明:
;转载自“老罗的缤纷天地”(http://laoluoc.yeah.net)
;***********************************************
.
386
.
model
flat
,
stdcall
option
casemap
:
none
include
\masm32\include\windows.inc
include
\masm32\include\kernel32.inc
include
\masm32\include\user32.inc
includelib
\masm32\lib\kernel32.lib
includelib
\masm32\lib\user32.lib
DllEntry
proto
:
HINSTANCE
,
:
DWORD
,
:
DWORD
Base64Encode
proto
:
DWORD
,
:
DWORD
Base64Decode
proto
:
DWORD
,
:
DWORD
.
data
;Base64 -> ASCII mapping table
base64_alphabet
db
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
;ASCII -> Base64 mapping table
base64table
db
43
dup
(
255
)
db
62
,
255
,
255
,
255
,
63
,
52
,
53
,
54
,
55
,
56
,
57
,
58
,
59
,
60
,
61
,
255
db
255
,
255
,
0
,
255
,
255
,
255
,
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
db
14
,
15
,
16
,
17
,
18
,
19
,
20
,
21
,
22
,
23
,
24
,
25
,
255
,
255
,
255
,
255
db
255
,
255
,
26
,
27
,
28
,
29
,
30
,
31
,
32
,
33
,
34
,
35
,
36
,
37
,
38
db
39
,
40
,
41
,
42
,
43
,
44
,
45
,
46
,
47
,
48
,
49
,
50
,
51
db
132
dup
(
255
)
.
code
DllEntry
proc
hInst
:
HINSTANCE
,
reason
:
DWORD
,
reserved1
:
DWORD
mov
eax
,
TRUE
ret
DllEntry
endp
;**********************************************************
;函数功能:进行Base64编码
;参数:
; source = 传入的字符串
; destination = 返回的编码
;**********************************************************
Base64Encode
proc
uses
ebx
edi
esi
source
:
DWORD
,
destination
:
DWORD
LOCAL
sourcelen
:
DWORD
invoke
lstrlen
,
source
mov
sourcelen
,
eax
mov
esi
,
source
mov
edi
,
destination
@@base64loop
:
xor
eax
,
eax
.
if
sourcelen
=
=
1
lodsb
;source ptr + 1
mov
ecx
,
2
;bytes to output = 2
mov
edx
,
03D3Dh
;padding = 2 byte
dec
sourcelen
;length - 1
.
elseif
sourcelen
=
=
2
lodsw
;source ptr + 2
mov
ecx
,
3
;bytes to output = 3
mov
edx
,
03Dh
;padding = 1 byte
sub
sourcelen
,
2
;length - 2
.
else
lodsd
mov
ecx
,
4
;bytes to output = 4
xor
edx
,
edx
;padding = 0 byte
dec
esi
;source ptr + 3 (+4-1)
sub
sourcelen
,
3
;length - 3
.
endif
xchg
al
,
ah
;flip eax completely
rol
eax
,
16
;can this be done faster
xchg
al
,
ah
@@
:
push
eax
and
eax
,
0FC000000h
;get the last 6 high bits
rol
eax
,
6
;rotate them into al
mov
al
,
byte
ptr
[
offset
base64_alphabet
+
eax
]
;get encode character
stosb
;write to destination
pop
eax
shl
eax
,
6
;shift left 6 bits
dec
ecx
jnz
@B
;loop
cmp
sourcelen
,
0
jnz
@@base64loop
;main loop
mov
eax
,
edx
;add padding and null terminate
stosd
ret
Base64Encode
endp
;**********************************************************
;函数功能:进行Base64解码
;参数:
; source = 传入的编码
; destination = 返回的字符串
;**********************************************************
Base64Decode
proc
uses
ebx
edi
esi
source
:
DWORD
,
destination
:
DWORD
LOCAL
sourcelen
:
DWORD
invoke
lstrlen
,
source
mov
sourcelen
,
eax
mov
esi
,
source
;esi
mov
edi
,
destination
;edi
mov
ecx
,
sourcelen
shr
ecx
,
2
cld
;-------------[decoding part]---------------
@@outer_loop
:
push
ecx
mov
ecx
,
4
xor
ebx
,
ebx
lodsd
@@inner_loop
:
push
eax
and
eax
,
0ffh
mov
al
,
byte
ptr
[
offset
base64table
+
eax
]
cmp
al
,
255
je
@@invalid_char
shl
ebx
,
6
or
bl
,
al
pop
eax
shr
eax
,
8
dec
ecx
jnz
@@inner_loop
mov
eax
,
ebx
shl
eax
,
8
xchg
ah
,
al
ror
eax
,
16
xchg
ah
,
al
stosd
dec
edi
pop
ecx
dec
ecx
jnz
@@outer_loop
xor
eax
,
eax
jmp
@@decode_done
;-------------------------------------------
@@invalid_char
:
mov
eax
,
-
1
@@decode_done
:
ret
Base64Decode
ENDP
end
DllEntry
;******************** over ********************
;by LC
测试程序:base64.asm
;***********************************************
;程序名称:演示Base64编码/解码原理
;作者:罗聪
;日期:2002-9-14
;出处:http://laoluoc.yeah.net(老罗的缤纷天地)
;注意事项:如欲转载,请保持本程序的完整,并注明:
;转载自“老罗的缤纷天地”(http://laoluoc.yeah.net)
;***********************************************
.
386
.
model
flat
,
stdcall
option
casemap
:
none
include
\masm32\include\windows.inc
include
\masm32\include\kernel32.inc
include
\masm32\include\user32.inc
include
Base64Dll.inc
includelib
\masm32\lib\kernel32.lib
includelib
\masm32\lib\user32.lib
includelib
Base64Dll.lib
WndProc
proto
:
DWORD
,
:
DWORD
,
:
DWORD
,
:
DWORD
.
const
IDC_BUTTON_ENCODE
equ
3000
IDC_BUTTON_DECODE
equ
3001
IDC_EDIT_INPUT
equ
3002
MAXSIZE
equ
260
.
data
szDlgName
db
"lc_dialog"
,
0
szCaption
db
"BASE64 demo by LC"
,
0
szBuffer
db
255
dup
(
0
)
szText
db
340
dup
(
0
)
szMsg
db
450
dup
(
0
)
szTemplate_Encode
db
"字符串 ""%s"" 的Base64编码是:"
,
13
,
10
,
13
,
10
,
"%s"
,
0
szTemplate_Decode
db
"编码 ""%s"" 经过Base64还原后的字符串是:"
,
13
,
10
,
13
,
10
,
"%s"
,
0
.
code
main
:
invoke
GetModuleHandle
,
NULL
invoke
DialogBoxParam
,
eax
,
offset
szDlgName
,
0
,
WndProc
,
0
invoke
ExitProcess
,
eax
WndProc
proc
uses
edi
hWnd
:
HWND
,
uMsg
:
UINT
,
wParam
:
WPARAM
,
lParam
:
LPARAM
LOCAL
hEdit
:
HWND
.
if
uMsg
=
=
WM_CLOSE
invoke
EndDialog
,
hWnd
,
0
.
elseif
uMsg
=
=
WM_COMMAND
mov
eax
,
wParam
mov
edx
,
eax
shr
edx
,
16
movzx
eax
,
ax
.
if
edx
=
=
BN_CLICKED
.
if
eax
=
=
IDCANCEL
invoke
EndDialog
,
hWnd
,
NULL
.
elseif
eax
=
=
IDC_BUTTON_ENCODE
|
|
eax
=
=
IDOK
;取得用户输入的字符串:
invoke
GetDlgItemText
,
hWnd
,
IDC_EDIT_INPUT
,
addr
szBuffer
,
255
;进行 ASCII->Base64 转换:
invoke
Base64Encode
,
addr
szBuffer
,
addr
szText
;格式化输出:
invoke
wsprintf
,
addr
szMsg
,
addr
szTemplate_Encode
,
addr
szBuffer
,
addr
szText
;显示结果:
invoke
MessageBox
,
hWnd
,
addr
szMsg
,
addr
szCaption
,
MB_OK
.
elseif
eax
=
=
IDC_BUTTON_DECODE
;取得用户输入的字符串:
invoke
GetDlgItemText
,
hWnd
,
IDC_EDIT_INPUT
,
addr
szBuffer
,
255
;进行 Base64->ASCII 转换:
invoke
Base64Decode
,
addr
szBuffer
,
addr
szText
;格式化输出:
invoke
wsprintf
,
addr
szMsg
,
addr
szTemplate_Decode
,
addr
szBuffer
,
addr
szText
;显示结果:
invoke
MessageBox
,
hWnd
,
addr
szMsg
,
addr
szCaption
,
MB_OK
.
endif
;全选edit里面的内容:
invoke
GetDlgItem
,
hWnd
,
IDC_EDIT_INPUT
invoke
SendMessage
,
eax
,
EM_SETSEL
,
0
,
-
1
.
endif
.
else
mov
eax
,
FALSE
ret
.
endif
mov
eax
,
TRUE
ret
WndProc
endp
end
main
;******************** over ********************
;by LC
测试程序的资源文件:base64.rc
#include "resource.h"
#define IDC_BUTTON_ENCODE 3000
#define IDC_BUTTON_DECODE 3001
#define IDC_EDIT_INPUT 3002
#define IDC_STATIC -1
LC_DIALOG DIALOGEX 10, 10, 195, 60
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
CAPTION "Base64 demo by LC"
FONT 9, "宋体", 0, 0, 0x0
BEGIN
LTEXT "请输入字符串:", IDC_STATIC, 11, 7, 130, 10
EDITTEXT IDC_EDIT_INPUT, 11, 20, 173, 12, ES_AUTOHSCROLL
DEFPUSHBUTTON "编码(&E)", IDC_BUTTON_ENCODE, 38, 39, 52, 15
PUSHBUTTON "解码(&D)", IDC_BUTTON_DECODE, 104, 39, 52, 15
END
如果你发现了有bug,一定要告诉我啊,并请来信讨论!
[email=lcother@163.net?subject=老罗,有关Base64的问题想跟你讨论!]
lcother@163.net
[/email]
最后给大家留下一个小小的习题,你知道下面这串Base64编码的原文是什么吗? :)
0LvQu8T6xM3XxdDU19O/tM3qztK1xEJhc2U2NL3Ms8yjoSCjuqOp
本文来自ChinaUnix博客,如果查看原文请点:
http://blog.chinaunix.net/u2/60332/showart_2104479.html
欢迎光临 Chinaunix (http://bbs.chinaunix.net/)
Powered by Discuz! X3.2