1.Base64是什么?

        Base64是一种二进制到文本的编码方式。如果要更具体一点的话,可以认为它是一种将 byte数组编码为字符串的方法,而且编码出的字符串只包含ASCII基础字符。

        例如字符串 mickey0380 对应的Base64为 bWlja2V5MDM4MA== 。其中那个 = 比较特殊,是填充符。

        值得注意的是Base64不是加密算法,其仅仅是一种编码方式,算法也是公开的,所以不能依赖它进行加密。

2.为什么叫Base64?

        因为它是基于(Base)64个字符的一种编码方式。使用其编码后的文本只包含64个ASCII码字符(偶尔加一个填充字符=),如下所示:

        Base64使用到的64个字符:

  • A-Z 26个

  • a-z 26个

  • 0-9 10个

  • + 1个

  • / 1个

        下表是Base64码表,可以看到从0到63的每个数字都对应一个上面的一个字符。

Index

Binary

Char

Index

Binary

Char

Index

Binary

Char

Index

Binary

Char

0

000000

A

16

010000

Q

32

100000

g

48

110000

w

1

000001

B

17

010001

R

33

100001

h

49

110001

x

2

000010

C

18

010010

S

34

100010

i

50

110010

y

3

000011

D

19

010011

T

35

100011

j

51

110011

z

4

000100

E

20

010100

U

36

100100

k

52

110100

0

5

000101

F

21

010101

V

37

100101

l

53

110101

1

6

000110

G

22

010110

W

38

100110

m

54

110110

2

7

000111

H

23

010111

X

39

100111

n

55

110111

3

8

001000

I

24

011000

Y

40

101000

o

56

111000

4

9

001001

J

25

011001

Z

41

101001

p

57

111001

5

10

001010

K

26

011010

a

42

101010

q

58

111010

6

11

001011

L

27

011011

b

43

101011

r

59

111011

7

12

001100

M

28

011100

c

44

101100

s

60

111100

8

13

001101

N

29

011101

d

45

101101

t

61

111101

9

14

001110

O

30

011110

e

46

101110

u

62

111110

+

15

001111

P

31

011111

f

47

101111

v

63

111111

-

Padding

=

3.Base64编码步骤

  1. 将原始数据每三个字节作为一组,每个字节是8个bit,所以一共是 24 个 bit

  2. 将 24 个 bit 分为四组,每组 6 个 bit

  3. 在每组前面加补 00,将其补全成四组8个bit 到此步,原生数据的3个字节已经变成4个字节了,增大了将近30%

  4. 根据Base64码表得到扩展后每个字节的对应符号(见上表)

        下图是维基百科上面的一个例子,假如我们的原文为Man,那么下表演示了如何按照上面的步骤将其编码为Base64字符串

        可以发现Man对应的Base64为TWFu.现在大家应该明白为什么只有64个字符了吧?因为算法将将8bit分割成6bit了,而6bit的取值范围为0~63

        如果要编码的字节数不能被3整除,最后会多出1个或2个字节,那么可以使用下面的方法进行处理:先使用0字节值在末尾补足,使其能够被3整除,然后再进行base64的编码。在编码后的base64文本后加上一个或两个'='号,代表补足的字节数。也就是说,当最后剩余一个八位字节(一个byte)时,最后一个6位的base64字节块有四位是0值,最后附加上两个等号;如果最后剩余两个八位字节(2个byte)时,最后一个6位的base字节块有两位是0值,最后附加一个等号。 参考下图:

4.Base64URL

        Base64 能够将二进制流很好的转换为一串可打印字符流,然而编码后的 =、/、+ 等字符不利于 url 中的查询参数、数据库保存时的转义等,所以在实际应用的场景中又产生了一种几乎等价的编码方案 Base64URL。

        Base64URL 的基本规则与 Base64 一模一样,差别在于最后两个特殊字符 +、/ 改成使用 -、_,同时也去除末尾额外添加的 = 字符即完成 Base64URL 的编码

4.1 Base64URL编码流程

        1、明文使用BASE64进行加密

        2、在BASE64的基础上进行一下的编码:

                2.1)去除尾部的"="

                2.2)把"+"替换成"-"

                2.3)把"/"替换成"_"

4.2 Base64URL解码流程

        1、把BASE64URL的编码做如下解码:

                1)把"-"替换成"+"

                2)把"_"替换成"/"

                3)(计算BASE64URL编码长度)%4

                        3.1)结果为0,不做处理

                        3.2)结果为2,字符串添加"=="

                        3.3)结果为3,字符串添加"="

        2、使用BASE64解码密文,得到原始的明文

5. Base64及Base64URL编解码C++代码实现

头文件 Base64Util.h

#pragma once

#if defined(_WIN32) || defined(_WIN64)
#ifndef WINAPI
#define WINAPI __stdcall
#endif
#else
#define WINAPI 
#endif

//#ifdef __cplusplus
//extern "C" {
//#endif

