繁体   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