首页 > 技术文章 > BASE64编解码

fgcs 2015-08-31 11:28 原文

由于历史原因,Email只被允许传送ASCII字符,即一个8位字节的低7位。这里的历史原因就在于第一封Email是由老美发出,当然字符编码也就只考虑美标ASCII码了。到后来,世界各地的人们都想使用便捷的“伊妹儿”,如果你发送了一封带有非 ASCII字符(即字节的最高位是1)的Email通过有历史问题的网关时就可能会出现问题。网关可能会把最高位置为0,这对于使用2字节或更多字节对文字进行编码的国家而言将不能实现正确的信息交流。为了能够使非美标ASCII编码的国家正常的传送Email,单单靠改变字母的位置的恺撒之类的方案也就不行了。这时,BASE64编码方案应运而生。

按照RFC2045的定义,Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)BASE64的主要思想是将输入的字符串或数据编码成只含有{ A - Z , a - z , 0 - 9 , + , / }这64个可打印字符的串,故称为“BASE64”。当然这种加密方式只能达到一眼望去看不出内容的效果,防君子不防小人。

BASE64是当今MIME邮件中常用的编码方式之一,除此之外,BASE64也是HTTP鉴权认证(Authentication)的编码方式之一,即Basic Access Authenticatio(另外一种是Digest Access Authentication)。具体的如果一个URL设置了访问权限(Authorization required for the requested URL),则客户端访问这个URL时,服务器将会发送HTTP/1.0 401 Unauthorized的响应头,客户端浏览器收到该响应后即弹出“连接到*.*.*.*”的登录密码框(User agents must take special care in parsing the WWW-Authenticate field:例如WWW-Authenticate: Basic realm="GeneralUser/Administrator")。其中realm(域)的值将出现在登录对话框中。用户输入的账户按照”用户名字符串:密码字符串”进行BASE64编码后才传送给服务器,服务器进行BASE64解码得到用户输入的用户名密码进行鉴权。

BASE64要求把每3个8 bit的字节转换为四个6 bit的字节(3*8 = 4*6 = 24),然后把6 bit高两位添0,组成4个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

BASE64编码的方法是将输入数据流每次取6 bit,用此6 bit的值(2^6=64,取值[0,63])作为索引去查表,输出相应的字符。这样,每3个字节将编码为4个可视字符(3×8 →4×6);不满4个字符的以‘=’填充。

原文的字节数量应该是3的倍数,当这个条件不能满足时,用全零字节补足,转化时BASE64编码用=号代替,这就是为什么有些BASE64编码以一个或两个等号结束的原因。具体的做法是:除3余1时,补两个“==”;除3余2时,补一个“=”。

转换后,用一个映射码表来查找相应的BASE64码,映射表如下:(摘自RFC2045)

索引

对应字符

索引

对应字符

索引

对应字符

索引

对应字符

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编码的规则

  ①.把3个字符变成4个字符

  ②每76个字符加一个换行符

  ③.最后的结束符也要处理

 

案例一:

转换前 10101101 10111010 01110110

转换后 00101011 00011011 00101001 00110110

十进制 43 27 41 54

对应码表中的值 r b p 2

所以上面的24位编码,编码后的Base64值为 rbp2

 

案例二:

转换前 logansoft:123456

转换前

0x6c 0x6f 0x67 0x61 0x6e 0x73 0x6f 0x66 0x74 0x3a 0x31 0x32 0x33 0x34 0x35 0x36

 

01101100 01101111 01100111 01100001 01101110 01110011 01101111 01100110 01110100 00111010 00110001 00110010 00110011 00110100 00110101 00110110

   

转换后

00011011 00000110 00111101 00100111 00011000 00010110 00111001 00110011 00011011 00110110 00011001 00110100 00001110 00100011 00000100 00110010 00001100 00110011 00010000 00110101 00001101 0010(0000)

   十进制

   27 6 61 39 24 22 57 51 27 54 25 52 14 35 4 50 12 51 16 53 13 32

