Base64编码原理与应用

本文内容转自网络,如需详细内容,请参考相关网址。

http://my.oschina.net/goal/blog/201032

代码参考:http://blog.csdn.net/prsniper/article/details/7097643

 

 

Base64,它用作把任意序列的8位字节描述为一种不易被人直接识别的形式,常用作开发中用于传递参数、浏览器的img标签通过base64字符串来渲染图片以及电子邮件的正文编码等等。

在计算机中显示的字符,比如英文字母、数字以及英文标点符号就是用一个字节来存储,通常称为ASCII码。而简体中文、繁体中文、日文以及韩文等都是用多字节来存储的,通常称之为多字节字符。因为Base编码的输入是字符串的编码,不同编码的字符串的Base64结果是不同的。所以,先来介绍基本的字符编码知识。

字符编码基本知识

最开始的计算机,只支持ASCII码,不支持中文等其他字符,一个字符采用一个字节(8位)表示,只使用低7位,最高位固定为0,因此总共有128个ASCII码(取值范围:0~127)。

为了支持更多地区的语言,各大组织机构和IT厂商开始推广自己的编码方案,以弥补ASCII编码的不足,如GB2312编码、GBK编码和Big5编码,这些编码只是针对局部地区的文字,往下兼容ASCII码,没办法表达所有的语言,这些不同的编码之间没有任何联系,他们之间的转换需要通过查表来实现。

为了提高计算机的信息处理和交换能力,使得各国文字都能在计算机中处理。国际ISO组织制定了通用多字节编码字符集(ISO 10646),简称UCS,这一标准为世界各种主要语言的字符以及附加符号,编制统一的内码。Unicode是Unicode学术学会机构制定的编码系统,从内容上来看,和UCS是同步一致的。

ANSI不代表具体的编码,而是代指本地编码,比如在简体中文版的Windows上面,它代表GB2312编码,在繁体中文版上面,它代表Big5编码,在日文操作系统上面,代表JIS编码。所以,当用户新建并且保存文件类型为ANSI编码,那么那会根据本地系统的编码来确定具体的编码。

Unicode编码

Unicode编码表和字符表是一一映射的,比如汉字”回“,其Unicode编码为56DE,通过56DE就能在Unicode表中找到汉字”回“。Unicode本身定义了每个字符的数值,是字符和自然数的映射关系,而UTF-8、UTF-16则定义了如何在字节流中断字。现在最为常用的Unicode编码为UTF-8和UTF-16.下图为常用的UTF-8的编码形式。在线汉字编码查询点此进入。

从图中可以看出,UTF-8为变长的编码方式(1~6个字节),向下兼容ASCII编码,通常将UTF-8看做单字节或三字节的实现,其他情况非常罕见。每个字节的开始很有规律,方便处理。

UTF-16

UTF-16编码是最直接的Unicode的实现方式,它采用固定两个字节来存储,因为是多字节,所以有小端存储和大端存储两种方式。UTF-16编码是Windows上默认的Unocode编码方式,两个字节小端存储。

在Windows的文本文档中,当另存为时,在编码类型中,有如下几个选择。

image 就同样一个汉字”回“字,其不同的编码内容如下:

image

unicode编码结果上面,前面的两个字节FF FE是文件头,代表这是一个UTF16编码的文件,DE 56是”回“字的UTF16编码十六进制表示,低位字节为DE,高位字节为56,组合在一起,就是0x56DE。

有了上述的知识积累,就可以很方便的在UTF8和UTF16之间相互转换了,还是以”回“字为例子,UTF16编码值为  0x56DE,它在0x0000_0800~0x0000_FFFF之间,对于的UTF8字节为三字节。所以,这个转换就是将2字节的UTF16转换为3字节的UTF8编码。注意到UTF-8表中的转换图中的x部分,就是对应0x56DE的各个位的数值。转化结果如下:image

UTF8转换为UTF16就是上面的逆过程,知道了转换规则,很容易实现其代码。

