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中再新增關於檔案處理的部分即可。