总共16个字节,16/3=1,故末尾补“==”。查表得“logansoft:123456”的BASE64编码为“bG9nYW5zb2Z0OjEyMzQ1Ng==”,共24个BASE64编码。

解码是编码的逆过程,这里不再详述。

注意:Base64编码为“bG9nYW5zb2Z0OjEyMzQ1NgA=”、“bG9nYW5zb2Z0OjEyMzQ1NgB=” 、“bG9nYW5zb2Z0OjEyMzQ1NgC=” 、“bG9nYW5zb2Z0OjEyMzQ1NgD=”的源码也为“logansoft:123456”。

他们对应的4x6 BASE64索引数据为:

00011011 00000110 00111101 00100111 00011000 00010110 00111001 00110011 00011011 00110110 00011001 00110100 00001110 00100011 00000100 00110010 00001100 00110011 00010000 00110101 00001101 0010(0000)00000000(00000001, 00000010, 00000011)

第21个BASE64索引00001101与第22个BASE64索引0010(0000)进行“尾6中2”的组合还原出字符6(ASCII码为00110110)。接下来解码算法将进行“尾4中4” 的组合还原,第22个BASE64索引0010(0000)的尾四位为“0000”,若第23个BASE64索引的中四位(带下划线的蓝色部分)也为“0000”,则组合出字符NUL(ASCII码为00000000),刚好为C语言字符串的结尾标识符,因此不影响解码结果。

    总结编解码规则如下:

(1)编码(3x8à4x6):头6、尾2头4、尾4头2、尾6。

(2)解码(4x6à3x8):尾6中2、尾4中4、尾2尾6。

 

在下载软件中加密下载地址的原理

  先以“迅雷下载”为例: 很多下载类网站都提供“迅雷下载”的链接,其地址通常是加密的迅雷专用下载地址。

  如thunder://QUFodHRwOi8vd3d3LmJhaWR1LmNvbS9pbWcvc3NsbTFfbG9nby5naWZaWg==

  其实迅雷的“专用地址”也是用Base64加密的,其加密过程如下:

  一、在地址的前后分别添加AA和ZZ

  如www.baidu.com/img/sslm1_logo.gif变成

  AAwww.baidu.com/img/sslm1_logo.gifZZ

  二、对新的字符串进行Base64编码

  如AAwww.baidu.com/img/sslm1_logo.gifZZ用Base64编码得到

  QUFodHRwOi8vd3d3LmJhaWR1LmNvbS9pbWcvc3NsbTFfbG9nby5naWZaWg==

  三、在上面得到的字符串前加上“thunder://”就成了

  thunder://QUFodHRwOi8vd3d3LmJhaWR1LmNvbS9pbWcvc3NsbTFfbG9nby5naWZaWg==

  另:

Flashget的与迅雷类似,只不过在第一步时加的“料”不同罢了,Flashget在地址前后加的“料”是[FLASHGET]。而QQ旋风的干脆不加料,直接就对地址进行Base64编码了。

 

 

