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 

知道为什么这不是对称的!?为什么'm0'(decode64_strict)根本不起作用?输入字符串被填充为 base64 字母表中 4 个字符的倍数。这里是 14 x 6 位 = 84 位,即 10 1/2 8 位字节,即 11 个字节。但是解码后的字符串似乎放弃了最后一个 nybble?

我错过了一些明显的东西还是这是一个错误?解决方法?参看。http://www.ietf.org/rfc/rfc4648.txt

4

4 回答 4

3

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

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

我将每个输入块的边界添加到 Base64 算法中:它需要 3 个八位字节的输入并返回 4 个字符的输出。所以我们的最后一个块只包含一个输入八位位组,因此根据标准,结果将是 4 个以“==”结尾的字符。

让我们看看最后一个块的规范编码是什么。在二进制表示8E中是10001110. RFC 告诉我们用零填充缺失的位,直到达到所需的 24 位:

100011 100000 000000 000000

我制作了 6 位组,因为这是我们从 Base64 字母表中获取相应字符所需的。第一组 (100011) 转换为十进制 35,因此是jBase64 字母表中的 a。第二个(100000)是十进制的 32,因此是“g”。根据规则,剩余的两个字符将被填充为“==”。所以规范编码是

jg==

如果你现在看 jq== ,在二进制中这将是

100011 101010 000000 000000

所以区别在于第二组。但是因为我们已经知道只有前 8 位是我们感兴趣的(“==”告诉我们 -> 我们只会从这四个字符中检索一个解码的八位字节),我们实际上只关心前两位第二组,因为第 1 组的 6 位和第 2 组的 2 个第一位构成我们解码的八位字节。100011 10一起再次形成我们的初始8E字节值。剩下的 16 位与我们无关,因此可以丢弃。

这也暗示了为什么“严格”Base64 编码的概念是有意义的:非严格解码将在最后丢弃任何垃圾,而严格解码将检查最后一组 6 中剩余位是否为零。这就是为什么您的非规范编码将被严格的解码规则拒绝的原因。

于 2011-08-15T11:08:51.337 回答
2

您链接的 RFC 清楚地表明最后一个四元xx==组对应于输入序列的一个八位字节。您不能从 12 位信息中提取 16 位信息(两个任意八位位组),因此此处舍入无效。

您的字符串在严格模式下被拒绝,因为由于jq==正确的 Base64 编码过程而无法出现。长度不是 3 的倍数的输入序列是零填充的,并且您的字符串具有它们不能出现的非零位:

   j      q      =      =
|100011|101010|000000|000000|
|10001110|10100000|00000000|
          ^^^
于 2011-08-15T11:01:24.603 回答
2

RFC4648第 3.5 节规范编码

例如,如果输入对于 base 64 编码只有一个八位字节,则使用第一个符号的所有六位,但只使用下一个符号的前两位。这些填充位必须通过符合编码器的方式设置为零……</p>

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

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

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

带下划线的位用于形成最后一个编码字节(十六进制 8E)。剩余的位(下面有星号)应该是零(这将被编码jg==,而不是jq==)。

m拆包正在原谅应该为零但不是的填充位。m0拆包并不像允许的那样宽容(参见 RFC 中引用的位中的“MAY”)。打包解压缩的结果不是对称的,因为您的编码值是非规范的,但该pack方法会产生规范编码(填充位等于零)。

于 2011-08-15T11:19:14.763 回答
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 

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

于 2011-08-15T21:08:21.577 回答