2 points par GN⁺ 2023-10-24 | 1 commentaires | Partager sur WhatsApp
  • L’encodage Base64 est une méthode qui transforme des données binaires en texte ASCII, afin de réduire le risque qu’elles soient mal interprétées lors du stockage ou de la transmission
  • Il ne s’agit pas de chiffrement, mais d’un changement de représentation : les données encodées peuvent donc être facilement reconverties en texte ou en données de fichier d’origine
  • 64 caractères peuvent être représentés sur 6 bits : un caractère Base64 contient donc 6 bits de données, et 3 octets (24 bits) sont transformés en quatre caractères Base64
  • C’est utile dans des environnements où le binaire brut peut poser problème, comme les Data URLs en HTML, l’envoi de binaire par e-mail, les réseaux centrés sur le texte ou les URL
  • Ruby, C#, PHP, JavaScript, la commande base64 du terminal et de nombreux autres langages et outils fournissent des fonctions d’encodage et de décodage

Ce que Base64 transforme

  • L’encodage Base64 convertit des données binaires en texte, plus précisément en texte ASCII
  • Le résultat n’utilise que les 64 caractères suivants
    • A-Z
    • a-z
    • 0-9
    • +
    • /
  • Cet alphabet sert de jeu de caractères sûr pour éviter les situations où des caractères comme <, > ou \n sont mal interprétés par d’anciens ordinateurs ou programmes
  • Encodé en Base64, "Ruby on Rails" devient UnVieSBvbiBSYWlscw==
  • Base64 n’est pas du chiffrement
    • Les données encodées peuvent être facilement reconverties en texte d’origine
    • Il ne cache pas les données, il ne fait qu’en changer la représentation

Dans quels cas utiliser Base64

  • Les Data URLs permettent d’intégrer directement dans du HTML des données de fichiers comme des images, en utilisant alors du texte encodé en Base64
  • Le format d’exemple est data:[<mime type>][;charset=<charset>][;base64],<encoded data>
  • Dans les e-mails, Base64 est utilisé depuis longtemps pour inclure en toute sécurité des données binaires, même dans des environnements où les serveurs peuvent modifier les retours à la ligne
  • Lorsqu’on insère directement des données d’image dans du code source HTML, l’encodage est nécessaire pour éviter que des caractères comme < et > soient interprétés comme des balises
  • Il peut aussi être utilisé pour stocker ou transmettre des données binaires via des réseaux conçus pour traiter du texte ou des données US-ASCII
  • Base64 peut également servir à transmettre des données contenant des caractères difficiles à inclure dans une URL
  • Les encodages de la famille Base permettent de manipuler des objets dans un éditeur de texte et sont utilisés dans de nombreuses applications

Algorithme d’encodage

  • L’encodage Base64 se déroule dans l’ordre suivant
    • Convertir le texte en représentation binaire
    • Découper les bits en groupes de 6 bits
    • Convertir chaque groupe de 6 bits en nombre décimal de 0 à 63
    • Remplacer ce nombre par le caractère correspondant de l’alphabet Base64
  • Si le dernier groupe ne contient pas assez de bits, = ou == peut être ajouté comme bourrage
  • Il faut 6 bits pour représenter 64 caractères
    • 2^6 = 64
    • Un chiffre Base64 représente 6 bits de données
  • Un octet vaut 8 bits, et le plus petit multiple commun proche de 8 et 6 est 24
    • 24 bits correspondent à 3 octets
    • 24 bits sont représentés par quatre chiffres Base64 de 6 bits

Exemple d’encodage de “Akshay”

  • "Akshay", après conversion de chaque caractère en valeur ASCII puis en binaire, donne ceci
    • 01000001 01101011 01110011 01101000 01100001 01111001
  • En découpant ces bits en groupes de 6 bits, on obtient
    • 010000 010110 101101 110011 011010 000110 000101 111001
  • La conversion de chaque groupe en nombre décimal donne les valeurs suivantes
    • 16 22 45 51 26 6 5 57
  • Convertis avec l’alphabet Base64, ils deviennent les caractères suivants
    • Q W t z a G F 5
  • La représentation Base64 de "Akshay" est donc QWtzaGF5
  • De la même façon, des fichiers comme des images, des PDF, du texte ou des vidéos peuvent être convertis en binaire puis encodés en Base64 afin d’être stockés ou transmis sous forme de texte ASCII

