flutter base64.decode:填充前的编码无效

rjjhvcjd  于 2022-12-24  发布在  Flutter
关注(0)|答案(3)|浏览(325)

我正在做一个flutter项目,我尝试使用base64.decode()方法解码一些字符串时遇到了一个错误。我创建了一个简短的dart代码,它可以重现我在处理特定字符串时遇到的问题:

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解码相同的字符串,它工作正常。如果有人能解释为什么我会得到这个错误,并可能告诉我一个解决方案,我会很高兴。谢谢。

46qrfjad

46qrfjad1#

Base64编码将二进制数据分解为3个完整字节的6位段,并将其表示为ASCII标准中的可打印字符。
第一步是将二进制字符串分解为6位块。Base64只使用6位(对应于2^6 = 64个字符)来确保编码数据可打印和可读。没有使用ASCII中可用的任何特殊字符。
这64个字符(因此命名为Base64)是10位数字、26个小写字符、26个大写字符以及加号(+)和正斜杠(/)。还有第65个字符称为填充符,即等号(=)。当二进制数据的最后一段不包含完整的6位时使用此字符
所以RU 5 UUKVHQUdSQVRJU 1 ==不遵循编码模式。

eqzww0vc

eqzww0vc2#

使用下划线字符“_”作为填充字符,并删除填充字节进行解码

由于某些原因,dart:convertbase64.decode会阻塞用=填充的字符串,并显示“invalid encoding before padding error”。即使使用包自己的填充方法base64.normalize,也会发生这种情况,该方法用正确的填充字符=填充字符串。
=确实是base64编码的正确填充字符。当输入组中可用位数少于24位时,它用于填充base64字符串。请参阅RFC 4648,第4节。
然而,RFC 4648第5节是URL的base64编码方案,使用下划线字符_作为填充,而不是=,以确保URL安全。
使用_作为填充字符将使base64.decode解码无误。
为了进一步将生成的字节列表解码为Utf8,您需要删除填充字节,否则将收到“无效的UTF-8字节”错误。
请参见下面的代码。Here is the same code as a working dartpad.dev example.

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;
}
kyks70gy

kyks70gy3#

根据RFC 4648,字符串RU5UUkVHQUdSQVRJU1==不是兼容的base 64编码,在第3.5节“规范编码”中指出:
Base 64和Base 32编码中的填充步骤如果实施不当,可能会导致编码数据发生不重要的变化。例如,如果输入仅为Base 64编码的一个八位字节,则会使用第一个符号的所有六位,但仅使用下一个符号的前两位。这些填充位必须由符合要求的编码器设置为零,这在下面关于填充的描述中进行了描述。如果不满足此属性,则基本编码数据没有规范表示,并且多个基本编码字符串可以解码为相同的二进制数据。如果满足此属性(以及本文档中讨论的其他属性),则保证使用规范编码。
在某些环境中,这种改变是关键的,因此如果填充位没有被设置为零,解码器可能会选择拒绝编码。涉及到这一点的规范可能会要求特定的行为。
(着重号后加。)
在这里,我们将手动执行Base 64解码过程。
获取编码字符串RU5UUkVHQUdSQVRJU1==,并从基本64字符集执行Map(如“表1:上述RFC的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兼容编码是RU5UUkVHQUdSQVRJUw==
ENTREGAGRATISP的RFC兼容编码是RU5UUkVHQUdSQVRJU1A=
这进一步突出了输入RU5UUkVHQUdSQVRJU1==的二义性,它两个都不匹配。
我建议您检查编码器,确定它为什么提供不兼容的编码,并确保您没有因此而丢失信息。

相关问题