/*********************************************************************************
        功  能: 对数据块进行Base64编码
        参        数: pInput      - [in]编码前数据块
                        nLength     - [in]输入数据块(pInput)长度
                        pOutput     - [out]Base64编码后数据块,大小为输入数据的4/3倍,
                                                   输出数据块pInput 和输入数据块pOutput 起始地址可以相同
                        nOutBufSize - [in]存放编码后数据(pOutput)的缓冲区大小
        返  回:          0   - 用于存放编码后数据的缓冲区不够,编码失败。
                        大于0 - 编码后数据长度,值为(inputLen+2)/3*4
*********************************************************************************/
int WINAPI Base64Encode(unsigned char *pInput, int nLength, char *pOutput, int nOutBufSize);

/*********************************************************************************
        功  能:对输入的Base64编码数据块进行Base64解码
        参        数:pInput  - [in]Base64编码数据块
                        nLength - [in]Base64编码数据块长度
                        pOutput - [out]Base64解码后的数据块
                                                输出数据块pInput和输入数据块pOutput起始地址可以相同
        返  回:   0  - 无效数据,解码失败
                        大于0 - Base64解码后数据长度
*********************************************************************************/
int WINAPI Base64Decode(const char *pInput, int nLength, unsigned char* pOutput);

/*********************************************************************************
        功  能: 对数据块进行Base64URL编码
        参        数: pInput      - [in]编码前数据块
                        nLength     - [in]输入数据块(pInput)长度
                        pOutput     - [out]Base64URL编码后数据块,大小为输入数据的4/3倍,
                                                           输出数据块pInput 和输入数据块pOutput 起始地址可以相同
                        nOutBufSize - [in]存放编码后数据(pOutput)的缓冲区大小
        返  回:          0   - 用于存放编码后数据的缓冲区不够,编码失败。
                        大于0 - 编码后数据长度,值为(inputLen+2)/3*4
*********************************************************************************/
int WINAPI Base64URLEncode(unsigned char *pInput, int nLength, char *pOutput, int nOutBufSize);

/*********************************************************************************
        功  能:对输入的Base64URL编码数据块进行base64URL解码
        参        数:pInput  - [in]Base64URL编码数据块
                        nLength - [in]Base64URL编码数据块长度
                        pOutput - [out]Base64URL解码后的数据块
                                                输出数据块pInput和输入数据块pOutput起始地址可以相同
        返  回:   0  - 无效数据,解码失败
                        大于0 - Base64URL解码后数据长度
*********************************************************************************/
int WINAPI Base64URLDecode(const char *pInput, int nLength, unsigned char* pOutput);

//#ifdef __cplusplus
//}
//#endif

源文件 Base64Util.cpp

#include "stdafx.h"

#include <malloc.h>
#include "Base64Util.h"

/* 0-63的数转化为可见字符查表 */
char g_b64EncTable[65] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

unsigned char g_b64DecTable[256] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,

        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
        0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

        0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
        0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
        0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
        0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,

        0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
        0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
        0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
        0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

int WINAPI Base64Encode(unsigned char *pInput, int nLength, char *pOutput, int nOutBufSize)
{        
        int nModulus = nLength%3;
        int nOutLen = (nLength+2)/3*4; /* 编码后数据是4的整数倍 */
        int nDatalen = nLength-nModulus;
        register int i, j;
        register int x, y, z;
        
        /* 缓冲区大小校验 */
        if(nOutBufSize < nOutLen)
        {
                return 0;
        }
        
        for (i=0, j=0; i<nDatalen; i+=3,j+=4)
        {
                x = pInput[i];
                y = pInput[i+1];
                z = pInput[i+2];
                pOutput[j] = g_b64EncTable[x>>2];
                pOutput[j+1] = g_b64EncTable[((x&3)<<4)|(y>>4)];
                pOutput[j+2] = g_b64EncTable[((y&15)<<2)|(z>>6)];
                pOutput[j+3] = g_b64EncTable[z&63];
        }

        i = nDatalen;
        j = (nDatalen/3)*4;
        if (nModulus == 1)
        {
                x = pInput[i];
                pOutput[j] = g_b64EncTable[x>>2];
                pOutput[j+1] = g_b64EncTable[(x&3)<<4];
                pOutput[j+2] = '=';
                pOutput[j+3] = '=';
        }
        else if (nModulus == 2)
        {
                x = pInput[i];
                y = pInput[i+1];
                pOutput[j] = g_b64EncTable[x>>2];
                pOutput[j+1] = g_b64EncTable[((x&3)<<4)|(y>>4)];
                pOutput[j+2] = g_b64EncTable[(y&15)<<2];
                pOutput[j+3] = '=';
        }
        return nOutLen; 
}

