2011-08-15 57 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 

唯一的問題則是,該解碼的字符串長度不保留,但我們可以解決這一點。