VC++详解Base64编解码原理以及Base64编解码接口实现(附源码)

Posted dvlinker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VC++详解Base64编解码原理以及Base64编解码接口实现(附源码)相关的知识,希望对你有一定的参考价值。

       Base64编码已广泛地应用于各式各样的应用程序中,这些软件都在享受着Base64编码带来的便捷,但对于Base64编码相关概念及原理又了解多少呢?本文就来讲述一下Base64编码相关的内容。

1、Base64编码帮我们解决的实际问题

       我们在使用libjingle(客户端)和XMPP服务器实现IM聊天功能时,测试过程中发现,当发送信息的包含一些特殊字符(不可识别字符)时,XMPP服务器会强行将客户端断开。起初很是奇怪,为啥随便发几个消息后,XMPP服务器就将libjingle客户端的连接断开了。经后来排查得知,libjingle和XMPP服务器之间交互的数据采用XML格式,是因为聊天信息字符串中包含了XML无法识别的字符,XMPP服务器认为XML数据是非法的,强行把发送XML数据的客户端给断开了。为了解决这个问题,先将聊天信息字符串进行base64编码,转成可识别字符,然后再写到XML的节点中,这样就能保证XML数据中不出现不可识别的字符了,XMPP服务器就不会再强行将libjingle客户端断开了。

2、Base64编解码原理

       Base64编码并不是安全领域的加密算法,经过base64编码输出的字符串是可以通过Base64解码得到原始字符串内容的,所以Base64编码不是加密算法,其核心作用是用于保证传输数据的正确性。Base64编码可以将任意二进制数据转换成64个可打印字符,主要用于下xml、http和mime协议下的数据传输。

        Base64编码用到的64个可打印字符由A-Z所有大写字母(26个)、a-z所有小写字母(26个)、0-9所有数字(10个)和+/(2个)构成,这64个字符数也是Base64编码名称的由来。另外,=号是Base64编码中的占位符。Base64为64个字符制定了一个编码表,如下:

       编码原理是以3个字节为单位,每3个字节转换成4个字节的字符。具体的做法是,先读入3个字节,每读一个字节,左移8位,再右移四次,每次6位,这样就有4个字节了,然后将每个字节转换成64个可见字符中的一个。当剩下的字符数量不足3个字节时,则应使用0进行填充,相应的,输出字符则使用'='占位,因此编码后输出的文本末尾可能会出现1至2个'='。

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

       所以,编码后的字符串长度编码前的字节的关系为:(假设编码前的字符串长度为beforeEncLen)

1)如果beforeEncLen是3的整数倍,那么长度为(beforeEncLen/3)*4
2)如果beforeEncLen不是3的整数倍,那么长度为(beforeEncLen/3+1)*4

所以,在调用EncodeBase64编码接口与DecodeBase64解码接口时,先根据上述关系计算出存放数据的目标buffer的长度,然后再去调用这两个接口。

3、Base编解码接口的实现

       代码中将64个可打印字符存放到字符数组s_base64_alphabet中,EncodeBase64是Base64编码接口,DecodeBase64是解码接口。所有的代码实现如下:

enum EmJError

	JSTR_ERROR_OK                    = 0,            // 正确
	JSTR_ERROR_INVALID_ARGUMENT,                     // 无效参数
	JSTR_ERROR_NOT_ENOUGH,                           // 没有足够缓冲区
	JSTR_ERROR_NO_MEMORY,                            // 分配内存失败
	JSTR_ERROR_FAIL_TRANSFER,                        // 转换失败(格式错误)
	JSTR_ERROR_TOO_LARGE_NUMBER,                     // 太大的数字
	JSTR_ERROR_BASE64_LENGTH_INVALID                 // base64字串长度无效
;

// 得到某个字节的前几位
#define GET_FRONT_BITS( b, n )     ((BYTE)((b)>>(8-(n))))
// 得到某个字节的后几位
#define GET_BACK_BITS( b, n )      (((BYTE)((b)<<(8-(n))))>>(8-(n)))

const char s_base64_alphabet[]= 
 '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','+','/', '=' ; 