Utilisation dans les langages et les outils

  • Ruby gère l’encodage et le décodage avec le module Base64
    • Base64.encode64("Ruby on Rails")
    • Base64.decode64(encoded)
  • C# convertit une chaîne en tableau d’octets, puis l’encode avec Convert.ToBase64String et la décode avec System.Convert.FromBase64String
  • PHP fournit les fonctions globales base64_encode et base64_decode
  • JavaScript encode avec btoa() et décode avec atob()
  • Dans un terminal, la commande base64 permet aussi d’effectuer l’encodage et le décodage
    • echo "akshay" | base64 affiche YWtzaGF5Cg==
    • echo "YWtzaGF5Cg==" | base64 -d affiche akshay

1 commentaires

 
GN⁺ 2023-10-24
Avis de Hacker News
  • Merci d’insister sur le fait qu’il ne s’agit pas ici de chiffrer du texte. Beaucoup de développeurs juniors apprennent trop tard, et à leurs dépens, la différence entre le chiffrement, qui nécessite une valeur secrète pour être réversible, le hachage, qui n’est pas réversible, et l’encodage, qui est toujours facilement réversible.
    Il faut aussi savoir que même si la sortie paraît aléatoire, son entropie est la même que celle de l’entrée. Autrement dit, il ne faut pas encoder un mot de passe en Base64 pour le rendre plus robuste.

    • C’est un peu pinailler et ce n’est pas vraiment le cœur du sujet, mais encoder un mot de passe en Base64 peut le rendre plus robuste. La robustesse d’un mot de passe n’est pas qu’une question d’entropie, même si une entropie élevée est la méthode la plus efficace.
      Si le mot de passe a été généré de façon totalement aléatoire, l’encodage Base64 n’a aucun effet. Mais si le mot de passe a été créé selon un système à faible entropie, comme des mots du dictionnaire ou des règles faciles à mémoriser, alors l’attaquant doit configurer un craqueur de mots de passe intelligent pour prendre aussi en compte les règles d’encodage Base64, ce qui ajoute au moins une opération supplémentaire à chaque tentative.
      Bien sûr, il ne faut pas utiliser ce genre de système de mots de passe. À mon avis, un mot de passe du type « correct horse battery staple » suffit.
    • Je pense que tout diplômé en informatique connaît ces différences. Ce sont des notions que même quelqu’un qui s’intéresse au code peut apprendre en un après-midi.
    • Un point connexe qu’il vaut toujours la peine de souligner est que les hashs ne sont pas forcément cryptographiquement sûrs.
      Le hachage a beaucoup d’usages en dehors de la sécurité, et il existe donc toutes sortes de bibliothèques de hash. Si vous utilisez un hash pour des usages liés à la sécurité ou à la cryptographie, il faut utiliser un hash conçu pour cet objectif. Les hashs CRC sont rapides, mais ce ne sont pas de bons choix pour des mots de passe utilisateur.
  • Un aspect intéressant de Base64, c’est que si l’on part d’une chaîne quelconque et qu’on l’encode à répétition, le début du résultat converge progressivement vers un point fixe. On peut même le vérifier en Bash.
    Je l’avais découvert par hasard il y a plus de dix ans et tweeté comme une sorte de message codé [1] ; quelqu’un a écrit un billet de blog sur le sujet et l’a aussi posté ici, mais il n’y a pas vraiment eu de discussion [2]. Quand quelqu’un d’autre l’a posté sur Reddit /r/compsci, il y a eu là-bas une discussion productive qui a corrigé le billet [3]. Le blog n’est plus en ligne aujourd’hui, mais une copie reste disponible sur Internet Archive [4].
    [1] https://twitter.com/p4bl0/status/298900842076045312
    [2] https://news.ycombinator.com/item?id=5181256
    [3] https://www.reddit.com/r/compsci/comments/18234a/the_base64_...
    [4] https://web.archive.org/web/20130315082932/http://fmota.eu/b...

  • Pour encoder en Bash, il faut utiliser l’option -n : $ echo -n "abcde" |base64
    Sans -n, echo ajoute un caractère de nouvelle ligne à la fin de la chaîne, et ce caractère est encodé aussi.

  • Il existe aussi base64URL, qui encode en utilisant d’autres caractères ASCII sûrs pour les URL. Certains développeurs appellent simplement BASE64URL « base64 », ce qui peut poser problème à ceux qui ne le savent pas.
    https://datatracker.ietf.org/doc/html/rfc4648#section-5

    • Le problème de base64url, c’est que ~ et . ne sont pas considérés comme des caractères de mot, donc un double-clic sur la valeur encodée ne sélectionne pas l’ensemble. Cela ajoute une friction inutile dans beaucoup de cas de copier-coller.
      L’encodage Base62 (0-9A-Za-z) est presque aussi efficace que base64url, reste sûr pour les URL et se copie-colle plus facilement. Si l’on veut réduire les ambiguïtés à la lecture humaine, on peut descendre à Base58, mais en général, quand on va jusqu’à utiliser un encodage BaseXX, la chaîne est assez longue pour que le copier-coller soit la norme, donc ce n’est pas un gros problème.
      https://en.wikipedia.org/wiki/Base62
    • Base64url omet aussi généralement le padding.
      Une chaîne Base64 avec padding a toujours une longueur multiple de 4 ; donc si l’on reçoit une chaîne dont la longueur n’est pas un multiple de 4, on peut savoir combien de padding il aurait dû y avoir à l’origine et déterminer comment décoder les 3 derniers octets.
      Du coup, je ne comprends pas très bien pourquoi le padding == est nécessaire en Base64 au départ.
  • Chaque fois qu’il est question de conversion de base, je fais sans vergogne la promotion de mon convertisseur de bases arbitraires : https://convert.zamicol.com
    Le base64 sous « useful alphabets » est une base « naturelle » qui fait des divisions répétées par la base, tandis que la méthode de conversion par « buckets » de la RFC se trouve sous extras.

  • Si vous encodez quelque chose qui doit être saisi à la main par un humain, je recommande https://en.wikipedia.org/wiki/Base32
    Rien n’est plus agaçant que de ne pas savoir, à cause d’une mauvaise police, si c’est l ou 1, ou o, O ou 0.

  • Pour être un peu plus rigoureux, il serait plus exact de dire que Base64 encode des données binaires vers un sous-ensemble d’ASCII, et non vers l’ensemble des caractères ASCII.
    ASCII compte 128 points de code, dont 95 caractères imprimables et 33 caractères de contrôle ; Base64 n’en utilise que 64, ou 65 si l’on inclut le padding.

  • L’article n’expliquait pas en détail le but du padding = / ==, ni ne montrait par des exemples comment traiter des données qui ne se divisent pas exactement en groupes de 6 bits
    J’ai l’impression d’avoir compris dans les grandes lignes, mais j’aimerais en être sûr. Ce serait bien d’avoir une réponse courte et complète : quand utilise-t-on = et quand utilise-t-on ==, est-ce qu’on les ajoute toujours ou y a-t-il des cas sans padding, comment sont traités exactement les bits restants dans une chaîne comme "5byte", et y a-t-il des points à prendre en compte au décodage ?

    • Les deux questions sont liées
      Un caractère Base64 représente 6 bits, donc un bloc de 3 octets de données correspond à un bloc de 4 caractères encodés en Base64. C’est pourquoi les données Base64 se traitent facilement par groupes de 4 caractères
      = est un padding que l’on ajoute, selon les besoins, à raison de 0, 1 ou 2 caractères, afin que la longueur de la chaîne encodée soit un multiple de 4. Par exemple, "543210" devient "543210==", "6543210" devient "6543210=", et "76543210" n’a pas besoin de padding. Il n’y a jamais besoin de 3 caractères = de padding, car même 1 octet de données nécessite au minimum 2 caractères Base64
      Les bits restants peuvent être complétés avec des zéros, et le décodeur peut les ignorer en constatant qu’il n’y a pas assez de bits pour former un octet complet. Dans la plupart des cas modernes, le padding relève davantage de la convention que d’une nécessité stricte. L’article Wikipédia est assez détaillé : https://en.wikipedia.org/wiki/Base64
    • Le padding n’est nécessaire que lorsqu’on concatène des données encodées ou qu’on fait du streaming, c’est-à-dire lorsque des caractères de padding apparaissent au milieu d’un flux encodé
      Les caractères de padding à la fin d’un flux, d’un fichier ou d’une chaîne peuvent être déduits à partir de la longueur déjà traitée ; ils ne sont donc pas strictement indispensables
      Cela dit, la gestion du padding est assez subtile, et ces différences ont donné lieu à des variantes d’implémentation intéressantes : https://eprint.iacr.org/2022/361.pdf
    • D’après l’article, un chiffre Base64 représente 6 bits de données. Un octet vaut 8 bits, et le plus petit multiple commun de 8 et 6 est 24 ; on peut donc représenter 24 bits, soit 3 octets, avec 4 chiffres Base64 de 6 bits
      Au final, on encode donc par unités de 24 bits. Quand les données se terminent, on complète la partie restante des 24 bits avec =, et non avec A, car A signifierait en tant que donnée 000000. Moi aussi, j’ai dû tout lire deux fois pour comprendre
  • Mon shader encodeur Base64 est ici : https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Je l’ai réduit à environ 13 lignes de GLSL : https://github.com/Rezmason/excel_97_egg/blob/main/glsl/base...
    Je l’utilise dans le Cursed Mode d’un side project : il rend environ 15 fois par seconde un framebuffer WebGL sous forme de BMP 640x480 en couleurs indexées, encodé en Base64 : https://rezmason.github.io/excel_97_egg/?cursed=1

  • Quand on commence à creuser, il y a d’autres détails intéressants, et les variations autour de ces détails sont étonnamment nombreuses
    Si la longueur des données d’entrée n’est pas exactement un multiple de 3 octets, on utilise 2 ou 3 caractères Base64 pour encoder le dernier octet ou les 2 derniers octets. Comme un caractère Base64 vaut 6 bits, on utilise 12 ou 18 bits pour représenter 8 ou 16 bits, ce qui laisse respectivement 4 ou 2 bits supplémentaires qui n’encodent rien
    La RFC exige que l’encodeur mette ces bits à 0, mais indique seulement que le décodeur peut rejeter une entrée où ces bits ne valent pas 0. En pratique, très peu d’implémentations rejettent cela par défaut ; à ma connaissance, seules Ruby, Rust et Go peuvent être configurées pour échouer sur ce type d’entrée. Python a une option validate, mais elle ne vérifie pas ces bits
    Une autre grande différence concerne le traitement des espaces et des caractères qui ne font pas partie du Base64. Un nombre étonnamment élevé d’implémentations, y compris Python, ignorent silencieusement des caractères arbitraires dans l’entrée. Cela peut poser problème si l’on choisit mal l’alphabet : par exemple, en Python, base64.standard_b64decode(base64.urlsafe_b64encode(b'\xFF\xFE\xFD\xFC')) ne produit pas d’erreur et renvoie silencieusement une sortie incorrecte
    Il est aussi amusant que l’encodeur Base64 de Ruby insère un saut de ligne tous les 60 caractères. À part PEM, aucun encodage standard n’exige des lignes aussi courtes, et PEM exige précisément des lignes de 64 caractères ; c’est donc un choix assez particulier
    J’ai écrit un article qui récapitule les différences entre langages de programmation et certaines bibliothèques JavaScript [1], et je travaille aussi à l’ajout d’un meilleur Base64 à JS [2]
    [1] https://gist.github.com/bakkot/16cae276209da91b652c2cb3f612a...
    [2] https://github.com/tc39/proposal-arraybuffer-base64