用於將位元組進行base64編碼或解碼(C語言實現)

舟清颺發表於2024-06-13

V1.0 2024年6月13日 釋出於部落格園

目錄
  • base64.h
  • base64.c

基本原理見程式碼註釋!

base64.h

#ifndef _BASE64_H
#define _BASE64_H
/**
 * @file name : base64.h
 * @brief     : 用於將位元組進行base64編碼或解碼
 * @author    : RISE_AND_GRIND@163.com
 * @date      : 2024年6月12日
 * @version   : 1.0
 * @note      :
 * CopyRight (c)  2023-2024   RISE_AND_GRIND@163.com   All Right Reseverd
 */

/**Base64基本介紹
 *  什麼是Base64編碼?
 *      編碼的主要目的是在需要透過文字傳輸或儲存二進位制資料的場景中,
 *      確保資料的完整性和可讀性。直接使用ASCII儲存二進位制資料存在一些問題,
 *      因為二進位制資料可能包含不可列印字元或特殊字元,這些字元在某些傳輸媒介(如電子郵件、URL等)中可能會被誤解或破壞。
 *  為什麼要使用Base64?
 *      可讀性和可傳輸性:
 *          Base64編碼將二進位制資料轉換為可列印的ASCII字元,確保資料在傳輸過程中不被改變。
 *          許多傳輸協議(如SMTP郵件協議)只支援文字資料,Base64編碼使得二進位制資料可以透過這些協議傳輸。
 *
 *      避免資料損壞:
 *          某些傳輸通道可能會對非文字資料進行處理或修改,Base64編碼可以避免這些問題。
 *
 *      相容性:
 *          Base64編碼後的資料可以在不同系統和平臺之間無縫傳輸,而不會受到字符集或編碼方式的影響。
 *
 *  Base64的應用:電子郵件附件、Web資料傳輸(圖片、檔案等)、資料儲存(某些資料庫)、加密和簽名
 */

/**Base64基本原理
 * Base64(64個字元) -->2^6即6個位元-->其字元編碼範圍為0~63
 * -->"A-Z" "a-z" "0~9" "+" "/"
 * -->'A-Z' -- 編碼範圍 0~25
 * -->'a-z' -- 編碼範圍 26~51
 * -->'0-9' -- 編碼範圍 52~61
 * -->'+'   -- 編碼範圍 62
 * -->'/'   -- 編碼範圍 63
 *
 * 注意, 最後不足4字元會以'='補齊
 *
 * 例如: 將ASCII碼的"Man"轉換為Base64編碼為TWFu
 *  M---->ASCII對應值: A(65)+12 = 77  --->十六進位制:0x4D--->二進位制:0100 1101
 *  a---->ASCII對應值: a(97)+0  = 97  --->十六進位制:0x61--->二進位制:0110 0001
 *  n---->ASCII對應值: a(97)+13 = 110 --->十六進位制:0x6E--->二進位制:0110 1110
 *
 *  在記憶體中(ASCII)        :0100 1101 0110 0001 0110 1110
 *  6位劃分為Base64        :  010011|  010110|  000101|  101110
 *  但記憶體是以位元組為單位    :00010011|00010110|00000101|00101110
 *  Base64顯示             : T(19)   W(22)   F(5)     u(46)
 *
 *  所以每3個位元組ASCII碼會生成4個位元組的Base編碼
 */
/***************************************標頭檔案***************************************/
#include <stdio.h>
#include <string.h>
/***************************************END******************************************/
// 查詢Base64字元在表中的位置, 進行數值還原
int base64_char_value(char c);
/**將字串進行Base64編碼
 * @name      base64_encode
 * @brief     將字串進行Base64編碼
 * @param     input 輸入的ASCII字串
 * @param     output  得到的base64編碼
 * @date      2024年6月12日
 * @version   1.0
 * @note
 */
void base64_encode(const unsigned char *input, char *output);
/**將字串進行Base64解碼
 * @name      base64_decode
 * @brief     將字串進行Base64解碼
 * @param     input 輸入的base64編碼
 * @param     output  得到的ASCII字串
 * @date      2024年6月12日
 * @version   1.0
 * @note
 */
void base64_decode(const char *input, unsigned char *output);
#endif

base64.c

/**
 * @file name : base64.c
 * @brief     : 用於將位元組進行base64編碼或解碼
 * @author    : RISE_AND_GRIND@163.com
 * @date      : 2024年6月12日
 * @version   : 1.0
 * @note      :
 * CopyRight (c)  2023-2024   RISE_AND_GRIND@163.com   All Right Reseverd
 */

#include "../../include/utilities/base64.h"

// Base64字元表, 用於編碼和解碼 static限制為檔案域
static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// 查詢Base64字元在表中的位置, 進行數值還原
int base64_char_value(char c)
{
    if (c >= 'A' && c <= 'Z') // 'A'到'Z'的值為0到25
        return c - 'A';
    if (c >= 'a' && c <= 'z') // 'a'到'z'的值為26到51
        return c - 'a' + 26;
    if (c >= '0' && c <= '9') // '0'到'9'的值為52到61
        return c - '0' + 52;
    if (c == '+') // '+'的值為62
        return 62;
    if (c == '/') // '/'的值為63
        return 63;
    return -1; // 非Base64字元返回-1
}

/**將字串進行Base64編碼
 * @name      base64_encode
 * @brief     將字串進行Base64編碼
 * @param     input 輸入的ASCII字串
 * @param     output  得到的base64編碼
 * @date      2024年6月12日
 * @version   1.0
 * @note
 */
