Pourquoi le codage base64 nécessite-t-il un remplissage si la longueur d'entrée n'est pas divisible par 3?

101

Quel est le but du remplissage dans l'encodage base64. Ce qui suit est l'extrait de wikipedia:

"Un caractère de remplissage supplémentaire est alloué et peut être utilisé pour forcer la sortie codée en un multiple entier de 4 caractères (ou de manière équivalente lorsque le texte binaire non codé n'est pas un multiple de 3 octets); ces caractères de remplissage doivent alors être ignorés lors du décodage mais autorise toujours le calcul de la longueur effective du texte non codé, lorsque sa longueur binaire d'entrée ne serait pas un multiple de 3 octets (le dernier caractère non-pad est normalement codé de sorte que le dernier bloc de 6 bits qu'il représente soit nul -padded sur ses bits les moins significatifs, au plus deux caractères de remplissage peuvent apparaître à la fin du flux codé). "

J'ai écrit un programme qui pouvait encoder en base64 n'importe quelle chaîne et décoder n'importe quelle chaîne encodée en base64. Quel problème le rembourrage résout-il?

Anand Patel
la source

Réponses:

210

Votre conclusion selon laquelle le rembourrage n'est pas nécessaire est juste. Il est toujours possible de déterminer la longueur de l'entrée sans ambiguïté à partir de la longueur de la séquence codée.

Cependant, le remplissage est utile dans les situations où les chaînes codées en base64 sont concaténées de telle manière que les longueurs des séquences individuelles sont perdues, comme cela pourrait arriver, par exemple, dans un protocole de réseau très simple.

Si les chaînes non ajoutées sont concaténées, il est impossible de récupérer les données d'origine car les informations sur le nombre d'octets impairs à la fin de chaque séquence individuelle sont perdues. Cependant, si des séquences remplies sont utilisées, il n'y a pas d'ambiguïté et la séquence dans son ensemble peut être décodée correctement.

Edit: une illustration

Supposons que nous ayons un programme qui code les mots en base64, les concatène et les envoie sur un réseau. Il encode "I", "AM" et "TJM", prend en sandwich les résultats ensemble sans remplissage et les transmet.

  • Iencode SQ( SQ==avec remplissage)
  • AMencode QU0( QU0=avec remplissage)
  • TJMencode VEpN( VEpNavec remplissage)

Donc, les données transmises sont SQQU0VEpN. Le récepteur base64-décode ceci comme I\x04\x14\xd1Q)au lieu de l'intention IAMTJM. Le résultat est absurde car l'expéditeur a détruit les informations sur la fin de chaque mot dans la séquence codée. Si l'expéditeur avait envoyé à la SQ==QU0=VEpNplace, le récepteur aurait pu le décoder sous la forme de trois séquences base64 distinctes qui se concaténeraient pour donner IAMTJM.

Pourquoi s'embêter avec le rembourrage?

Pourquoi ne pas simplement concevoir le protocole pour préfixer chaque mot avec une longueur entière? Ensuite, le récepteur pourrait décoder le flux correctement et il n'y aurait pas besoin de remplissage.

C'est une excellente idée, tant que nous connaissons la longueur des données que nous encodons avant de commencer à les encoder. Mais que se passerait-il si, au lieu de mots, nous encodions des morceaux de vidéo à partir d'une caméra en direct? Nous pourrions ne pas connaître la longueur de chaque morceau à l'avance.

Si le protocole utilisait un remplissage, il n'y aurait aucun besoin de transmettre une longueur. Les données pourraient être encodées au fur et à mesure qu'elles venaient de la caméra, chaque morceau se terminant par un remplissage, et le récepteur serait capable de décoder le flux correctement.

C'est évidemment un exemple très artificiel, mais cela illustre peut-être pourquoi le rembourrage pourrait être utile dans certaines situations.

TJM
la source
22
+1 La seule réponse qui fournit en fait une réponse raisonnable en plus "parce que nous aimons la verbosité et la redondance pour une raison inexplicable".
non valide
1
Cela fonctionne bien pour les blocs qui sont codés distinctement, mais qui sont censés être concaténés de manière indivisible après le décodage. Si vous envoyez U0FNSQ == QU0 =, vous pouvez reconstruire la phrase, mais vous perdez les mots qui composent la phrase. Mieux que rien, je suppose. Notamment, le programme GNU base64 gère automatiquement les encodages concaténés.
Marcelo Cantos
2
Et si la longueur des mots était un multiple de 3? Cette méthode stupide de concaténation détruit les informations (fins de mots), pas la suppression du remplissage.
GreenScape le
2
La concaténation en base64 permet aux encodeurs de traiter de gros morceaux en parallèle sans avoir à aligner les tailles de bloc sur un multiple de trois. De même, en tant que détail d'implémentation, il peut y avoir un encodeur qui doit vider un tampon de données interne d'une taille qui n'est pas un multiple de trois.
Andre D
1
Cette réponse pourrait vous faire penser que vous pouvez décoder quelque chose comme "SQ == QU0 = VEpN" en le donnant simplement à un décodeur. En fait, il semble que vous ne puissiez pas, par exemple les implémentations en javascript et php ne le supportent pas. En commençant par une chaîne concaténée, vous devez soit décoder 4 octets à la fois, soit diviser la chaîne après le remplissage des caractères. Il semble que ces implémentations ignorent simplement les caractères de remplissage, même lorsqu'ils sont au milieu d'une chaîne.
Roman
38

