首頁 > 軟體

一文講清base64編碼原理

2023-03-02 18:01:08

前言

平靜之下,驀然回首,base64 卻在燈火闌珊處。

今天翻開舊專案發現挺多圖片相關的外掛都是用 base64 來顯示圖片的。談到 base64,腦海遐想翩翩,思緒迴盪之下 base64 瑕瑜互見。本文旨在記錄工作中遇見的問題並加以總結,如有不妥請指正~

base64 由來

base64 是網路傳輸 8Bit 位元組程式碼的編碼方式之一,是一種基於 64 個可列印字元來表示二進位制資料的方法。在做支付系統時,報文互動都需要使用 base64 對明文進行轉碼,然後再進行簽名或加密,之後再進行(或再次 base64 轉碼)傳輸。那麼,base64 到底起到什麼作用呢?

在引數傳輸的過程中經常遇到的一種情況:使用全英文的字串沒問題,但一旦涉及到中文就會出現亂碼的情況。與此類似,網路上傳輸的字元並不全是可列印的字元,比如二進位制檔案、圖片等。base64 的出現就是為了解決此問題,它是基於 64 個可列印的字元來表示二進位制的資料的一種方法。

電子郵件剛問世的時候,只能傳輸英文,但後來隨著使用者的增加,中文、日韓俄文等文字的使用者也有需求,但這些字元並不能被伺服器或閘道器有效處理,因此 base64 就登場了。隨後,base64 在 URL、Cookie、網頁傳輸少量二進位制檔案中也有相應的使用。

base64 的編碼原理

基於a-zA-Z0-9+/這 64 個字元來標識二進位制資料,另外=符號用於當位元組缺位時補用。

base64 編碼對照表

base64 編碼對照表

索引

對應字元

索引

對應字元

索引

對應字元

索引

對應字元

0

A

17

R

34

i

51

z

1

B

18

S

35

j

52

0

2

C

19

T

36

k

53

1

3

D

20

U

37

l

54

2

4

E

21

V

38

m

55

3

5

F

22

W

39

n

56

4

6

G

23

X

40

o

57

5

7

H

24

Y

41

p

58

6

8

I

25

Z

42

q

59

7

9

J

26

a

43

r

60

8

10

K

27

b

44

s

61

9

11

L

28

c

45

t

62

+

12

M

29

d

46

u

63

/

13

N

30

e

47

v

14

O

31

f

48

w

15

P

32

g

49

x

16

Q

33

h

50

y

base64 的編碼轉換規則

base64 要求把每三個 8Bit 的位元組轉換四個 6Bit 的位元組(3*8 = 4*6 = 24),然後把 6Bit再添兩位高位 0,組成四個 8Bit 的位元組(4*8=32)。

為什麼使用 3 個位元組一組呢?因為 6 和 8 的最小公倍數為 24,三個位元組正好 24 個二進位制位,每 6 個 bit 位一組,恰好能夠分為 4 組。

同時用於分組後每組新增兩個高位 0,轉換後的字串理論上將要比原來的字元長 1/3(24/32=1/3)

  • 步驟分解:

    將待轉換的字串每三個字元分為一組,每個字元位元組佔 8bit,那麼共有 24 個二進位制位。

    將 24 個二進位制位每 6 個位元組為一組,共分為 4 組。

    在每組 6 位元組前面新增兩個 0,每組由 6 位元組變為 8 位元組二進位制位,組成總共 32 個二進位制位,即四個位元組。

    根據 base64 編碼對照表獲得對應的值。

  • 舉個栗子

    • 以標準 3 個字元LJY為例。

    LJY 對應的 ASCII 碼值分別為 76、74、89,對應的二進位制值是 01001100、01001010、01011001。由此組成一個 24 位的二進位制位字串。

    將 24 位的二進位制位字串,按照每 6 位二進位制位一組分成 4 組。

    對 4 組 每組 6 位二進位制位字串前面補兩個 0,每組擴充套件為 8 位二進位制位,4 組共擴充套件成 32 個二進位制位,此時 4 組二進位制位分別為:00010011、00000100、00101001、00011001。其對應的 base64 編碼索引為:19、4、41、25。

    用 base64 編碼索引值在 base64 編碼表中進行查詢,分別對應:T、E、p、Z。

    • 因此LJYbase64 編碼之後就變為:TEpZ。
|   文字           |    L     |    J     |    Y     |
|  ASCII          |    76    |    74    |    89    |
| 二進位制位         | 01001100 | 01001010 | 01011001 |
| 分組二進位制       | 010011   | 000100   | 101001   | 011001   |
| 分組二進位制補2個0 | 00010011 | 00000100 | 00101001 | 00011001 |
| 分組索引         |   19     |   4      |   41     |   25     |
| base64編碼       |   T     |   E      |    p      |   Z      |

主要展示:
轉換前二進位制位: 01001100 01001010 01011001
轉換後二進位制位: 00010011 00000100 00101001 00011001
複製程式碼
  • 字元位數不足情況

