Ruby Base64编码/解码/unpack('m')遇到的问题

4
遇到奇怪的 Ruby 编码问题:
ruby-1.9.2-p180 :618 > s = "a8dnsjg8aiw8jq".ljust(16,'=')
 => "a8dnsjg8aiw8jq==" 
ruby-1.9.2-p180 :619 > s.size
 => 16 

ruby-1.9.2-p180 :620 > s.unpack('m0')
ArgumentError: invalid base64
    from (irb):631:in `unpack'

ruby-1.9.2-p180 :621 > s.unpack('m')
 => ["k\xC7g\xB28<j,<\x8E"] 
ruby-1.9.2-p180 :622 > s.unpack('m').first.size
 => 10

ruby-1.9.2-p180 :623 > s.unpack('m').pack('m')
 => "a8dnsjg8aiw8jg==\n" 
ruby-1.9.2-p180 :624 > s.unpack('m').pack('m') == s
 => false 

有没有想法为什么这不对称!? 为什么'decode64_strict'根本不起作用?输入字符串在base64字母表中填充到多个4个字符。 这里是14 x 6位= 84位,即10 1/2个8位字节,即11个字节。 但解码后的字符串似乎删除了最后一个nybble? 我是否遗漏了一些明显的东西,还是这是一个错误? 解决方法? 参见http://www.ietf.org/rfc/rfc4648.txt
4个回答

3

没有对称性是因为Base64不是一个针对填充字符串的一对一映射。让我们从实际解码内容开始。如果您使用例如s.unpack('H*')以十六进制查看解码后的字符串,它将如下所示:

6B C7 67 | B2 38 3C | 6A 2C 3C | 8E

我已经为Base64算法中的每个输入块添加了边界:它需要3个八位字节的输入,并返回4个输出字符。因此,我们的最后一个块只包含一个输入八位字节,因此结果将是4个以“==”结尾的字符,符合标准要求。
让我们看看最后一个块的规范编码是什么。在二进制表示中,8E10001110。RFC告诉我们用零填充缺失的比特,直到达到所需的24比特。
100011 100000 000000 000000

我将6个比特分为一组,因为这是我们需要从Base64字母表中获取对应字符所需的位数。第一组(100011)翻译为35十进制,因此是Base64字母表中的j。第二组(100000)是32十进制,因此是'g'。剩下的两个字符需要根据规则填充为"=="。因此,规范编码为:

jg==

如果你现在看jq==,以二进制表示就是:
100011 101010 000000 000000

因此,区别在于第二组。但是由于我们已经知道只有前8位对我们有用("=="告诉我们这一点->我们只会从这四个字符中检索一个解码的八位字节),因此我们实际上只关心第二组的前两位,因为第一组的6位和第二组的前2位形成了我们解码的八位字节。100011 10再次形成了我们最初的8E字节值。剩余的16位对我们来说是不相关的,因此可以被丢弃。
这也说明了为什么“严格”Base64编码的概念是有意义的:非严格解码将丢弃末尾的任何垃圾,而严格解码将检查最后一组6的剩余位是否为零。这就是为什么您的非规范编码将被严格的解码规则拒绝的原因。

2
您提供的链接中的RFC明确指出,形式为xx==的最后一个四元组对应于输入序列的一个八位字节。在这里,您不能从12个字节中获取16位信息(任意两个八位字节),因此向上取整是无效的。
在严格模式下,您的字符串将被拒绝,因为jq==不能作为正确的Base64编码过程的结果出现。长度不是3的倍数的输入序列会被填充零,并且您的字符串在非零位上出现了它们不能出现的位置:
   j      q      =      =
|100011|101010|000000|000000|
|10001110|10100000|00000000|
          ^^^

2
中译英:

来自RFC4648第3.5节 规范编码

例如,如果输入只有一个八位字节用于Base64编码,则第一个符号的所有六个比特位都会被使用,但是下一个符号仅使用前两个比特位。这些填充比特必须由符合要求的编码器设置为零…

在某些环境中,更改是至关重要的,因此如果未将填充比特设置为零,则解码器可能会选择拒绝编码。

您的最后四个字节(jq==)解码为以下二进制值:

100011 101010
------ --****

下划线部分用于组成最后一个编码字节(十六进制8E)。其余位(下有星号)应为零(应编码为“jg==”,而不是“jq==”)。
解压缩操作对应的程序会容忍填充位不为零的情况,而对应的程序则不会容忍(参见RFC中的“MAY”一词)。将解压缩后的结果进行打包操作并不对称,因为您的编码值是非规范的,但方法会生成一个规范的编码(填充位等于零)。

0

感谢对b64的良好解释。我已经给你们所有人点赞并接受了@emboss的回答。

然而,这不是我要找的答案。为了更好地阐述问题,应该是这样的:

如何填充一串b64字符,以便可以通过unpack('m0')解码为零填充的8位字节?

从你们的解释中,我现在明白这将适用于我们的目的:

ruby-1.9.2-p180 :858 >   s = "a8dnsjg8aiw8jq".ljust(16,'A')
 => "a8dnsjg8aiw8jqAA" 
ruby-1.9.2-p180 :859 > s.unpack('m0')
 => ["k\xC7g\xB28<j,<\x8E\xA0\x00"] 
ruby-1.9.2-p180 :861 > s.unpack('m0').pack('m0') == s
 => true 

唯一的问题是解码后的字符串长度没有被保留,但我们可以解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接