Base64编码已广泛地应用于各式各样的应用程序中,这些软件都在享受着Base64编码带来的便捷,但对于Base64编码相关概念及原理又了解多少呢?本文就来讲述一下Base64编码相关的内容。
我们在使用libjingle(客户端)和XMPP服务器实现IM聊天功能时,测试过程中发现,当发送信息的包含一些特殊字符(不可识别字符)时,XMPP服务器会强行将客户端断开。起初很是奇怪,为啥随便发几个消息后,XMPP服务器就将libjingle客户端的连接断开了。经后来排查得知,libjingle和XMPP服务器之间交互的数据采用XML格式,是因为聊天信息字符串中包含了XML无法识别的字符,XMPP服务器认为XML数据是非法的,强行把发送XML数据的客户端给断开了。为了解决这个问题,先将聊天信息字符串进行base64编码,转成可识别字符,然后再写到XML的节点中,这样就能保证XML数据中不出现不可识别的字符了,XMPP服务器就不会再强行将libjingle客户端断开了。
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的长度,然后再去调用这两个接口。
代码中将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;
}
编写如下的测试代码,先调用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的长度应该设置为多大?上面我们已经讲过编码后的字符串长度和编码前的字节的关系,按照换算关系即可计算出来,此处就不再赘述了。