上述栗子是面向剛好三個字元為一組的情況。當然不是所有時候都這麼巧字元位數足夠,除此以外有位數不足的情況。那麼,面對字元位數不足的情況下該如何處理呢?

base64 給出的方案是,當每組字元不足三位時,不足位數位置需要使用=符號補上。

位數不足情況處理情景:

  • 位數缺一個位元組:一個位元組共 8 個二進位制位,依舊按照規則進行分組。此時共 8 個二進位制位,每 6 個一組,則第二組缺少 4 位,用 0 補齊,得到兩個 base64 編碼,而後面兩組沒有對應資料,都用=補上。
  • 位數缺兩個位元組:兩個位元組共 16 個二進位制位,依舊按照規則進行分組。此時總共 16 個二進位制位,每 6 個一組,則第三組缺少 2 位,用 0 補齊,得到三個 base64 編碼,第四組完全沒有資料則用=補上。

位數不足圖解如下:

<!-- 缺2位字元,字串以A為例轉換base64後位QQ== -->
|   文字(1Byte)  |    A     |          |          |
| 二進位制位         | 01000001 |          |          |
| 分組二進位制       | 010000   | 010000   |          |          |
| 分組二進位制補0    | 00010000 | 00010000 |          |          |
| 分組索引         |   16     |   16     |          |          |
| base64編碼       |   Q     |   Q      |    =      |   =      |

<!-- 缺1位字元,字串以AB為例轉換base64後位QUI= -->
|   文字(1Byte)  |    A     |     B    |          |
| 二進位制位         | 01000001 | 01000010 |          |
| 分組二進位制       | 010000   | 010100   |  001000  |          |
| 分組二進位制補0    | 00010000 | 00010100 | 00001000 |          |
| 分組索引         |   16     |   20     |    8      |          |
| base64編碼       |   Q     |   U      |    I      |   =      |
複製程式碼

列舉了一個字元到三個字元轉換為 base64 ,可以發現將 base64 就是按照 base64 編碼對照表來將二進位制轉換為字串,使得資料不能直接明文展示出來,但也算不上是加密,而這巧好可用在傳輸、儲存、表示二進位制領域的情景。

  • 另外值得注意的是,不用語言如中文有多種編碼(比如:utf-8、gb2312、gbk 等),不同編碼對應 base64 編碼結果都不一樣。
  • 其次在推演過程中可發現 base64 即用 6 位位元組(2 的 6 次冪就是 64)表示字元同理,Base32 就是用 5 位位元組,Base16 就是用 4 位位元組。大家可以按照上面的步驟進行演化測試。

base64 優缺點

知道 base64 是什麼後,也該到為什麼出現了。為什麼要是使用 base64 呢,這要從其優缺點入手來選擇適合場景了。

  • 優勢:
    • base64 適合不同平臺、不同語言的傳輸;
    • 頁面中內嵌使用 base64 格式的小圖片,可減少了伺服器存取次數;
    • 二進位制位轉換 base64 演演算法簡單,對效能影響不大;
  • 缺點
    • 二進位制檔案轉換為 base64 後,體積大概增加 1/3;
      • 在基於 Android6.0 及以下預設瀏覽器實測場景中發現,某些機型如中興上傳 base64 圖片會因為字元大小過大導致上傳奔潰的情況。
      • 字元長度過大的 base64 不適應使用在 URL 情景,因為 IOS 端瀏覽器會限制 URL 長度,當長度超過時會自動切除多餘部分,導致資料丟失。
      • base64 字元過大會導致頁面載入速度變慢,因此建議 10kb 以下的圖片使用。
    • base64 無法快取,要快取只能快取包含 base64 的檔案,比如 js 或者 css;
    • 面對大檔案時,會消耗一定的 CPU 進行編解碼

JavaScript 的 base64 轉碼方法

Web API 二進位制與 base64 轉換

  • atob(encodedData) : 解碼一個 base64 編碼的字串。

enCodedData,是一個通過 btoa() 方法編碼的字串, 為二進位制字串包含 base64 編碼的資料。並返回包含來自 encodedData 的解碼資料的 ASCII 字串。

  • btoa(stringToEncode) : 建立一個 bas64 編碼的字串。

stringToEncode 為要編碼的二進位制字串。並返回包含 stringToEncode 的 base64 表示形式的 ASCII 字串。

另外在 JavaScript 中,字串使用 UTF-16 字元編碼表示:在這種編碼中,字串表示為 16 位(2 位元組)單元的序列。每個 ASCII 字元都可以放入其中一個單元的第一個位元組,但許多其他字元不能。

base64 在設計上需要二進位制資料作為其輸入。就 JavaScript 字串而言,這意味著每個字元只佔用一個位元組的字串。因此,如果將一個字串傳遞到 btoa()中,其中包含佔用多個位元組的字元,則會出現錯誤,因為這不被視為二進位制資料,因此超 16 位字元在使用 btoa()時需要先對字元轉碼為二進位制位。