BYTE _GetBase64Index( char chBase64 )

	if ( chBase64 >= 'A' && chBase64 <= 'Z' )
	
		return chBase64 - 'A';
	
	else if ( chBase64 >= 'a' && chBase64 <= 'z' )
	
		return chBase64 - 'a' + 26;
	
	else if ( chBase64 >= '0' && chBase64 <= '9' )
	
		return chBase64 - '0' + 52;
	
	else if ( chBase64 == '+' )
	
		return 62;
	
	else if ( chBase64 == '/' )
	
		return 63;
	
	else if ( chBase64 == '=' )
	
		return 64;
	
	else
	
		return (BYTE)-1;
	



// 把字节流转换成base64编码 
int EncodeBase64( char * szBase64, unsigned long & dwSize, const unsigned char * pbySrc, unsigned long dwSrcLength )

	if ( 0 == szBase64 || 0 == dwSize || 0 == pbySrc || 0 == dwSrcLength )
	
		return JSTR_ERROR_INVALID_ARGUMENT;
	

	DWORD dwMultiple  = dwSrcLength / 3;
	DWORD dwResidue   = dwSrcLength % 3;

	DWORD dwMinSize = 4 * dwMultiple;

	if ( 0 != dwResidue )
	
		dwMinSize += 4;
	

	// 保留一个结束字符'\\0'
	if ( dwSize < dwMinSize + 1 )
	
		return JSTR_ERROR_NOT_ENOUGH;
	

	DWORD i;
	for ( i = 0; i < dwMultiple; i++ )
	
		const BYTE * p = pbySrc + 3 * i;

		BYTE b1 = GET_FRONT_BITS( p[0], 6 ) ;
		BYTE b2 = ( GET_BACK_BITS( p[0], 2 )<<4) + GET_FRONT_BITS(p[1], 4);
		BYTE b3 = ( GET_BACK_BITS(p[1],4)<<2 ) + GET_FRONT_BITS(p[2], 2);
		BYTE b4 = GET_BACK_BITS( p[2], 6 );

		assert( b1 <= 63 && b2 <= 63 && b3 <= 63 && b4 <= 63 );

		char * q = szBase64 + 4 * i;

		q[0] = s_base64_alphabet[b1];
		q[1] = s_base64_alphabet[b2];
		q[2] = s_base64_alphabet[b3];
		q[3] = s_base64_alphabet[b4];
	

	const BYTE * p = pbySrc + 3 * i;
	char * q = szBase64 + 4 * i;

	if ( 1 == dwResidue )
	
		BYTE b1 = GET_FRONT_BITS( p[0], 6 ) ;
		BYTE b2 = ( GET_BACK_BITS( p[0], 2 )<<4);

		q[0] = s_base64_alphabet[b1];
		q[1] = s_base64_alphabet[b2];
		q[2] = s_base64_alphabet[64];
		q[3] = s_base64_alphabet[64];

		i++;
	
	else if ( 2 == dwResidue )
	
		BYTE b1 = GET_FRONT_BITS( p[0], 6 ) ;
		BYTE b2 = ( GET_BACK_BITS( p[0], 2 )<<4) | GET_FRONT_BITS(p[1], 4);
		BYTE b3 = GET_BACK_BITS(p[1],4)<<2;

		q[0] = s_base64_alphabet[b1];
		q[1] = s_base64_alphabet[b2];
		q[2] = s_base64_alphabet[b3];
		q[3] = s_base64_alphabet[64];

		i++;
	

	dwSize = 4 * i;
	*( szBase64 + 4 * i ) = '\\0';

	return JSTR_ERROR_OK;


