base64 編碼原理

caohaoyu發表於2019-09-25

一、背景

公司業務中呼叫介面需要透過閘道器轉換,閘道器內的一些規則導致*在傳遞的過程中存在問題,所以決定使用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 協議》,轉載必須註明作者和本文連結

相關文章