void base64_encode(const unsigned char *input, char *output)
{
    int length = strlen((const char *)input); // 計算輸入字串的長度
    int i = 0;                                // 計數器
    int j = 0;
    unsigned char a3[3] = {0}; // 每次輸入3個ASCII碼位元組緩衝區, 3個ASCII碼可生成4個Base64編碼
    unsigned char a4[4] = {0}; // 每次輸出4個Base64字元緩衝區

    while (length--) // 輸入字串的長度每次載入3個位元組, 即長度每次-3
    {
        a3[i++] = *(input++); // 載入字元到輸入陣列中
        if (i == 3)           // 若輸入了3個字元, 則觸發編碼
        {
            /**                           a3[0]     a3[1]     a3[2]
             *  在記憶體中(ASCII)        :0100 1101 0110 0001 0110 1110
             *  6位劃分為Base64        :  010011|  010110|  000101|  101110
             */
            a4[0] = (a3[0] & 0xfc) >> 2;                           // a3[0] & 11111100 取a3[0]的前6位
            a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); // 取a3[0]的後2位和a3[1]的前4位
            a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); // 取a3[1]的後4位和a3[2]的前2位
            a4[3] = a3[2] & 0x3f;                                  // 取a3[2]的後6位

            for (i = 0; i < 4; i++) // 每次輸出4個編碼後的字元
            {
                *output++ = base64_table[a4[i]]; // 將編碼後的Base64值對映到Base64字元表, 轉換為ASCII碼的形式顯示(只是顯示, 內容是Base64)
            }
            i = 0; // 重置計數器
        }
    }

    if (i) // 若位數不足3個位元組
    {
        for (j = i; j < 3; j++) // 用0填充不足的位元組部分
        {
            a3[j] = '\0';
        }
        // 同上操作
        a4[0] = (a3[0] & 0xfc) >> 2;
        a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
        a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
        a4[3] = a3[2] & 0x3f;
        // 將已編碼好的部分輸出, +1是因為若只有一個位元組也至少生成2個字元, 始終會+1
        for (j = 0; j < (i + 1); j++)
        {
            *output++ = base64_table[a4[j]];
        }

        while ((i++ < 3))
        {
            *output++ = '='; // 用'='填充不足的部分並輸出
        }
    }

    *output = '\0'; // 輸出字串完畢
}

/**將字串進行Base64解碼
 * @name      base64_decode
 * @brief     將字串進行Base64解碼
 * @param     input 輸入的base64編碼
 * @param     output  得到的ASCII字串
 * @date      2024年6月12日
 * @version   1.0
 * @note
 */
void base64_decode(const char *input, unsigned char *output)
{
    int i = 0; // 計數器
    int j = 0;
    unsigned char a4[4] = {0}; // 每次輸入4個Base64字元緩衝區
    unsigned char a3[3] = {0}; // 每次輸出3個ASCII碼位元組緩衝區, 4個Base64編碼可生成3個ASCII碼

    while (*input)
    {
        for (i = 0; i < 4; i++) // 以4個base字元為單位進行處理
        {
            if (*input == '=') // 若是末尾
            {
                a4[i] = 0; // 轉換為'\0'
            }
            else // 若不是末尾
            {
                a4[i] = base64_char_value(*input);
            }
            input++; // 繼續讀入1個字元
        }

        a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);          // 解碼第一個位元組 取消移位
        a3[1] = ((a4[1] & 0x0f) << 4) + ((a4[2] & 0x3c) >> 2); // 解碼第二個位元組
        a3[2] = ((a4[2] & 0x03) << 6) + a4[3];                 // 解碼第三個位元組

        for (j = 0; j < 3; j++)
        {
            *output++ = a3[j]; // 將解碼後的位元組寫入輸出
        }
    }

    *output = '\0'; // 結束字串
}

#if 0
int main()
{
    // 測試Base64編碼
    const char *input_encode = "我愛中國"; // 正確值應該是"5oiR54ix5Lit5Zu9"
    char output_encode[50];                // 需要足夠大的陣列來儲存Base64編碼後的字串
    base64_encode((const unsigned char *)input_encode, output_encode);
    printf("Base64 Encoded: %s\n", output_encode);

    // 測試Base64解碼
    const char *input_decode = "6Zey5p2l5peg5LqL5pG46bG85pe2LCDlip7lhazlrqTlhoXmhI/mrLLnprvjgIIKIOaJi+aPoem8oOagh+i9u+i9u+a7kSwg55y86KeC5bGP5bmV5b+D6Ieq5Zek44CCCiDlkIzkuovlv5nnoozlv5nlvpfntK8sIOaIkeWNtOmXsumAuOW/g+asouWWnOOAggog5oyH5bCW6L275pWy6ZSu55uY5aOwLCDpsbzmuLjmsLTkuK3ku7vmhI/lrInjgIIKIOS4iuWPuOW/veeEtuS4tOaXtuiHsywg5oiR5YyG5b+Z5oqK6bG86JeP56We56eY44CCCiDpnaLlrrnkuKXogoPnnLzlpoLnlLUsIOe6teeEtuaIkeW/g+aFjOWmgum4oeOAggog5pil6aOO5ouC6Z2i5b+Y5Yqz6IumLCDor5fmhI/nm47nhLblv4PmrKLkuZDjgIIKIOS4gOeJh+i9u+advuWcqOW/g+WktCwg5pG46bG85Ly85LmO5peg56m35LmQ44CC";
    unsigned char output_decode[1024]; // 需要足夠大的陣列來儲存解碼後的資料
    base64_decode(input_decode, output_decode);
    printf("Base64 Decoded:\n %s\n", output_decode);

    return 0;
}
#endif

相關文章