Sur une note connexe, voici un convertisseur de base pour la conversion de base arbitraire que j'ai créé pour vous. Prendre plaisir! https://convert.zamicol.com/

Que sont les caractères de remplissage?

Les caractères de remplissage aident à satisfaire les exigences de longueur et n'ont aucune signification.

Exemple décimal de remplissage: étant donné l'exigence arbitraire que toutes les chaînes doivent avoir une longueur de 8 caractères, le nombre 640 peut répondre à cette exigence en utilisant les 0 précédents comme caractères de remplissage car ils n'ont aucune signification, "00000640".

Encodage binaire

Le paradigme de l'octet: l'octet est l'unité de mesure standard de facto et tout schéma de codage doit se rapporter aux octets.

Base256 s'inscrit exactement dans ce paradigme. Un octet est égal à un caractère en base256.

Base16 , hexadécimal ou hexadécimal, utilise 4 bits pour chaque caractère. Un octet peut représenter deux caractères base16.

Base64 ne rentre pas uniformément dans le paradigme octet (pas plus que base32), contrairement à base256 et base16. Tous les caractères base64 peuvent être représentés en 6 bits, 2 bits à court d'un octet complet.

Nous pouvons représenter le codage base64 par rapport au paradigme octet sous forme de fraction: 6 bits par caractère sur 8 bits par octet . Cette fraction est réduite de 3 octets sur 4 caractères.

Ce ratio, 3 octets pour 4 caractères base64, est la règle que nous voulons suivre lors de l'encodage base64. L'encodage Base64 ne peut que promettre une mesure même avec des faisceaux de 3 octets, contrairement à base16 et base256 où chaque octet peut être autonome.

Alors pourquoi le remplissage est-il encouragé même si l'encodage pourrait fonctionner correctement sans les caractères de remplissage?

Si la longueur d'un flux est inconnue ou s'il peut être utile de savoir exactement quand un flux de données se termine, utilisez le remplissage. Les caractères de remplissage indiquent explicitement que ces points supplémentaires doivent être vides et excluent toute ambiguïté. Même si la longueur est inconnue avec le remplissage, vous saurez où se termine votre flux de données.

À titre d'exemple, certaines normes comme JOSE n'autorisent pas les caractères de remplissage. Dans ce cas, s'il manque quelque chose, une signature cryptographique ne fonctionnera pas ou d'autres caractères non base64 seront manquants (comme le "."). Bien que des hypothèses sur la longueur ne soient pas faites, le remplissage n'est pas nécessaire car s'il y a quelque chose qui ne va pas, cela ne fonctionnera tout simplement pas.

Et c'est exactement ce que dit la RFC base64 ,

Dans certaines circonstances, l'utilisation du remplissage ("=") dans les données codées en base n'est ni requise ni utilisée. Dans le cas général, lorsque des hypothèses sur la taille des données transportées ne peuvent être faites, un remplissage est nécessaire pour produire des données décodées correctes.

[...]

L'étape de remplissage en base 64 [...] si elle est mal implémentée, conduit à des altérations non significatives des données codées. Par exemple, si l'entrée n'est qu'un octet pour un codage en base 64, alors les six bits du premier symbole sont utilisés, mais seuls les deux premiers bits du symbole suivant sont utilisés. Ces bits de remplissage DOIVENT être mis à zéro par des codeurs conformes, ce qui est décrit dans les descriptions sur le remplissage ci-dessous. Si cette propriété ne tient pas, il n'y a pas de représentation canonique des données codées en base, et plusieurs chaînes codées en base peuvent être décodées en les mêmes données binaires. Si cette propriété (et d'autres discutées dans ce document) tient, un encodage canonique est garanti.

Le remplissage nous permet de décoder l'encodage base64 avec la promesse de ne pas perdre de bits. Sans remplissage, il n'y a plus d'acquittement explicite de la mesure dans des faisceaux de trois octets. Sans remplissage, vous ne pourrez peut-être pas garantir la reproduction exacte de l'encodage original sans informations supplémentaires généralement provenant d'un autre endroit de votre pile, comme TCP, des sommes de contrôle ou d'autres méthodes.

Exemples

Voici l'exemple de formulaire RFC 4648 ( http://tools.ietf.org/html/rfc4648#section-8 )

