簡體   English   中英

base64.decode: 填充前編碼無效

[英]base64.decode: Invalid encoding before padding

我正在做一個 flutter 項目,目前我嘗試使用 base64.decode() 方法解碼的一些字符串出現錯誤。 我創建了一個簡短的飛鏢代碼,可以重現我在使用特定字符串時遇到的問題:

import 'dart:convert';

void main() {
  final message = 'RU5UUkVHQUdSQVRJU1==';
  print(utf8.decode(base64.decode(message)));
}

我收到以下錯誤消息:

Uncaught Error: FormatException: Invalid encoding before padding (at character 19)
RU5UUkVHQUdSQVRJU1==

我試過用 JavaScript 解碼相同的字符串並且它工作正常。 如果有人能解釋為什么我會收到此錯誤,並可能向我展示解決方案,我會很高興。 謝謝。

Base64 編碼將二進制數據分解為 3 個完整字節的 6 位段,並將這些段表示為 ASCII 標准中的可打印字符。 它基本上分兩步完成。

第一步是將二進制字符串分解為 6 位塊。 Base64 僅使用 6 位(對應於 2^6 = 64 個字符)來確保編碼數據可打印且可讀。 沒有使用 ASCII 中可用的特殊字符。

64 個字符(因此命名為 Base64)是 10 個數字、26 個小寫字符、26 個大寫字符以及加號 (+) 和正斜杠 (/)。 還有一個第 65 個字符稱為填充,即等號 (=)。 當二進制數據的最后一段不包含完整的 6 位時使用此字符

所以 RU5UUkVHQUdSQVRJU1== 不遵循編碼模式。

使用下划線字符“_”作為填充字符並刪除填充字節進行解碼

出於某種原因, dart:convertbase64.decode在用=填充的字符串上阻塞,並出現“填充錯誤前的無效編碼”。 即使您使用包自己的填充方法base64.normalize會使用正確的填充字符=填充字符串,也會發生這種情況。

=確實是 base64 編碼的正確填充字符。 當輸入組中可用的位數少於 24 位時,它用於填充 base64 字符串。 請參閱RFC 4648,第 4 節

但是, RFC 4648 第 5 節是 Urls 的 base64 編碼方案,它使用下划線字符_作為填充而不是=來確保 Url 安全。

使用_作為填充字符將導致base64.decode解碼無誤。

為了進一步將生成的字節列表解碼為 Utf8,您需要刪除填充字節,否則您將收到“無效的 UTF-8 字節”錯誤。

請參閱下面的代碼。 這是與工作 dartpad.dev 示例相同的代碼。

    import 'dart:convert';

void main() {
  //String message = 'RU5UUkVHQUdSQVRJU1=='; //as of dart 2.18.2 this will generate an "invalid encoding before padding" error
  //String message = base64.normalize('RU5UUkVHQUdSQVRJU1'); // will also generate same error

  String message = 'RU5UUkVHQUdSQVRJU1';
  print("Encoded String: $message");
  print("Decoded String: ${decodeB64ToUtf8(message)}");
}

decodeB64ToUtf8(String message) {
  message =
      padBase64(message); // pad with underline => ('RU5UUkVHQUdSQVRJU1__')
  List<int> dec = base64.decode(message);
  //remove padding bytes
  dec = dec.sublist(0, dec.length - RegExp(r'_').allMatches(message).length);
  return utf8.decode(dec);
}

String padBase64(String rawBase64) {
  return (rawBase64.length % 4 > 0)
      ? rawBase64 += List.filled(4 - (rawBase64.length % 4), "_").join("")
      : rawBase64;
}


根據RFC 4648 ,字符串RU5UUkVHQUdSQVRJU1==不是兼容的 base 64 編碼,在第 3.5 節“規范編碼”中指出:

base 64 和 base 32 編碼中的填充步驟如果實施不當,可能會導致編碼數據發生不重要的更改。 例如,如果輸入只是 base 64 編碼的一個八位位組,則使用第一個符號的所有六位,但僅使用下一個符號的前兩位。 這些填充位必須由符合標准的編碼器設置為零,這在下面的填充描述中有描述。 如果此屬性不成立,則不存在基本編碼數據的規范表示,並且多個基本編碼字符串可以解碼為相同的二進制數據。 如果此屬性(以及本文檔中討論的其他屬性)成立,則可以保證規范編碼。

在某些環境中,更改是關鍵的,因此如果填充位未設置為零,解碼器可以選擇拒絕編碼。 引用此的規范可能會強制執行特定行為。

(強調已添加。)

在這里,我們將手動完成 base 64 解碼過程。

獲取編碼字符串RU5UUkVHQUdSQVRJU1==並從 base 64 字符集執行映射(在上述 RFC 的“表 1:The Base 64 Alphabet”中給出),我們有:

  R      U      5      U      U      k      V      H      Q      U      d      S      Q      V      R      J      U      1      =       =
010001 010100 111001 010100 010100 100100 010101 000111 010000 010100 011101 010010 010000 010101 010001 001001 010100 110101 ______ ______

(使用__來表示填充字符)。

現在,將這些按 8 個而不是 6 個分組,我們得到

01000101 01001110 01010100 01010010 01000101 01000111 01000001 01000111 01010010 01000001 01010100 01001001 01010011 0101____ ________
  E        N        T        R        E        G        A        G        R        A        T        I        S        P

重要的部分在最后,那里有一些非零位,然后是填充。 Dart 實現正確地確定提供的填充沒有意義,前提是前一個字符的最后四位不解碼為零。

因此, RU5UUkVHQUdSQVRJU1==的解碼是不明確的。 ENTREGAGRATIS還是ENTREGAGRATISP 這正是 RFC 聲明“這些填充位必須由符合標准的編碼器設置為零”的原因。

事實上,正因為如此,我認為將RU5UUkVHQUdSQVRJU1==解碼為ENTREGAGRATIS而沒有抱怨的實現是有問題的,因為它會默默地丟棄非零位。

ENTREGAGRATIS 的 RFC 兼容編碼是ENTREGAGRATIS RU5UUkVHQUdSQVRJUw==

ENTREGAGRATISP 的 RFC 兼容編碼是ENTREGAGRATISP RU5UUkVHQUdSQVRJU1A=

這進一步突出了您輸入RU5UUkVHQUdSQVRJU1==的歧義,兩者都不匹配。

我建議您檢查您的編碼器以確定它為什么向您提供不兼容的編碼,並確保您不會因此丟失信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM