Base64的编解码方法

2016-07-04

Base64是一种很常用的编码方式,利用它可以将任何二进制的字符编码到可打印的64个字符之中, 这样,不管是图片,中文文本等都可以编码成只有ASCII的纯文本。至于为什么要进行这个转换呢, 最初主要使用在EMail领域,早期的一些邮件网关只识别ASCII, 如果发现邮件里有其他字符,就会将它们过滤掉,这样中文的邮件,有图片附件的邮件在这些网关上就会发生问题,于是将中文和图片都使用base64编码然后传输,接受后再解码就客服了这个问题了。 Base64除了可以使用在相似场合,还可以用作简单的加密等等。下面介绍下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 (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y

所有的字符就是'A'~'Z','a'~'z','0'~'9','+','/'共64个,以及末尾的填充字符'='

编码的方法是:

从输入缓冲中依次取出字符,第一个字符的,从最高位开始取出6个 bit,这6个bit的值的范围在0~63,将这个值作为索引, 对应上面的表格,找到相应的字符,这便是第一个Base64后的字符,然后将第一个字符的低2位与第二个字符的高4位组成6个bit, 同样查表得到第二个 Base64字符,以此类推,从左向右没凑足6个bit就转换成一个Base64字符,由于输入缓冲中每3个字符 包含24个bit,这24个bit正好可以转成4个 Base64字符,所以没3个字符能组成一个转换循环,如果输入缓冲中字符的个数是3 的整数倍,那么结果就是4的整数倍,两者的长度是3:4的关系,但是如果输入字符不是3的整数倍呢?这就涉及到了末尾填充问题。

输入缓冲的末尾可能余下一个字符,或两个字符:

如果余下一个字符,前6个bit转换成Base64,剩下的低2位要右边补0 ,凑成6bit,然后转换成Base64,为了让解析者了解这个 情况,在输出缓冲的最后要补上两个'='。

如果余下两个字符,同样转换出两个Base64 字符后,在剩下的4个bit右边补0,凑成6bit,然后转换成Base64,同样在输出缓冲 的末尾要补上一个'=' 。
由此可见Base64 后的字符串,长度一定是4的整数倍,末尾有一个,两个或没有'='。

要注意的是为了兼容有些邮件服务器, Base64后的字符串经常要插入来确保每一行不超过76个字符,解析时要跳过它们。

好了,原理就是这样的,是不是很简单,就是取3个转成4个,好了,上代码:

首先是编码:

      	const BYTE Base64ValTab[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ
                           abcdefghijklmnopqrstuvwxyz0123456789+/";

        #define AVal(x) Base64ValTab[x]
      	int CSeeBase64Dlg::EncodeBase64(char * pInput, char * pOutput)
        {
		int i = 0;
		int loop = 0;
		int remain = 0;
		int iDstLen = 0;
		int iSrcLen = (int)strlen(pInput);
	
		loop = iSrcLen/3;
		remain = iSrcLen%3;
	
		// also can encode native char one by one as decode method
		// but because all of char in native string  is to be encoded so encode 3-chars one time is easier.
	
		for (i=0; i < loop; i++)
		{
			BYTE a1 = (pInput[i*3] >> 2);
			BYTE a2 = ( ((pInput[i*3] & 0x03) << 4) | (pInput[i*3+1] >> 4) );
			BYTE a3 = ( ((pInput[i*3+1] & 0x0F) << 2) | ((pInput[i*3+2] & 0xC0) >> 6) );
			BYTE a4 = (pInput[i*3+2] & 0x3F);
	
			pOutput[i*4] = AVal(a1);
			pOutput[i*4+1] = AVal(a2);
			pOutput[i*4+2] = AVal(a3);
			pOutput[i*4+3] = AVal(a4);
		}
	
		iDstLen = i*4;
	
		if (remain == 1)
		{
			// should pad two equal sign
			i = iSrcLen-1;
			BYTE a1 = (pInput[i] >> 2);
			BYTE a2 = ((pInput[i] & 0x03) << 4);
			
			pOutput[iDstLen++] = AVal(a1);
			pOutput[iDstLen++] = AVal(a2);
			pOutput[iDstLen++] = '=';
			pOutput[iDstLen++] = '=';
			pOutput[iDstLen] = 0x00;
		}
		else if (remain == 2)
		{
			// should pad one equal sign
			i = iSrcLen-2;
			BYTE a1 = (pInput[i] >> 2);
			BYTE a2 = ( ((pInput[i] & 0x03) << 4) | (pInput[i+1] >> 4));
			BYTE a3 = ( (pInput[i+1] & 0x0F) << 2);
	
			pOutput[iDstLen++] = AVal(a1);
			pOutput[iDstLen++] = AVal(a2);
			pOutput[iDstLen++] = AVal(a3);
			pOutput[iDstLen++] = '=';
			pOutput[iDstLen] = 0x00;
		}
		else
		{
			// just division by 3
			pOutput[iDstLen] = 0x00;
		}
	
		return iDstLen;
}
      

下面是解析的:

	const BYTE Base64IdxTab[128] =
	{
	255,255,255,255,  255,255,255,255,  255,255,255,255,  255,255,255,255,
	255,255,255,255,  255,255,255,255,  255,255,255,255,  255,255,255,255,
	255,255,255,255,  255,255,255,255,  255,255,255,62,   255,255,255,63,
	52,53,54,55,      56,57,58,59,      60,61,255,255,    255,255,255,255,
	255,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,255,     255,255,255,255,
	255,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,255,     255,255,255,255
	};

	#define BVal(x) Base64IdxTab[x]

	int CSeeBase64Dlg::DecodeBase64(char * pInput, char * pOutput)
	{
		int i = 0;
		int iCnt = 0;
		int iSrcLen = (int)strlen(pInput);
	
		char * p = pOutput;
	
		for (i=0; i < iSrcLen; i++)
		{
			if (pInput[i] > 127) continue;
			if (pInput[i] == '=') return p-pOutput+1;
	
			BYTE a = BVal(pInput[i]);
			if (a == 255) continue;
			
			switch (iCnt)
			{
			case 0:
				{
					*p = a << 2;
					iCnt++;
				}
				break;
	
			case 1:
				{
					*p++ |= a >> 4;
					*p = a << 4;
					iCnt++;
				}
				break;
	
			case 2:
				{
					*p++ |= a >> 2;
					*p = a << 6;
					iCnt++;
				}
				break;
			case 3:
				{
					*p++ |= a;
					iCnt = 0;
				}
				break;
			} 
		}
	
		*p = 0x00;
		return p-pOutput;
	}