中国大陆使用的中文标准为GB2312,一共收录了7445个常用简体汉字和中文符号。

Big5是台湾使用的编码标准,大约编码了8千多个繁体汉字。

HKSCS是香港地区使用的编码标准,但和Big5有所不同。

上述这几套中文编码互不兼容,妨碍软件开发,国际上针对此情况,制定了针对中文的统一字符集GBK和GB18030,其中,GBK已经在Windows、Linux等多种操作系统上实现。GBK1.0收录了21886个符号,分为汉字区和图形符号区,2000年的GB18030是取代GBK1.0,成为正式国家标准,该标准收录了27484个汉字,还收录了藏文、蒙文、维吾尔文等主要少数民族汉字。一般设备上,只需要支持GB2312就足够了。

从ASCII、GB2312、GBK到GB18030,这些编码是向下兼容的,其中,区分中文编码的方法是高字节的最高位不为0.

Unicode只与ASCII兼容,与GB系列码不兼容。例如,“汉”字的Unicode码为6C49,GB码为BABA。

 

上面讲述了最基本的ASCII、UTF-8、UTF-16等编码的基础知识和对于的转化规则,下面开始介绍本文重点内容Base64编码。

 

Base64编码

Base64用在必须用可打印字符表示二进制内容的场合,将任意字节转为可读字符的编码,这种编码,不是为安全,因为它是可逆的,而是为了显示。比如需要在xml文档中包含一段音频或者数字签名,URL传递参数,电子邮件的传输编码,可打印字符包括大小写字母(A-Z,a-z),数字(0-9),加号(“+”),正斜杠(“/”),外加补全符号(“=”)

Base64编码要求把3个8位字节(3*8=24位)编码成4个6位的字节(4*6=24位),之后在每个6位字节前面,补充两个0,形成4个8位字节的形式(取值范围在0~63),由于2^6次方等于64,所以每6个位组成一个单元,对于某个可打印的字符,当原始数据不是3的整数倍时,

当最后剩下一个输入字节时,在编码后面添加两个”=”

当最后剩下两个输入字节时,在编码后面添加一个“=”

当数据可以被3整除,就不需要添加数据。

下图为Base64转码表:

上述为标准的Base64编码,标准的Base64编码不适合直接放在URL里面传输,因为URL编码器会将”/“和”+”字符转变为形如”%XX”的形式,而这些”%”号在存入数据库时,还需要转换,因为ANSI SQL中”%“是通配符。

人们为了解决此问题,提出了用于URL的改进Base64编码,它不在末尾填充”=”,并且将标准Base64中的”+”和”/”分别改成了”-”和“_”,这样就免去URL的编解码和数据存储时的格式转换,长度保持不变,统一了数据库、表单等处理对象的格式。

编码过程,简单下来可以总结为:

1. 先将输入的字节数凑成3的整数倍N,然后申请N*4/3+1这么多的空间内存空间

2. 按照3个字节为一组,转换为4个字节的输出原则转换,特别要注意申请空间的最后一位的处理

3. 解密时,有取值判断和查表两种方法,推荐使用查表法。

4. 在字符串输出的最后,需要加上结束标志符’\0’

 

下面给出C语言的Base64代码,在网友给出的代码基础上,经过自己认真测试,现在贡献出来,希望能够帮助到其他人。我在这里面,对结束符的处理是映射为0xFF,个人觉的,只要知道这个标志出现了,做适当的处理就可以了。

 

/**************************************************************************************************
  Filename:       base64.c
  Revised:        2014-12-09 15.18

  Description: this file use to descript the Base64 encode and decode
  Author:          huhao
  Email:           huhao0126@163.com
***************************************************************************************************/

#include "stdlib.h"
#include "base64.h"


