一、背景
公司業務中呼叫介面需要透過閘道器轉換,閘道器內的一些規則導致*在傳遞的過程中存在問題,所以決定使用base64進行編碼處理。
在使用過程中發現閘道器對'='也有處理,導致我的傳參到介面方會丟失掉最後的等號,但是經過解碼後的竟然是正確的!
於是發現這麼長的時間,經常使用base64,但是對這個編碼的原理並不理解,所以就有了這篇學習記錄。
二、基礎知識瞭解
1.常見的編碼:
- ASCII(American Standard Code for Information Interchange,美國資訊交換標準程式碼)
- UTF-8(8-bit Unicode Transformation Format)可用1~4個位元組表示一個字元(unicode的實現方式)
- GBK 漢字內碼擴充套件規範
2.計算機本質都是二進位制,最小的資料單位是位元bit,一個位元組有8個bit。
3.base64作用:計算機中任何資料都是按ascii碼儲存的,而ascii碼的128~255之間的值是不可見字元。而在網路上交換資料時,比如說從A地傳到B地,往往要經過多個路由裝置,由於不同的裝置對字元的處理方式有一些不同,這樣那些不可見字元就有可能被處理錯誤,這是不利於傳輸的。所以就先把資料先做一個Base64編碼,統統變成可見字元,降低錯誤率。
三、編碼原理
是基於A-Z、a-z、0-9以及'+' 和'/'共64個字元的編碼方式,因為2的6次方等於64,所以說只需要6個位元即可表示一個base64的字元。
編碼表:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
核心原理是將二進位制資料進行分組,以6位一組進行分組,並在每組前面都填兩個高位 0,然後將 8 bit 的位元組轉換成十進位制,對照 BASE64 編碼表 (上表),得到對應編碼後的字元。
四、等號是哪裡來的?
一個位元組是8bit,一個base64的字元6bit,24是最小公倍數,所以3個位元組可以完整轉化為4個base64字元;
但是我們無法控制需要編碼的資料正好是3的倍數,所以要進行補零---在不足3的倍數的字串末尾用0x00進行填充;
因為base64編碼中的下標0對應的字元是'A',而末尾填充上的0x00在分組補零後同樣是下標0x00,這樣就無法分辨出到底是末尾填充的0x00還是二進位制資料中的0x00。
所以引進了等號,這就是'='字元不在Base64字符集中,但是也出現在Base64編碼的原因了。
五、編碼過程
以對6666P進行base64編碼的步驟說明
每個字元轉化為8bit:
6----->00110110
6----->00110110
6----->00110110
6----->00110110
P----->01010000
補位----->00000000
整體拼接結果:
001101100011011000110110001101100101000000000000
以6bit一組進行分割:
001101 100011 011000 110110 001101 100101 000000 000000
轉化為base64編碼腳標:
13 35 24 54 13 37 0 0
獲取對應的base64編碼:
N j Y 2 N l A A
得到結果:string(8) "NjY2NlA="
六、關於解碼
由編碼過程可知,編碼的結果的長度一定是4的倍數,所以當解碼的長度不等於4的倍數時,需要用等號進行補位。
這也就是去掉末尾的等號後進行解碼得出的原文不會出錯的原因了!
瞭解過編碼過程後逆向回去即為解碼過程,在此就不再詳細敘述了。
七、base64的變種
但是標準的base64並不能滿足所有場景的需要,比如URL編碼器會把中的“/”和“+”字元變為形如“%XX”的形式,所以也就出現了實現思路一致的編碼變種:
(1)適應url的變種改進:不在末尾填充’=’號,並將“+”和“/”分別改成了“-”和“_”
(2)適應正規表示式的變種:將“+”和“/”改成了“!”和“-”
八、實踐程式碼
<?php
$a = "6666P";
$decbinStr= '';
//計算補位
$end = (3 - strlen($a)%3)%3;
for($i=0;$i<strlen($a);$i++){
//每個字元轉化為8bit
$decbin = str_pad(decbin(ord($a[$i])),8,"0",STR_PAD_LEFT);
$decbinStr .= $decbin ;
}
//增加補位
$decbinStr = $decbinStr. str_pad("",$end*8,"0");
//以6bit一組進行分割
$arr = str_split($decbinStr,6);
//轉化成對應的base64編碼
$result = implode("", array_map("getBase64Str",$arr));
//末尾補位的資料處理
for($j=0;$j<$end;$j++){
if($result[strlen($result)-1-$j] == 'A'){
$result[strlen($result)-1-$j] = "=";
}
}
//驗證下跟自帶的base64_encode結果是否一致
var_dump($result);
var_dump(base64_encode($a));
exit();
function getBase64Str($sixStr){
$number = bindec($sixStr);
$baseHash = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
return $baseHash[$number];
}
當然也可以使用位運算等方式進行實現,以上程式碼僅代表個人的思路。
本作品採用《CC 協議》,轉載必須註明作者和本文連結