ruby base64编码/解码/解包('m')麻烦

时间:2011-08-15 10:04:22

标签: ruby string base64 decode unpack

遇到奇怪的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×6比特= 84比特,即10 1/2 8比特字节,即11字节。但是解码后的字符串似乎会丢掉最后一个nybble?

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

4 个答案:

答案 0 :(得分: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 == now,二进制文件就是

100011 101010 000000 000000

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

这也意味着为什么“严格”Base64编码的概念有意义:非严格解码将在最后丢弃任何垃圾,而严格解码将检查最后一组6中的剩余比特为零。这就是为什么你的非规范编码将被严格的解码规则拒绝。

答案 1 :(得分:2)

您链接的RFC明确表示,xx==形式的最后四元组对应于输入序列的一个八位字节。您不能在12个中生成16位信息(两个任意八位字节),因此这里的舍入无效。

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

   j      q      =      =
|100011|101010|000000|000000|
|10001110|10100000|00000000|
          ^^^

答案 2 :(得分:2)

来自section 3.5 Canonical EncodingRFC4648

  

例如,如果输入只是基本64位编码的一个八位字节,      然后使用第一个符号的所有六位,但只使用第一个      使用下一个符号的两位。这些填充位必须设置为      通过符合编码器来实现零...

  

在某些环境中,更改至关重要      如果填充位没有,则解码器可以选择拒绝编码      已设为零。

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

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

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

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

答案 3 :(得分: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 

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