- 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
base64du 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-Za-z0-9+/
- Cet alphabet sert de jeu de caractères sûr pour éviter les situations où des caractères comme
<,>ou\nsont mal interprétés par d’anciens ordinateurs ou programmes - Encodé en Base64,
"Ruby on Rails"devientUnVieSBvbiBSYWlscw== - 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 ceci01000001 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 doncQWtzaGF5 - 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
Base64Base64.encode64("Ruby on Rails")Base64.decode64(encoded)
- C# convertit une chaîne en tableau d’octets, puis l’encode avec
Convert.ToBase64Stringet la décode avecSystem.Convert.FromBase64String - PHP fournit les fonctions globales
base64_encodeetbase64_decode - JavaScript encode avec
btoa()et décode avecatob() - Dans un terminal, la commande
base64permet aussi d’effectuer l’encodage et le décodageecho "akshay" | base64afficheYWtzaGF5Cg==echo "YWtzaGF5Cg==" | base64 -dafficheakshay
1 commentaires
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.
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.
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" |base64Sans
-n,echoajoute un caractère de nouvelle ligne à la fin de la chaîne, et ce caractère est encodé aussi.echo. https://linux.die.net/man/1/printfIl 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
~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
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
lou1, ouo,Oou0.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 bitsJ’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 ?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 Base64Les 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
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
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 avecA, carAsignifierait en tant que donnée000000. Moi aussi, j’ai dû tout lire deux fois pour comprendreMon 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 bitsUne 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 incorrecteIl 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