static const char BASE_CODE[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static const unsigned char base64_dec_map[128] =
{
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,   //0 ~ 9
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,   //10~19
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,   //20~29
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,   //30~39
    127, 127, 127,  62, 127, 127, 127,  63,  52,  53,   //40~49
    54,  55,  56,  57,  58,  59,  60,  61, 127, 127,    //50~59
    127, 0xFF, 0, 0, 0,   0,   1,   2,   3,   4,        //60~69
    5,   6,   7,   8,   9,  10,  11,  12,  13,  14,     //70~79
    15,  16,  17,  18,  19,  20,  21,  22,  23,  24,    //80~89
    25, 127, 127, 127, 127, 127, 127,  26,  27,  28,    //90~99
    29,  30,  31,  32,  33,  34,  35,  36,  37,  38,    //100~109
    39,  40,  41,  42,  43,  44,  45,  46,  47,  48,    //110~119
    49,  50,  51, 127, 127, 127, 127, 127               //120~127
};

/**************************************************************************************************
 * @fn          base64_encode
 *
 * @brief       This function encode the bindata into base64 format.
 *
 * input parameters
 *
 * @param       bindata   - the input bindata
 * @param       output    - the output base64 data.
 * @param       slen      - the  length of input data.
 *
 * output parameters
 *
 * None.
 *
 * @return the length of encoder output
 **************************************************************************************************
 */
int Base64Encode(const unsigned char* bindata, unsigned char* output,int slen)
{
    int vlen = 0;
    unsigned char* temp_data;

    temp_data = bindata;

    while(slen > 0 )
    {
        *output++ = BASE_CODE[  (temp_data[0]>>2) & 0x3F ];
        if(slen > 2 ) //长度大于三个字符 处理生成4个字符
        {
            *output++ = BASE_CODE[  ( ( temp_data[0] & 0x03 )<<4) | ( temp_data[1] >>4) ];
            *output++ = BASE_CODE[  ( ( temp_data[1] & 0x0F )<<2) | ( temp_data[2] >>6) ];
            *output++ = BASE_CODE[    ( temp_data[2] & 0x3F )];
        }else if( slen  == 2)  //恰好为两个字符
        {
            *output++ = BASE_CODE[  ( ( temp_data[0] & 0x03 )<<4) | ( temp_data[1] >>4) ];
            *output++ = BASE_CODE[  ( ( temp_data[1] & 0x0F )<<2)];
            *output++ = '=';
        }else if( slen == 1)   //恰好为一个字符
        {
            *output++ = BASE_CODE[ (temp_data[0]&0x03) << 4];
            *output++ = '=';
            *output++ = '=';
        }

        temp_data += 3;
        slen    -= 3;
        vlen    += 4;
    }
    *output = '\0';     //this is very improtant
    return vlen;
}

/**************************************************************************************************
 * @fn          GetCharIndex
 *
 * @brief       This function get the mapping value.
 *
 * input parameters
 *
 * @param       c       - Base64 code
 *
 * output parameters
 *              none
 *  it has two ways to map from ciphertext to plaintext,one is lookup-table,and the other is value judgements.
 * @return  original value
**************************************************************************************************
 */
unsigned char GetCharIndex(unsigned char c)
{
    #if 1
    if(  ( c >= 'A' )  && ( c <= 'Z'))
    {
        return c - 'A';
    }else if( (c >= 'a') && (c <= 'z' ))
    {
        return c-'a'+26;
    }else if( (c >= '0') && ( c <= '9'))
    {
        return c - '0' + 52;
    }
    else if( c == '+')
    {
        return 62;
    }else if( c == '/')
    {
        return 63;
    }else if(c == '=')
    {
        return 0xFF;
    }
    #else
    return base64_dec_map[c];
    #endif
    return 0;
}

/**************************************************************************************************
 * @fn          BaseDecode
 *
 * @brief       This function decode the bindata into base64 format.
 *
 * input parameters
 *
 * @param       input    - the encoded input data
 * @param       output   - the decode  output data
 * @param       sLen     - the length of encoded input data.
 *
 * output parameters
 *              none
 *
 * @return      vlen     - the length of decode data
 **************************************************************************************************
 */
int Base64Decode(const unsigned char* input,unsigned char *output,int sLen)
{
    static unsigned char lpCode[4] = {0};
    unsigned char* data_temp = input;
    int vlen = 0;

    //Base64 length must be a multiple of 4 including '='
    if( sLen % 4 )
    {
        return -1;
    }

    while(sLen > 0 )
    {
        lpCode[0] = GetCharIndex(data_temp[0]);
        lpCode[1] = GetCharIndex(data_temp[1]);
        lpCode[2] = GetCharIndex(data_temp[2]);
        lpCode[3] = GetCharIndex(data_temp[3]);

        if(  lpCode[3] == 0xFF )
        {
            if( lpCode[2] == 0xFF )   // if there has two '=' at the end
            {
                *output++ = (lpCode[0] << 2) | (lpCode[1] >>4);
                vlen +=1;
                break;
            }else                   // if there has one '=' at the end
            {
                *output++ = (lpCode[0] << 2) | (lpCode[1] >>4);
                *output++ = (lpCode[1] << 4) | (lpCode[2] >>2);
                vlen +=2;
                break;
            }
        }else
        {
            *output++ = (lpCode[0] << 2) | (lpCode[1] >>4);
            *output++ = (lpCode[1] << 4) | (lpCode[2] >>2);
            *output++ = (lpCode[2] << 6) | (lpCode[3]);

            data_temp+=4;
            sLen -=4;
            vlen +=3;
        }
    }

    *output = '\0'; //this is very improtant
    return vlen;
}


/*
main test function
*/
int test_base64()
{
    unsigned char* output_buffer = NULL;
    char input_str[100]={0};

    int allocate_len;
    int input_len;
    int output_len;

    printf("Please input string : \n ");
    scanf("%s",input_str);

    input_len = strlen(input_str);
    printf("the length of input is %d \n",strlen(input_str));

    //补齐字节数,使得输出缓存为4的倍数,这样来考虑,先把输入长度补齐到3的倍数,然后将其乘以4再除以3
    allocate_len =  ( input_len % 3 ) ? (  input_len + 3 - input_len%3 ) :( input_len);
    allocate_len =  ( allocate_len * 4 )/3;

    output_buffer = (unsigned char*)malloc(allocate_len+1);     //加1 很重要,用于结束编解码的输出字符串
    if(NULL == output_buffer)
    {
        printf("allocate memory fail! \n");
        return -1;
    }else
    {
        printf("success allocate %d bytes. \n",allocate_len);
        memset(output_buffer,0,allocate_len);
    }

    output_len = Base64Encode(input_str,output_buffer,input_len);

    printf("Base64ENcode(\" %s \" ,encoding output length is %d \")  \n encode content is %s \" \n\n",input_str,output_len,output_buffer);

    memset(input_str,0,sizeof(input_str));
    output_len = Base64Decode(output_buffer,input_str,output_len);

    printf("Base64Decode(\" %s  \" ,decoding output length is %d \")  \n decode content is %s   \n",output_buffer,output_len,input_str);

    free(output_buffer);        //release the allocated memory
    output_buffer = NULL;
}

执行结果如下:

image

 

完成这篇博客,从基础知识的准备到base64原理的了解,再到最终代码的实现和调试,花费了一天的时间,具体原理不难,调试过程中,遇到了很多问题,深知,网上得来终觉浅,绝知此事要躬行。别人给出的代码,自己如果不敲一片,字字斟酌,如果只是匆匆扫过,是怎么样也不会体会别人的思路和方法。现在网络资源异常丰富,我们在别人的基础上,进行自己的改进和优化,博采众长,提升自己。

对代码来说,光说不练假把式,虽说是今天实现的只是一个小小的功能,把小功能步步都想清楚,各种情况都处理好,每天深入了解学习一个知识点,能够解决一个问题,那这一天就很有价值,没有白白度过。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2014-12-09 15:29  浩天之家  阅读(2650)  评论(1编辑  收藏  举报