int WINAPI Base64Decode(const char *pInput, int nLength, unsigned char* pOutput)
{
        register int i,j;
        register int x, y, z, k;
        int nOutLen;
        int nPadBytes;
        int nDataLen;
        
        if(nLength%4 != 0)
        {
                return 0; 
        }

        /* 根据'='的个数判断编码前数据的长度 */
        if(pInput[nLength-2] == '=')
        {
                nPadBytes = 2;
        }
        else if(pInput[nLength-1] == '=')
        {
                nPadBytes = 1;
        }
        else
        {
                nPadBytes = 0;
        }

        nOutLen = nLength/4*3 - nPadBytes;
        nDataLen = (nLength-nPadBytes)/4*3;
        
        /* 开始解码 */
        for(i=0, j=0; i<nDataLen; i+=3, j+=4)
        {
                x = g_b64DecTable[pInput[j]];
                y = g_b64DecTable[pInput[j+1]];
                z = g_b64DecTable[pInput[j+2]];
                k = g_b64DecTable[pInput[j+3]];
                if (x==0xff || y==0xff || z==0xff || k==0xff)
                {
                        return 0;
                }
                pOutput[i] = (x<<2)|(y>>4);
                pOutput[i+1] = ((y&15)<<4)|(z>>2);
                pOutput[i+2] = ((z&3)<<6)|k;
        }

        i = nDataLen;
        if (nPadBytes == 1)
        {
                x = g_b64DecTable[pInput[j]];
                y = g_b64DecTable[pInput[j+1]];
                z = g_b64DecTable[pInput[j+2]];

                pOutput[i] = (x<<2)|(y>>4);
                pOutput[i+1] = ((y&15)<<4)|(z>>2);
        }
        else if (nPadBytes == 2)
        {
                x = g_b64DecTable[pInput[j]];
                y = g_b64DecTable[pInput[j+1]];

                pOutput[i] = (x<<2)|(y>>4);
        }
        return nOutLen;
}

/*
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法
BASE64URL编码的流程:
        1、明文使用BASE64进行加密 
        2、在BASE64的基础上进行一下的编码:
                2.1)去除尾部的"=" 
                2.2)把"+"替换成"-" 
                2.3)把"/"替换成"_"
BASE64URL解码的流程:
        1、把BASE64URL的编码做如下解码:
                1)把"-"替换成"+"
                2)把"_"替换成"/"
                3)(计算BASE64URL编码长度)%4
                        3.1)结果为0,不做处理
                        3.2)结果为2,字符串添加"=="
                        3.3)结果为3,字符串添加"="
        2、使用BASE64解码密文,得到原始的明文
*/
int WINAPI Base64URLEncode(unsigned char *pInput, int nLength, char *pOutput, int nOutBufSize)
{
        //1.明文使用BASE64进行加密 
        int nOutLen = Base64Encode(pInput,nLength, pOutput,nOutBufSize);
        if (nOutLen <= 0)
        {
                return nOutLen;
        }
        //2.在BASE64的基础上进行一下的编码
        //2.1 去除尾部的"=" 
        if ('=' == pOutput[nOutLen - 2])
        {
                pOutput[nOutLen - 2] = '\0';
                nOutLen = nOutLen - 2;
        }
        else if ('=' == pOutput[nOutLen - 1])
        {
                pOutput[nOutLen - 1] = '\0';
                nOutLen = nOutLen - 1;
        }
        //        2.2)把"+"替换成"-" 
        //  2.3)把"/"替换成"_"
        for (int i = 0; i<nOutLen; i++)
        {
                if ('+' == pOutput[i])
                        pOutput[i] = '-';
                else if ('/' == pOutput[i])
                        pOutput[i] = '_';
        }
        return nOutLen;
}

int WINAPI Base64URLDecode(const char *pInput, int nLength, unsigned char* pOutput)
{
        int iTmpLen = nLength;
        char* pTmpBuffer = (char*)malloc((nLength + 10)*sizeof(char));
        memcpy(pTmpBuffer, pInput, nLength);
        //1、把BASE64URL的编码做如下解码
        // 1)把"-"替换成"+". 
        // 2)把"_"替换成"/" .
        for (int i = 0; i<nLength; i++)
        {
                if ('-' == pTmpBuffer[i])
                        pTmpBuffer[i] = '+';
                else if ('_' == pTmpBuffer[i])
                        pTmpBuffer[i] = '/';
        }
        //3)(计算BASE64URL编码长度)%4
        //   3.1)结果为0,不做处理
        //   3.2)结果为2,字符串添加"=="
        //   3.3)结果为3,字符串添加"="
        int nModulus = nLength % 4;
        if (2 == nModulus)
        {
                pTmpBuffer[nLength] = '=';
                pTmpBuffer[nLength+1] = '=';
                iTmpLen = nLength + 2;
        }
        else if (3 == nModulus)
        {
                pTmpBuffer[nLength] = '=';
                iTmpLen = nLength + 1;
        }
        // 使用BASE64解码密文,得到原始的明文
        int nOutLen = Base64Decode(pTmpBuffer, iTmpLen, pOutput);

        free(pTmpBuffer);
        return nOutLen;
}

6. C++测试工程源代码

编译器版本:VS2015

https://download.csdn.net/download/mickey2007/89029149

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