1. 程式人生 > >base64編解碼原理與C程式碼實現

base64編解碼原理與C程式碼實現

1、base64編碼原理分析:

(1)、背景與應用:
可參考部落格:Base64編碼原理與應用
所謂base64就是基於ASCII碼的64個可見字元子集的一種編碼方式。

(2)、編碼原理與核心分析:
上面提到的子集如下所示:
這裡寫圖片描述

A~Z、a~z、0~9、+、/共64個字元。

base64編碼方式要點

①分組:對需要編碼的資料流(位元組流)進行分組,三個位元組為一組,若是最後一組不足3個位元組,則對該分組後面補充0補足3個位元組。然後將3byte(3 * 8=24bit)等分為4部分,每部分6bit(4 * 6=24bit)。將每一部分的6bit作為一個位元組的低6位,高2位均填充0。即將3個位元組變為4個位元組。
②替換:

由於6bit可表示最大值為63(111111),將6bit的資訊作為Base64編碼表的索引,索引到的字元即為該6bit資訊編碼得到的編碼結果。因此3byte編碼會得到4個字元。
③結尾處理:對於資料流中分組產生的6bit,如果為全0,即索引到的字元為‘A’,但是對於結尾不滿3byte而補充的0,分組要進行特殊處理,用’=’來替換(由於’=’只在結尾可能存在,因此不算入編碼表中)。

舉例分析:
0110 0001 0100 0000,該二進位制流(位元組流)共2個位元組,其ASCII碼的字元分別為a、@。其base64編碼結果為“YUA=”。
這裡寫圖片描述

又如:0110 0001 0100 0000 0110 0001 0110 0001的編碼結果為“YUBhYQ==”。
這裡寫圖片描述

(3)、解碼:
對於解碼,其實就是編碼的逆過程:將得到的base64編碼資訊分為4位元組一組,由4個字元值反推出其索引,然後將四個索引的低6bit共24bit拼接成完整的三個位元組,得到24bit資料流(可能還需要一步:將3個位元組的ASCII字元再算出來)。

需要注意:在程式設計時,由於逆向解碼,對於已知字元求其索引,需要進行整個表的遍歷,若需要解密的資料較大,則其效率是相當低的。因此我們可以做一個逆向索引的表,該表以64個字元的ASCII作為索引index,而以正向索引表的索引作為逆向索引表的值。如下為正向編碼索引表(64個有效值)

static const char base64[64
] = { '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', '+', '/' };

而逆向解碼索引表如下所示(65個有效的值)

static const char base64_back[128] = 
{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
};

2、程式碼編寫與程式測試:

以下程式採用命令列輸入的方式編解碼進行測試:

(1)輸入格式處理程式檔案:

//fileName:formatDeal.cpp
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "base64.h"

int main(int argc, char **argv)
{
    if(argc != 3){
        printf("number of parameter error:\n"); 
        printf("\t./base64 -d(e) text\n");
        exit(EXIT_FAILURE);
    }

    int argv_e = strcmp(argv[1], "-e");
    int argv_d = strcmp(argv[1], "-d");

    if( !argv_e ){//text encrypt
        base64_encrypt_text(argv[2], strlen(argv[2]));  
    }
    else if( !argv_d ){//text decrypt
        base64_decrypt_text(argv[2], strlen(argv[2]));  
    }
    else{   
        printf("type of parameter error:\n");   
        printf("\t./base64 -d(e) text\n");
        exit(EXIT_FAILURE);
    }
    return 0;
}

(2)base64編碼程式檔案:

/*****************************************************************
 *  FileName:base64.cpp
 *  Time:  2017-12-11 20:54:21
 *  Author: KangRuojin
 *  Mail: [email protected]
 *  Version: v1.1
 * 
 *  c - ciphertext
 *  p - plaintext
******************************************************************/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "base64.h"

static const char base64[64] = 
{
    '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', '+', '/'
};
/*
 *  以base64的編碼值作為索引進行原index的索引.即逆轉換表
 *  首先,A~Z、a~z、0~9、+、/共64個符號
 *  這64個符號(ASCII)作為base64_back[]的索引,其在base64[]中的索引作為base64_back[]的索引值
 *  其次,由於編碼中'=(ASCII為61)'是由補的0轉換成的,所以在逆轉換表中index=61的位置,值為0
 *  其他index值均為-1
*/
static const char base64_back[128] = 
{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
};

void base64_encrypt_text(const char * pbuf,int plen)
{
    int clen = (plen % 3) ? (plen/3 + 1) : (plen/3);

    char * buf = (char *)malloc(clen*3);
    char * cbuf = (char *)malloc(clen * 4);
    if(NULL == cbuf || NULL == buf){
        exit(EXIT_FAILURE);
    }
    memset(cbuf, 0, clen*4);
    memset(buf, 0, clen*3);
    memcpy(buf, pbuf, plen);

    //編碼轉換
    for(int i = 0; i < clen; i++){
        base64_encrypt(&buf[i*3], &cbuf[i*4]);
    }
    //由於對於0進行了統一的base6_encrypt(),所以需要對末尾的'A'進行修正為'='
    if(plen % 3 == 2){//只補一個位元組,對應一個'='
        cbuf[clen*4 - 1] = '=';
    }
    else if(plen % 3 == 1){//補兩個位元組則對應兩個'='
        cbuf[clen*4 - 1] = cbuf[clen*4 - 2] = '=';
    }
    show_base64(cbuf, clen*4);
    free(buf);
    free(cbuf);
}
void base64_decrypt_text(const char * cbuf,int clen)
{
    int plen = clen/4;

    char * pbuf = (char *)malloc(plen*3);

    if(NULL == pbuf){
        exit(EXIT_FAILURE);
    }
    memset(pbuf, 0, plen*3);

    for(int i = 0; i < plen; i++){
        base64_decrypt(&cbuf[i*4], &pbuf[i*3]);
    }
    show_base64(pbuf, plen*3);
    free(pbuf);
}
void base64_encrypt(const char * pbuf, char * cbuf)
{
    int temp = (pbuf[0] << 16) | (pbuf[1] << 8) | (pbuf[2] << 0);
    for(int i = 0; i<4; i++){
        int index = (temp >> (18-i*6)) & 0x3F;
        cbuf[i] = base64[index];
    }
}
void base64_decrypt(const char * cbuf, char * pbuf)
{
    int temp = 0;
    for(int i = 0; i<4; i++){//反向索引,根據編碼求得原index並進行移位異或
        temp |= base64_back[cbuf[i]] << (18-6*i);//temp的高1byte未使用
    }
    for(int i = 0; i<3; i++){//移位異或得到的temp的低三byte分別為原有三個byte
        pbuf[i] = (temp >> (16-i*8)) & 0XFF;
    }
}
void show_base64(const char * buf, int len)
{
    for(int i=0; i<len; i++){
        printf("%c",buf[i]);
    }
    printf("\n");
}

測試結果(可能存在指令碼識別問題,所以text引數需要加單引號):
這裡寫圖片描述

此外,base64還用於圖片編碼,圖片編碼屬於檔案處理,需要在formatDeal.cpp和base64.cpp中再新增關於檔案處理的部分即可。