// 2、把base64编码字符串转换成字节流,其中pdwSize是输入输出参数,pbyDest的大小
int DecodeBase64( unsigned char * pbyDest, unsigned long * pdwSize, 
	const char * szBase64 )

	if ( 0 == pbyDest || 0 == pdwSize || 0 == szBase64 )
	
		return JSTR_ERROR_INVALID_ARGUMENT;
	

	DWORD dwBase64Size = strlen( szBase64 );

	if ( 0 != dwBase64Size % 4 )
	
		return JSTR_ERROR_BASE64_LENGTH_INVALID;
	

	DWORD dwMultiple =  dwBase64Size / 4;

	DWORD i;

	DWORD dwSize = 0;

	for ( i = 0; i < dwMultiple; i++ )
	
		BYTE * q = pbyDest + 3 * i;
		const char * p = szBase64 + 4 * i;

		BYTE b1 = _GetBase64Index( p[0] );
		BYTE b2 = _GetBase64Index( p[1] );
		BYTE b3 = _GetBase64Index( p[2] );
		BYTE b4 = _GetBase64Index( p[3] );

		if (   b1 == (BYTE)(-1) || b2 == (BYTE)(-1)
			|| b3 == (BYTE)(-1) || b4 == (BYTE)(-1) )
		
			return JSTR_ERROR_INVALID_ARGUMENT;
		

		if ( 64 == b1 || 64 == b2 )
		
			return JSTR_ERROR_INVALID_ARGUMENT;
		

		if ( i < dwMultiple - 1 )
		
			if ( 64 == b3 || 64 == b4 )
			
				return JSTR_ERROR_INVALID_ARGUMENT;
			

			dwSize += 3;
			if ( *pdwSize < dwSize )
			
				return JSTR_ERROR_NOT_ENOUGH;
			

			q[0] = (GET_BACK_BITS(b1,6)<<2) | GET_FRONT_BITS(b2,4);
			q[1] = (GET_BACK_BITS(b2,4)<<4) | GET_FRONT_BITS(b3,6);
			q[2] = (GET_BACK_BITS(b3,2)<<6) | b4;
		
		else
		
			if ( 64 == b3 )
			
				if ( 64 != b4 )
				
					return JSTR_ERROR_INVALID_ARGUMENT;
				

				dwSize++;
				if ( *pdwSize < dwSize )
				
					return JSTR_ERROR_NOT_ENOUGH;
				

				q[0] = (GET_BACK_BITS(b1,6)<<2) | GET_FRONT_BITS(b2,4);
			
			else if ( 64 == b4 )
			
				dwSize += 2;
				if ( *pdwSize < dwSize )
				
					return JSTR_ERROR_NOT_ENOUGH;
				

				q[0] = (GET_BACK_BITS(b1,6)<<2) | GET_FRONT_BITS(b2,4);
				q[1] = (GET_BACK_BITS(b2,4)<<4) | GET_FRONT_BITS(b3,6);
			
			else
			
				dwSize += 3;
				if ( *pdwSize < dwSize )
				
					return JSTR_ERROR_NOT_ENOUGH;
				

				q[0] = (GET_BACK_BITS(b1,6)<<2) | GET_FRONT_BITS(b2,4);
				q[1] = (GET_BACK_BITS(b2,4)<<4) | GET_FRONT_BITS(b3,6);
				q[2] = (GET_BACK_BITS(b3,2)<<6) | b4;

				*pdwSize += 3;
			
		
	

	*pdwSize = dwSize;
	return JSTR_ERROR_OK;

4、测试代码

       编写如下的测试代码,先调用EncodeBase64接口对原始字符串进行编码,然后再调用DecodeBase64接口进行解码,看看解码出来的字符串是否和原始字符串相同,经验证是完全相同的。测试代码如下:

// 原始字符串
char* pszOrig = "测试123";
int nLen = strlen(pszOrig);

// 先编码
char szBase64[260] =  0 ;
unsigned long ulBase64Len = 260;
EncodeBase64( szBase64, ulBase64Len, (unsigned char*)pszOrig, strlen(pszOrig));

// 再解码 
unsigned char szOrig[260] =  0 ;
unsigned long ulOrigLen = 260;
DecodeBase64( szOrig, &ulOrigLen, szBase64 );

有人可能会问,在调用EncodeBase64与DecodeBase64两接口时,存放结果数据的目标buffer的长度应该设置为多大?上面我们已经讲过编码后的字符串长度编码前的字节的关系,按照换算关系即可计算出来,此处就不再赘述了。

以上是关于VC++详解Base64编解码原理以及Base64编解码接口实现(附源码)的主要内容,如果未能解决你的问题,请参考以下文章

Base64编解码是什么?

iOS Base64 String 转成 NSString (base64编/解码)

Go语言实现Base64Base58编码与解码

python3的base64编解码

Base64编解码 ---- 支持64编码

Android Base64 URI 编解码bad base-64异常处理