Chaque caractère à l'intérieur de la fonction "BASE64" utilise un octet (base256). Nous traduisons ensuite cela en base64.

BASE64("")       = ""           (No bytes used. 0%3=0.)
BASE64("f")      = "Zg=="       (One byte used. 1%3=1.)
BASE64("fo")     = "Zm8="       (Two bytes. 2%3=2.)
BASE64("foo")    = "Zm9v"       (Three bytes. 3%3=0.)
BASE64("foob")   = "Zm9vYg=="   (Four bytes. 4%3=1.)
BASE64("fooba")  = "Zm9vYmE="   (Five bytes. 5%3=2.)
BASE64("foobar") = "Zm9vYmFy"   (Six bytes. 6%3=0.)

Voici un encodeur avec lequel vous pouvez jouer: http://www.motobit.com/util/base64-decoder-encoder.asp

Zamicol
la source
16
-1 C'est un article intéressant et approfondi sur le fonctionnement des systèmes numériques, mais il n'explique pas pourquoi le remplissage est utilisé alors que l'encodage fonctionnerait parfaitement sans.
Matti Virkkunen
2
Avez-vous même lu la question? Vous n'avez pas besoin de rembourrage pour décoder correctement.
Navin
3
Je pense que cette réponse expliquait en fait la raison énoncée ici: "nous ne pouvons plus garantir la reproduction exacte de l'encodage original sans informations supplémentaires". C'est vraiment simple, le rembourrage nous a fait savoir que nous avons reçu l'encodage complet. Chaque fois que vous avez 3 octets, vous pouvez supposer en toute sécurité que vous pouvez aller de l'avant et le décoder, ne vous inquiétez pas, hum ... peut-être qu'un octet de plus va venir éventuellement changer l'encodage.
Didier A.
@DidierA. Comment savez-vous qu'il n'y a pas 3 octets de plus dans une sous-chaîne base64? Pour décoder a char*, vous avez besoin de la taille de la chaîne ou d'un terminateur nul. Le rembourrage est redondant. D'où la question d'OP.
Navin
4
@Navin Si vous décodez en flux les octets base64, vous ne connaissez pas la longueur, avec le remplissage de 3 octets, vous savez que chaque fois que vous avez 3 octets, vous pouvez traiter les 4 caractères, jusqu'à ce que vous atteigniez la fin du flux. Sans cela, vous devrez peut-être revenir en arrière, car l'octet suivant pourrait modifier le caractère précédent, ce qui vous permettra d'être sûr de l'avoir correctement décodé une fois que vous avez atteint la fin du flux. Donc, ce n'est pas très utile, mais il y a quelques cas extrêmes où vous pourriez le vouloir.
Didier A.
1

Il n'y a pas beaucoup d'avantages à cela de nos jours. Donc , le regard let cela comme une question de ce que l' origine peut avoir été but historique.

L'encodage Base64 fait sa première apparition dans la RFC 1421 datée de 1993. Cette RFC est en fait centrée sur le cryptage des e-mails, et base64 est décrite dans une petite section 4.3.2.4 .

Cette RFC n'explique pas le but du remplissage. Le plus proche que nous ayons d'une mention du but initial est cette phrase:

Un quantum de codage complet est toujours terminé à la fin d'un message.

Cela ne suggère pas la concaténation (réponse du haut ici), ni la facilité de mise en œuvre comme objectif explicite du remplissage. Cependant, compte tenu de la description entière, il n'est pas déraisonnable de supposer que cela peut avoir été destiné à aider le décodeur à lire l'entrée en unités de 32 bits ( "quanta" ). Cela ne sert à rien aujourd'hui, mais en 1993, un code C dangereux aurait très probablement profité de cette propriété.

Roman Starkov
la source
1
En l'absence de remplissage, une tentative de concaténer deux chaînes lorsque la longueur de la première chaîne n'est pas un multiple de trois produirait souvent une chaîne apparemment valide, mais le contenu de la deuxième chaîne serait décodé de manière incorrecte. L'ajout du remplissage garantit que cela ne se produit pas.
supercat du
1
@supercat Si tel était le but, ne serait-il pas plus facile de terminer chaque chaîne base64 par un seul "="? La longueur moyenne serait plus courte, et cela empêcherait encore les concaténations erronées.
Roman Starkov
2
La durée moyenne de b'Zm9vYmFyZm9vYg==' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy' b'Zm9vYmFyZm9vYmFyZg==' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v' est la même que celle de b'Zm9vYmFyZm9vYg=' b'Zm9vYmFyZm9vYmE=' b'Zm9vYmFyZm9vYmFy=' b'Zm9vYmFyZm9vYmFyZg=' b'Zm9vYmFyZm9vYmFyZm8=' b'Zm9vYmFyZm9vYmFyZm9v='
Scott