2011-08-15 59 views
4

有一个奇怪红宝石编码遭遇:红宝石BASE64编码/解码/解压缩(“M”)困扰

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×6位= 84位,它是10 1/2 8位字节,即11个字节。但解码后的字符串似乎放弃了最后一个nybble?

我错过了一些明显的东西,或者这是一个错误?解决方法? 比较http://www.ietf.org/rfc/rfc4648.txt

回答

3

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

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

我添加到每个输入块到Base64编码算法的界限:它需要输入3个字节,并返回4个字符输出。所以我们的最后一个块只包含一个输入八位组,因此结果将是4个字符,按照标准以“==”结尾。是RFC 10001110。RFC告诉我们用零填充缺失的位,直到达到所需的24位:

100011 100000 000000 000000 

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

jg== 

如果你看一下JQ ==现在,在二进制这将是

100011 101010 000000 000000 

所以,不同的是第二小组。但是由于我们已经知道只有前8位对我们很重要(“==”告诉我们 - >我们只会从这四个字符中检索一个解码的八位字节),但我们实际上只关心前两位第二组,因为组1的6个比特和​​组2的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

RFC4648section 3.5 Canonical Encoding

例如,如果输入是只有一个底座64编码八位位组中,使用 然后第一个符号的所有六个位,但仅在第一 两个比特使用下一个符号。这些填充比特必须通过符合编码器被设置为 零...

在一些环境中,所述改变是至关重要的,并且因此 解码器可以选择,如果填充比特没有拒绝的编码 已被设置为零。

你的最后四个字节(jq==)进行解码,以这些二进制值:

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

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

m拆箱正在宽恕填充位应该是零,但不是。 m0解包不是如此宽容,因为它是允许的(请参阅RFC中引用位的“可能”)。打包解包结果不对称,因为您的编码值是非规范的,但方法会生成规范编码(填充位等于零)。

0

感谢您对b64的很好的解释。我赞成你们所有人并接受了@ emboss的回应。

但是,这不是我正在寻找的答案。为了更好地说明问题,这将是,

如何垫的B64字符的字符串,以便它可以通过解包(“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 

唯一的问题则是,该解码的字符串长度不保留,但我们可以解决这一点。