4

奇妙なルビーエンコーディングの遭遇:

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 バイトです。しかし、デコードされた文字列は最後のニブルを落としているように見えますか?

明らかな何かが欠けていますか、それともバグですか? 回避策 参照。http://www.ietf.org/rfc/rfc4648.txt

4

4 に答える 4

3

Base64はパディングされた文字列の1対1のマッピングではないため、対称性はありません。実際にデコードされたコンテンツから始めましょう。デコードされた文字列を16進数で表示する場合(たとえば、次のs.unpack('H*')ようになります:

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

各入力ブロックの境界をBase64アルゴリズムに追加しました。これは、3オクテットの入力を受け取り、4文字の出力を返します。したがって、最後のブロックには入力オクテットが1つしか含まれていないため、標準では「==」で終わる4文字になります。

その最後のブロックの正規エンコーディングがどうなるか見てみましょう。バイナリ表現8Eでは10001110です。RFCは、必要な24ビットに達するまで、欠落しているビットをゼロで埋めるように指示しています。

100011 100000 000000 000000

Base64アルファベットから対応する文字を取得するために必要なのは、6ビットのグループを作成したためです。最初のグループ(100011)は10進数の35に変換されるためj、Base64アルファベットのaになります。2番目(100000)は小数点以下32桁であるため、「g」です。残りの2文字は、規則に従って「==」として埋め込まれます。したがって、正規のエンコーディングは

jg==

今jq==を見ると、バイナリではこれは次のようになります。

100011 101010 000000 000000

したがって、違いは2番目のグループにあります。ただし、最初の8ビットのみが対象であることがすでにわかっているため(「==」はそのように指示します->これらの4文字からデコードされたオクテットを1つだけ取得します)、実際には最初の2ビットのみを処理します。グループ1の6ビットとグループ2の最初の2ビットがデコードされたオクテットを形成するためです。100011 10一緒に再び初期8Eバイト値を形成します。残りの16ビットは私たちとは無関係であるため、破棄できます。

これは、「厳密な」Base64エンコーディングの概念が理にかなっている理由も意味します。厳密でないデコードでは最後にガベージが破棄され、厳密なデコードでは6の最後のグループの残りのビットがゼロになるかどうかがチェックされます。そのため、非正規エンコーディングは厳密なデコードルールによって拒否されます。

于 2011-08-15T11:08:51.337 に答える
2

リンクしたRFCは、フォームの最後のクワッドがxx==入力シーケンスの1オクテットに対応することを明確に示しています。12から16ビットの情報(2つの任意のオクテット)を作成することはできないため、ここでの切り上げは無効です。

jq==正しいBase64エンコーディングプロセスの結果として表示できないため、文字列は厳密モードで拒否されます。長さが3の倍数でない入力シーケンスにはゼロが埋め込まれ、文字列にはゼロ以外のビットがあり、それらを表示できません。

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

RFC4648のセクション3.5 Canonical Encodingから:

たとえば、入力が base 64 エンコーディングの 1 オクテットのみの場合、最初のシンボルの 6 ビットすべてが使用されますが、次のシンボルの最初の 2 ビットのみが使用されます。これらのパッド ビットは、準拠するエンコーダーによってゼロに設定する必要があります…</p>

一部の環境では、変更が重要であるため、パッド ビットが 0 に設定されていない場合、デコーダはエンコードを拒否することを選択できます (MAY)。

最後の 4 バイト ( jq==) は、次のバイナリ値にデコードされます。

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

下線付きのビットは、最後にエンコードされたバイト (16 進 8E) を形成するために使用されます。残りのビット (下にアスタリスクが付いている) はゼロであると想定されます (これはjg==ではなくでエンコードされますjq==)。

アンパックは、mゼロであるべきであるがそうではないパディングビットについて寛容です。解凍は、m0許可されているため、それほど寛大ではありません (RFC から引用されたビットの「MAY」を参照)。アンパックされた結果のパックは、エンコードされた値が非正規であるため対称的ではありませんが、packメソッドは正規のエンコードを生成します (パッド ビットはゼロに等しい)。

于 2011-08-15T11:19:14.763 に答える
0

b64の良い説明をありがとう。私はあなた方全員に賛成し、@embossの返答を受け入れました。

しかし、これは私が探していた答えではありません。質問をより適切に述べると、次のようになります。

unpack('m0')によってゼロパディングされた8ビットバイトにデコードできるようにb64文字の文字列をパディングする方法は?

あなたの説明から、これが私たちの目的のために機能することがわかりました:

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 に答える