// 簡單資料
const encodedData = btoa('Hello, world'); // encode a string
const decodedData = atob(encodedData); // decode the string

/* 複雜資料 */
// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
  const codeUnits = new Uint16Array(string.length);
  for (let i = 0; i < codeUnits.length; i++) {
    codeUnits[i] = string.charCodeAt(i);
  }
  const charCodes = new Uint8Array(codeUnits.buffer);
  let result = '';
  for (let i = 0; i < charCodes.byteLength; i++) {
    result += String.fromCharCode(charCodes[i]);
  }
  return result;
}
function fromBinary(binary) {
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  const charCodes = new Uint16Array(bytes.buffer);
  let result = '';
  for (let i = 0; i < charCodes.length; i++) {
    result += String.fromCharCode(charCodes[i]);
  }
  return result;
}

// a string that contains characters occupying > 1 byte
const myString = '☸☹☺☻☼☾☿';

const converted = toBinary(myString);
const encoded = btoa(converted);
console.log(encoded); // OCY5JjomOyY8Jj4mPyY=

const decoded = atob(encoded);
const original = fromBinary(decoded);
console.log(original); // ☸☹☺☻☼☾☿
  • 相容性:atob() 方法不支援 IE9 及更早的 IE 版本。

base64 轉二進位制

// base64編碼表
const map = {
  0: 52,
  1: 53,
  2: 54,
  3: 55,
  4: 56,
  5: 57,
  6: 58,
  7: 59,
  8: 60,
  9: 61,
  A: 0,
  B: 1,
  C: 2,
  D: 3,
  E: 4,
  F: 5,
  G: 6,
  H: 7,
  I: 8,
  J: 9,
  K: 10,
  L: 11,
  M: 12,
  N: 13,
  O: 14,
  P: 15,
  Q: 16,
  R: 17,
  S: 18,
  T: 19,
  U: 20,
  V: 21,
  W: 22,
  X: 23,
  Y: 24,
  Z: 25,
  a: 26,
  b: 27,
  c: 28,
  d: 29,
  e: 30,
  f: 31,
  g: 32,
  h: 33,
  i: 34,
  j: 35,
  k: 36,
  l: 37,
  m: 38,
  n: 39,
  o: 40,
  p: 41,
  q: 42,
  r: 43,
  s: 44,
  t: 45,
  u: 46,
  v: 47,
  w: 48,
  x: 49,
  y: 50,
  z: 51,
  '+': 62,
  '/': 63,
};

function base64to2(base64) {
  let len = base64.length * 0.75; // 轉換為int8array所需長度
  base64 = base64.replace(/=*$/, ''); // 去掉=號(佔位的)

  const int8 = new Int8Array(len); //設定int8array檢視
  let arr1,
    arr2,
    arr3,
    arr4,
    p = 0;

  for (let i = 0; i < base64.length; i += 4) {
    arr1 = map[base64[i]]; // 每次迴圈 都將base644個位元組轉換為3個int8array直接
    arr2 = map[base64[i + 1]];
    arr3 = map[base64[i + 2]];
    arr4 = map[base64[i + 3]];
    // 假設資料arr 資料 00101011 00101111 00110011 00110001
    int8[p++] = (arr1 << 2) | (arr2 >> 4);
    // 上面的操作 arr1向左邊移動2位 變為10101100
    // arr2 向右移動4位元:00000010
    // | 為'與'操作: 10101110
    int8[p++] = (arr2 << 4) | (arr3 >> 2);
    int8[p++] = (arr3 << 6) | arr4;
  }
  return int8;
}

base64 轉成 Blob

// base64圖片轉blob
function base64toBlob(base64) {
  var arr = base64.split(','),
    mime = arr[0].match(/:(.*?);/)[1] || 'image/png',
    bstr = atob(arr[1]), // 將base64轉為Unicode規則編碼
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n); // 轉換編碼後才可以使用charCodeAt 找到Unicode編碼
  }
  return new Blob([u8arr], { type: mime });
}

/* 優化版 */
function base64ToBlob(base64) {
  var arr = base64.split(',');
  var mime = arr[0].match(/:(.*?);/)[1] || 'image/png';
  // 去掉url的頭,並轉化為byte
  var bytes = window.atob(arr[1]);
  // 處理異常,將ascii碼小於0的轉換為大於0
  var ab = new ArrayBuffer(bytes.length);
  // 生成檢視(直接針對記憶體):8位元無符號整數,長度1個位元組
  var u8arr = new Uint8Array(ab);

  for (var i = 0; i < bytes.length; i++) {
    u8arr[i] = bytes.charCodeAt(i);
  }

  return new Blob([u8arr], { type: mime });
}

相關文獻

到此這篇關於一文講清base64編碼原理的文章就介紹到這了,更多相關base64編碼內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com