附:BASE64编解码C语言实现

 

 

 

  1. // BASE64.H  
  2. #ifndef _BASE64_H  
  3. #define _BASE64_H  
  4.   
  5. // #include <stdlib.h>  
  6.   
  7. // 编码映射  
  8. static const char Base64EncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";  
  9. char* Base64Encode(char const* orig);  
  10.   
  11. // 解码映射  
  12. static char Base64DecodeTable[256];  
  13. static void initBase64DecodeTable();  
  14. char* Base64Decode(char const* base64code);  
  15.   
  16. #endif  
  17.   
  18. // BASE64.CPP  
  19. //////////////////////////////////////////////////////////////////////////  
  20. #include <string.h>  
  21. #include <stdio.h>  
  22.   
  23. #include "base64.h"  
  24.   
  25. // "654321:123456" -- "NjU0MzIxOjEyMzQ1Ng=="  
  26. // "logansoft:123456" -- "bG9nYW5zb2Z0OjEyMzQ1Ng=="  
  27. // "ABC" -- "QUJD"  
  28. // "AB"  -- "QUI="  
  29. // "A"   -- "QQ=="  
  30.   
  31. // (1)未做76个字符的换行处理  
  32. // (2)未做汉字的特殊处理  
  33.   
  34. // BASE64编码  
  35. char* Base64Encode(char const* orig)  
  36. {  
  37.     size_t origLen = strlen(orig);  
  38.   
  39.     // (1)按比特计算,求算最终结果含多少为BASE64码  
  40.     int quotientBit = origLen * 8 / 6;  // BASE64码的个数  
  41.     int remainderBit = origLen * 8 % 6; // 3x8转化为x6的余位  
  42.     if (remainderBit) // 有余数  
  43.     {  
  44.         quotientBit++; // 不足字节的低位补零  
  45.     }  
  46.       
  47.     // (2)按字节计算,求算等号补位数  
  48.     int quotientByte = origLen / 3; // 3x8转化为x6的分组数  
  49.     int remainderByte = origLen %3; // 不足一组  
  50.     if (remainderByte) // 有余数  
  51.     {  
  52.         quotientByte++; // 补充一组  
  53.     }  
  54.     int cover  = 0; // 等号补充字节数  
  55.     if (remainderByte == 1)  
  56.     {  
  57.         cover = 2; // 补“==”  
  58.     }  
  59.     else if (remainderByte == 2)  
  60.     {  
  61.         cover = 1; // 补‘=’  
  62.     }  
  63.       
  64.     // (3)3x8 -> 4x6  
  65.     char* base64code = new char[quotientBit + cover + 1]; // 分配内存  
  66.     memset(base64code, 0, sizeof(base64code)); // 清零  
  67.       
  68.     int i, j;  
  69.     for (i = 0; i < quotientByte; i++)  
  70.     {  
  71.         for (j = 0; j < 4; j++)  
  72.         {  
  73.             // 处理最后多出的位(不足位)  
  74.             if (remainderBit && (4 * i + j + 1 == quotientBit)) // 不为  
  75.             {  
  76.                 base64code[4 * i + j] = (orig[origLen -1] << (6 - remainderBit)) & 0x3f;  
  77.                 break; // BASE64码转换完毕  
  78.             }  
  79.               
  80.             if (j == 0) // 头6  
  81.             {  
  82.                 base64code[4 * i + j] = (orig[i * 3] >> 2) & 0x3f;  
  83.             }  
  84.             else if (j == 1) // 尾2头4  
  85.             {                 
  86.                 base64code[4 * i + j] = ((orig[i * 3] & 0x03) << 4) |   
  87.                     ((orig[i * 3 + 1] & 0xf0) >> 4);  
  88.             }  
  89.             else if (j == 2) // 尾4头2  
  90.             {                 
  91.                 base64code[4 * i + j] = ((orig[i * 3 + 1] & 0x0f) << 2) |   
  92.                     ((orig[i * 3 + 2] & 0xc0) >> 6);  
  93.             }  
  94.             else if (j == 3) // 尾6  
  95.             {  
  96.                 base64code[4 * i + j] = orig[i * 3 + 2]  & 0x3f;  
  97.             }  
  98.         }  
  99.           
  100.         if (4 * i + j >= quotientBit)  
  101.         {  
  102.             break;  // 3x8->4x6转换完毕  
  103.         }  
  104.     }  
  105.       
  106.     // (4)查表得出BASE64码  
  107.     int k, index;  
  108.     for (k = 0; k < quotientBit; k++)  
  109.     {  
  110.         index = base64code[k];  
  111.         base64code[k] = Base64EncodeTable[index];  
  112.     }  
  113.       
  114.     // (5)用等号补足的倍数个BASE64码  
  115.     if (cover == 1)  
  116.     {  
  117.         base64code[quotientBit] = '=';  
  118.     }  
  119.     else if (cover == 2)  
  120.     {  
  121.         base64code[quotientBit] = '=';  
  122.         base64code[quotientBit + 1] = '=';  
  123.     }  
  124.       
  125.     base64code[quotientBit + cover] = 0; // 结尾符号  
  126.   
  127.     return base64code;  
  128. }  
  129.   
  130. // 解码映射  
  131. static void initBase64DecodeTable()   
  132. {  
  133.     int i;  
  134.     for (i = 0; i < 256; ++i)   
  135.         Base64DecodeTable[i] = (char)0x00;  
  136.     // default value: invalid  
  137.       
  138.     for (i = 'A'; i <= 'Z'; ++i)   
  139.         Base64DecodeTable[i] = 0 + (i - 'A');  
  140.     for (i = 'a'; i <= 'z'; ++i)   
  141.         Base64DecodeTable[i] = 26 + (i - 'a');  
  142.     for (i = '0'; i <= '9'; ++i)   
  143.         Base64DecodeTable[i] = 52 + (i - '0');  
  144.     Base64DecodeTable[(unsigned char)'+'] = 62;  
  145.     Base64DecodeTable[(unsigned char)'/'] = 63;  
  146. }  
  147.   
  148. // BASE64解码  
  149. char* Base64Decode(char const* base64code)  
  150. {  
  151.     initBase64DecodeTable();  
  152.   
  153.     size_t base64Len = strlen(base64code);  
  154.     // (1)等号补位数  
  155.     int cover = 0;  
  156.     if (base64code[base64Len - 2] == '=') // 倒数第二个  
  157.     {  
  158.         cover = 2;  
  159.     }  
  160.     else if (base64code[base64Len - 1] == '=') // 倒数第一个  
  161.     {  
  162.         cover = 1;  
  163.     }  
  164.   
  165.     // (2)计算源码字节数  
  166.     int origLen = base64Len * 6 / 8  - cover; // 源码字节数  
  167.     int quotientByte = origLen / 3; // 3x8分组数  
  168.     int remainderByte = origLen % 3; // 不足一组  
  169.     if (remainderByte)  
  170.     {  
  171.         quotientByte++; // 多加一组  
  172.     }  
  173.   
  174.     // (3)4x6 -> 3x8  
  175.     char *orig = new char[origLen + 1];  
  176.     memset(orig, 0, sizeof(orig));  
  177.   
  178.     int i, j;  
  179.     for (i = 0; i < quotientByte; i++)  
  180.     {  
  181.         for (j = 0; j < 3; j++)  
  182.         {  
  183.             if (remainderByte && 3 * i + j == origLen)  
  184.             {  
  185.                 break; // 解码完毕  
  186.             }  
  187.   
  188.             if (j == 0) // 尾6中2  
  189.             {  
  190.                 orig[3 * i + j] = (Base64DecodeTable[base64code[4 * i]] << 2) |   
  191.                     ((Base64DecodeTable[base64code[4 * i + 1]] & 0x30) >> 4); //            
  192.             }  
  193.             else if (j == 1) // 尾4中4  
  194.             {  
  195.                 orig[3 * i + j] = (Base64DecodeTable[base64code[4 * i + 1]] << 4) |   
  196.                     ((Base64DecodeTable[base64code[4 * i + 2]] & 0x3c) >> 2); //  
  197.             }  
  198.             else if (j == 2) // 尾2尾6  
  199.             {  
  200.                 orig[3 * i + j] = (Base64DecodeTable[base64code[4 * i + 2]] << 6) |   
  201.                     (Base64DecodeTable[base64code[4 * i + 3]] & 0x3f); //  
  202.             }  
  203.         }  
  204.   
  205.         if (3 * i + j == origLen)  
  206.         {  
  207.             break;  
  208.         }  
  209.     }  
  210.   
  211.     orig[origLen] = 0;  
  212.   
  213.     return orig;  
  214. }  

 

 

 

 

参考:

BASE64

BASE64编码规则

MIME之Base64编解码

Base64 Encoder/Decoder in C#

Base64加密/解密

Base64编码加密

推荐阅读