2011-08-15 4 views
4

Avoir une étrange rencontre d'encodage rubis:rubis base64 encode/decode/déballer ('m') trouble

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 

Toute idée pourquoi ce n'est pas symétrique !? Et pourquoi 'm0' (decode64_strict) ne fonctionne pas du tout? La chaîne d'entrée est complétée par un multiple de 4 caractères dans l'alphabet base64. Ici, il s'agit de 14 x 6 bits = 84 bits, ce qui représente 10 octets de 8 bits, c'est-à-dire 11 octets. Mais la chaîne décodée semble laisser tomber le dernier nybble?

Ai-je manqué quelque chose d'évident ou est-ce un bug? Solution de contournement? cf. http://www.ietf.org/rfc/rfc4648.txt

Répondre

3

Il n'y a pas de symétrie, car base64 est pas un à un mapping cordes rembourrées. Commençons par le contenu décodé réel. Si vous affichez votre chaîne décodée en hexadécimal (en utilisant par exemple s.unpack('H*') il sera ceci:

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

J'ai ajouté les limites pour chaque bloc d'entrée à l'algorithme base64: il faut 3 octets d'entrée et renvoie 4 caractères de sortie. Donc, notre dernier bloc ne contient qu'un seul octet d'entrée, donc le résultat sera 4 caractères qui se terminent par "==" selon la norme

Voyons ce que serait le codage canonique de ce dernier bloc En représentation binaire 8E est 10001110 Le RFC nous dit de combler les bits manquants avec des zéros jusqu'à ce que nous atteignions les 24 bits requis:

100011 100000 000000 000000 

J'ai créé des groupes de 6 bits, parce que c'est ce dont nous avons besoin pour obtenir les caractères correspondants de l'alphabet Base64. Le premier groupe (100011) se traduit par 35 décimales et est donc un j dans l'alphabet Base64. La seconde (100000) est 32 décimale et donc un 'g'. Les deux caractères restants doivent être remplis comme "==" selon les règles. Donc, l'encodage canonique est

jg== 

Si vous regardez JQ == maintenant, en binaire ce sera

100011 101010 000000 000000 

Donc, la différence est dans le deuxième groupe. Mais puisque nous savons déjà que seuls les 8 premiers bits nous intéressent (le "==" nous le dit -> nous ne récupérerons qu'un octet décodé de ces quatre caractères) nous ne nous occupons que des deux premiers bits du deuxième groupe, car les 6 bits du groupe 1 et les 2 premiers bits du groupe 2 forment notre octet décodé. 100011 10 ensemble former à nouveau notre initiale 8E valeur d'octet. Les 16 bits restants ne nous concernent pas et peuvent donc être rejetés. Cela implique aussi que la notion de codage Base64 "strict" soit logique: le décodage non strict rejettera tout déchet à la fin tandis que le décodage strict vérifiera que les bits restants sont nuls dans le dernier groupe de 6.C'est pourquoi votre encodage non-canonique sera rejeté par des règles de décodage strictes.

2

Le RFC que vous avez dit clairement que lié le quad final de forme xx== correspond à un octet de la séquence d'entrée. Vous ne pouvez pas faire 16 bits d'information (deux octets arbitraires) sur 12, donc arrondir est invalide ici.

Votre chaîne est rejetée en mode strict, car jq== ne peut pas apparaître à la suite d'un processus de codage Base64 correct. séquence d'entrée dont la longueur est pas un multiple de 3 est complété par des zéros, et votre chaîne a bits non nuls où ils ne peuvent pas apparaître:

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

De section 3.5 Canonical Encoding de RFC4648:

Par exemple, si l'entrée est un seul octet pour une base 64 codage, puis tous les six bits du premier symbole sont utilisés, mais seulement la première deux bits du symbole suivant sont utilisés. Ces bits pad doit être réglé sur zéro par des codeurs conformes ...

et

Dans certains environnements, l'altération est décodeurs critique et donc peut choisir de rejeter un codage si les bits pad ont pas été réglé à zéro.

Vos quatre derniers octets (jq==) décodent à ces valeurs binaires:

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

Les bits soulignés sont utilisés pour former le dernier octet codé (8E hex). Les bits restants (avec des astérisques sous eux) sont supposés être zéro (ce qui serait codé jg==, pas jq==).

Le déballage m pardonne les bits de remplissage qui devraient être zéro mais ne le sont pas. Le déballage m0 ne pardonne pas, comme il est autorisé à l'être (voir "MAY" dans le bit cité du RFC). L'empaquetage du résultat non compressé n'est pas symétrique car votre valeur codée est non-canonique, mais la méthode pack produit un codage canonique (les bits de remplissage sont égaux à zéro).

0

Merci pour les bonnes explications sur b64. Je vous ai tous mis à jour et j'ai accepté la réponse de @ emboss.

Cependant, ce n'est pas la réponse que je cherchais. Pour mieux préciser la question, il serait,

Comment pad une chaîne de caractères B64 afin qu'il puisse être décodé à octets de 8 bits par zéro matelassées Déballez (« m0 »)?

A partir de vos explications, je vois maintenant que cela va fonctionner pour nos besoins:

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 

Le seul problème étant alors que la longueur de la chaîne décodée ne se conserve pas, mais nous pouvons travailler autour de cela.