簡體   English   中英

ruby base64 編碼/解碼/解包('m')麻煩

[英]ruby base64 encode / decode / unpack('m') troubles

遇到奇怪的 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

沒有對稱性,因為 Base64 不是填充字符串的一對一映射。 讓我們從實際解碼的內容開始。 如果您以十六進制查看解碼的字符串(使用例如s.unpack('H*')它將是這樣的:

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

我將每個輸入塊的邊界添加到 Base64 算法中:它需要 3 個八位字節的輸入並返回 output 的 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 位是我們感興趣的(“==”告訴我們 -> 我們只會從這四個字符中檢索一個解碼的八位字節),我們實際上只關心前兩位第二組,因為第 1 組的 6 位和第 2 組的 2 個第一位構成我們解碼的八位字節。 100011 10再次構成我們最初的8E字節值。 剩下的 16 位與我們無關,因此可以丟棄。

這也暗示了為什么“嚴格”Base64 編碼的概念是有意義的:非嚴格解碼將在最后丟棄任何垃圾,而嚴格解碼將檢查最后一組 6 中的剩余位是否為零。 這就是為什么您的非規范編碼將被嚴格的解碼規則拒絕的原因。

您鏈接的 RFC 清楚地表明,形式xx==的最后一個四元組對應於輸入序列的一個八位字節。 您不能從 12 位信息中提取 16 位信息(兩個任意八位位組),因此此處舍入無效。

您的字符串在嚴格模式下被拒絕,因為jq==不能作為正確的 Base64 編碼過程的結果出現。 長度不是 3 的倍數的輸入序列是零填充的,並且您的字符串具有它們不能出現的非零位:

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

RFC4648第 3.5 節規范編碼

例如,如果輸入對於 base 64 編碼只有一個八位字節,則使用第一個符號的所有六位,但只使用下一個符號的前兩位。 這些填充位必須通過符合要求的編碼器設置為零……

在某些環境中,更改至關重要,因此如果填充位未設置為零,解碼器可能會選擇拒絕編碼。

您的最后四個字節( jq== )解碼為這些二進制值:

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

帶下划線的位用於形成最后一個編碼字節(十六進制 8E)。 剩余的位(下面有星號)應該為零(編碼為jg== ,而不是jq== )。

m解包正在原諒應該為零但不是的填充位。 m0解包並不像允許的那樣寬容(參見 RFC 引用位中的“MAY”)。 打包解包結果不是對稱的,因為您的編碼值是非規范的,但是pack方法會產生規范編碼(填充位等於零)。

感謝您對 b64 的良好解釋。 我已經向你們所有人投了贊成票並接受了@emboss 的回復。

然而,這不是我想要的答案。 為了更好地 state 問題,它會是,

如何填充 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 

唯一的問題是解碼后的字符串長度沒有被保留,但我們可以解決這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM