Vous avez à traiter le format Base64 ? Alors ce site est parfait pour vous ! Utilisez notre outil en ligne super pratique pour encoder ou décoder vos données.

Décodage Base64 en Bash : un guide complet

De temps en temps, une chaîne de lettres, de chiffres et, au besoin, de +, / ou = atterrit dans votre terminal, et vous avez besoin de la vraie chose. Un JWT collé dans un ticket, un fichier .b64 joint à un e-mail de support, un secret Kubernetes qui ressemble à une soupe de lettres, un PNG caché dans une balise data: HTML. Voici le guide de terrain pour récupérer les octets dans Bash et la shell, avec les outils que vous avez presque certainement déjà installés.

Le format en un souffle : le Base64 réécrit chaque groupe de trois octets de données brutes en quatre caractères tirés d'un alphabet de 64 lettres (A-Z, a-z, 0-9, plus + et /), et ajoute un ou deux signes = à la fin quand le nombre d'octets n'est pas un multiple de trois. Le décodage, c'est le sens qui rétrécit dans cet échange : quatre caractères entrent, trois octets sortent. La page d'accueil de ce site détaille le format pas à pas, donc cet article consacre son temps à la place où il doit passer, sur la partie shell du travail.

Voici le retournement de situation : il n'existe pas une seule commande base64. Le nom est partagé par un programme C de GNU, une réécriture en Rust, un applet BusyBox, un vestige BSD et un utilitaire OpenSSL dont les drapeaux se chevauchent d'une manière vraiment dangereuse. Tous s'accordent sur l'alphabet, mais pas toujours sur l'air qu'a une entrée cassée, et c'est là que les scripts vont mourir. Alors, première étape : découvrir qui répond quand vous tapez base64.

Connaître le décodeur auquel on parle

Une seule commande raconte l'essentiel de l'histoire :

base64 --version

Selon la machine, vous avez affaire à l'une de ces familles :

Ce que vous voyez Ce que vous avez Drapeau de décodage
base64 (GNU coreutils) 9.x L'implémentation C classique, encore la valeur par défaut sur la plupart des distributions Linux -d ou --decode
base64 (uutils coreutils) 0.8.x La réécriture de coreutils en Rust, le userland par défaut des sorties Ubuntu actuelles -d (et, chose inhabituelle, -D fonctionne aussi)
Texte d'utilisation de style BSD, sans drapeau de version Le base64 BSD de macOS et des BSD, descendant de l'ancien outil bintrans -D (dans cette famille, le -d minuscule signifie debug, pas décodage)
BusyBox v1.x Le binaire tout-en-un d'Alpine Linux et des systèmes embarqués -d

Remarquez ce qui manque dans ce tableau : OpenSSL. openssl base64 est un tout autre animal, et son drapeau -d signifie déchiffrer, pas décoder. Ce seul drapeau est responsable de plus de fichiers de sortie vides en silence que toute autre habitude de cet article, donc nous le rencontrerons comme il se doit dans la section de repli.

Si votre distribution embarque plusieurs familles côte à côte (c'est le cas de l'Ubuntu actuelle), deux commandes de plus vous montrent le tableau complet :

command -v base64
base64 --version 2>&1 | head -1

Quatre one-liners qui couvrent la plupart des journées

Décodez une chaîne depuis l'entrée standard. C'est le geste que vous ferez mille fois, et le printf empêche la shell de décorer votre payload :

printf '%s' "SGVsbG8sIFdvcmxkIQ==" | base64 -d

Décodez un fichier. Toute implémentation sérieuse accepte un argument FILE, et c'est le moyen le plus propre de tenir les données à l'écart du mécanisme de guillemets de la shell :

base64 -d payload.b64 > payload.bin

Décodez depuis une here-string. La here-string ajoute un saut de ligne à la fin, mais tous les décodeurs traitent les sauts de ligne comme des espaces ignorables, donc c'est totalement sûr pour les petits blobs :

base64 -d <<< "SGVsbG8sIFdvcmxkIQ=="

Décodez un blob enveloppé sur plusieurs lignes avec un heredoc. Mettre le délimiteur entre guillemets simples empêche la shell d'interpréter quoi que ce soit à l'intérieur :

base64 -d <<'EOF'
SGVs
bG8s
IFdv
cmxk
IQ==
EOF

Les quatre affichent Hello, World!. Sur macOS et les BSD, remplacez -d par -D dans tous les exemples ci-dessus ; le reste de la syntaxe est identique.

Une entrée en désordre, c'est la norme

Le Base64 que vous croisez dans la nature est rarement une seule ligne propre. Il arrive enveloppé à 76 caractères (convention MIME) ou 64 caractères (convention PEM), exporté depuis Windows avec des fins de ligne CRLF, ou copié depuis une fenêtre de chat avec des espaces égarés au milieu. La bonne nouvelle : le décodeur ne s'intéresse pas à l'endroit où tombent les coupures de ligne, tant qu'elles sont de vrais sauts de ligne.

Le remède universel pour n'importe quel style d'enveloppement est de laver les coupures de ligne avant de décoder :

tr -d '\r\n' < blob.b64 | base64 -d

Les retours chariot sont le cas particulier. Un saut de ligne est une entrée autorisée, mais un \r n'est pas un saut de ligne pour les décodeurs stricts. Un blob qui a traversé un système Windows fait trébucher le décodeur GNU, qui affiche un résultat partiel puis échoue ; la solution est de d'abord enlever les retours chariot :

printf 'SGVs\r\nbG8s\r\n' | tr -d '\r' | base64 -d

Si vous voyez des fragments comme Hel suivis d'une erreur, vous êtes en plein sur un blob CRLF. Le même lavage, tr -d '\r\n', avant le décodage est l'habitude portable pour toute entrée que vous n'avez pas produite vous-même.

Pour une entrée vraiment corrompue, GNU et uutils offrent le drapeau -i (--ignore-garbage), qui saute les caractères hors de l'alphabet et décode ce qu'il peut :

printf 'SGVs!bG8' | base64 -di

Cela affiche Hello. Avant de faire de -i une habitude par défaut, sachez pourquoi la norme met en garde contre : la RFC 4648, section 3.3, dit que les implémentations doivent rejeter les données contenant des caractères hors de l'alphabet, parce que les caractères ignorés peuvent être exploités comme canal caché qui fait passer en contrebande des données qui n'apparaissent jamais dans la sortie décodée. Utilisez -i quand un copier-coller depuis un document a amené de la ponctuation, pas quand vous vérifiez des données auxquelles vous faites confiance.

Voici comment les trois principaux décodeurs se comportent vraiment aux bords, avec les outils de 2026 (uutils 0.8.x, GNU coreutils 9.7, BusyBox 1.37) :

Entrée uutils 0.8.x GNU 9.7 BusyBox 1.37
SGVsbG8sIFdvcmxkIQ==, propre Hello, World!, exit 0 Hello, World!, exit 0 Hello, World!, exit 0
SGVsbG8s, huit caractères, sans remplissage Hello,, exit 0 Hello,, exit 0 Hello,, exit 0
Zg, deux caractères, sans remplissage f, exit 0 f, exit 0 erreur, « truncated input »
SGV, trois caractères, sans remplissage erreur, aucune sortie He affiché, puis erreur erreur, « truncated input »
Lignes enveloppées en CRLF décode sans souci, exit 0 sortie partielle, puis erreur décode sans souci, exit 0
SGVs!bG8, ponctuation égarée erreur (avec -i : Hello) sortie partielle (avec -i : Hello) sortie partielle (pas de drapeau -i du tout)
SGV=, bits de réserve non canoniques erreur, aucune sortie He affiché, puis erreur He, exit 0
TQ==junk, déchets après le remplissage exit 0, continue à décoder junk exit 0, continue à décoder junk M affiché, puis « truncated input » (exit 1)

Trois enseignements de ce tableau. D'abord, il n'y a pas de règle universelle pour les queues sans remplissage : BusyBox veut une longueur multiple de quatre, tandis que GNU et uutils acceptent les restes légaux mais seulement quand les bits restants sont tous nuls (c'est pourquoi Zg passe et SGV non). Deuxièmement, GNU et BusyBox écrivent les octets qu'ils ont déjà décodés avant d'échouer, donc un script qui redirige vers un fichier et vérifie le code de sortie ensuite gardera joyeusement un fichier moitié décodé. Vérifiez toujours l'état de sortie, et considérez tout fichier laissé par un décodage échoué comme suspect. Troisièmement, cette dernière ligne : uutils et GNU continuent de décoder junk après le == et sortent avec 0, parce que rien ne leur dit que le flux devait s'arrêter ; BusyBox fait exception, affichant l'octet avant le remplissage puis échouant avec une erreur d'entrée tronquée. Si les déchets de fin comptent pour vous, validez la forme de l'entrée avant de faire confiance à la sortie.

base64url : l'alphabet des jetons et des URL

La section 5 de la RFC 4648 définit un second dialecte : les mêmes calculs en 6 bits, mais avec - et _ qui remplacent + et /, et le remplissage supprimé, parce qu'une URL a rarement besoin de déclamer la longueur exacte en octets. La RFC est sans équivoque là-dessus : cet encodage « ne doit pas être considéré comme identique à l'encodage base64 ». Si vous avez déjà regardé un JWT, vous avez déjà croisé le dialecte, parce que ses segments sont du base64url dont on a retiré le remplissage.

La recette shell est un échange en deux temps : retranslatez les caractères URL-safe vers leurs cousins standards, puis décodez :

printf '%s' "_k-C" | tr '_-' '/+' | base64 -d | xxd -p

Cela renvoie fe4f82, trois octets bruts qui portaient juste une tenue d'URL. L'échange est positionnel, donc la direction compte : l'encodage passe par tr '+/' '-_', le décodage par tr '_-' '/+'. Les mélanger ne donne pas d'erreur ; cela produit simplement, en silence, d'autres octets, ce qui est le pire genre de bug.

Voici maintenant le piège qui attrape ceux qui traitent le base64url comme du Base64 ordinaire. Le remplissage est optionnel dans le dialecte, et un segment dont la longueur est trois modulo quatre est exactement la forme que les décodeurs stricts examinent de près. Le geste sûr est de rétablir d'abord les caractères = manquants, ce qui fait une petite fonction :

b64url_decode () {
  local s=$1
  local n
  n=$(printf '%s' "$s" | wc -c | tr -d '[:space:]')
  while [ $(( n % 4 )) -ne 0 ]; do
    s="${s}="
    n=$(( n + 1 ))
  done
  printf '%s' "$s" | tr '_-' '/+' | base64 -d
}
b64url_decode "eyJzdWIiOiJob21lciJ9"

La dernière ligne affiche {"sub":"homer"}, un objet JSON tout nu qu'un serveur web a signé ou scellé un instant plus tôt. Les segments dont la longueur est un modulo quatre sont mal formés de toute façon, et aucune quantité de remplissage ne les sauve, donc le rejet de ce cas par la fonction est une fonctionnalité.

Il y a aussi un chemin natif dans GNU coreutils : basenc, le grand frère de base64, comprend le dialecte directement :

printf '%s' "Zg==" | basenc --base64url -d

Cela affiche f, l'unique octet caché dans deux caractères. Un avertissement avant d'édifier des pipelines dessus : le basenc GNU de cette époque décode même une entrée base64url sans remplissage (un simple Zg) sans se plaindre, tandis que le basenc uutils veut encore que le remplissage soit rétabli avant, comme le montre l'exemple ci-dessus. La petite fonction ci-dessus fonctionne partout, et c'est pour ça qu'elle est le choix portable.

Ouvrir un JWT

Un JSON Web Token est trois segments base64url reliés par des points, selon la RFC 7515. Les deux premiers sont du JSON pur (l'en-tête et les claims du payload), donc ils se décodent directement en texte lisible. Le troisième est la signature, un digest binaire brut, alors ne le touchez pas : le décoder vous donne les octets de la signature, pas un message.

token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123"
b64url_decode "$(printf '%s' "$token" | cut -d. -f2)"

Cela affiche {"sub":"42"}, la claim subject, sans qu'aucun serveur soit impliqué. Lisez les claims avec la tête claire sur ce qu'est le décodage et ce qu'il n'est pas : il révèle, il ne vérifie pas. La signature ne dit rien tant que le serveur qui détient la clé ne l'a pas recalculée, et c'est le travail de openssl dgst (l'article sur l'encodage montre la chorégraphie complète de la frappe), pas de celui-ci. Une erreur de terrain courante est de traiter un payload décodé comme la preuve qu'un jeton est valide ; un attaquant peut fabriquer à la main des jetons non signés ou faiblement signés, et un décodeur les lira tous, joyeusement.

Fichiers, octets et le mur des variables

Le décodage vous remette des octets bruts. Ils peuvent épeler une phrase en anglais, ou ils peuvent être le milieu d'un PNG, d'une bibliothèque partagée ou d'une archive zip. Le fichier est le seul endroit d'un script shell où les octets sont totalement en sécurité, donc le parcours par défaut est décodage-vers-fichier puis comparaison :

base64 -d photo.b64 > photo.png

Quand l'original est à côté de vous, la comparaison octet par octet est la seule preuve qui compte :

cmp photo.png photo.png.orig && echo "byte-for-byte identical"

Quand l'original est loin, comparez les sommes de contrôle à la place :

sha256sum expected.bin
base64 -d blob.b64 | sha256sum

Deux hachages identiques et votre décodage est prouvable exact, ce qui bat n'importe quel examen visuel dans un visionneur d'images ou un dump hexadécimal.

Les variables shell sont une autre histoire, et elles forment un mur pour deux raisons. La substitution de commande $(...) retire tous les sauts de ligne finaux de la sortie, et elle ne peut tout simplement pas retenir un octet NUL : bash affiche un avertissement et les jette en silence. Un payload de trois octets, 41 00 42, montre les deux problèmes en une seule démo :

printf 'QQBC' | base64 -d > out.bin
xxd out.bin

Le fichier retient les trois octets (41 00 42). Le chemin par variable, non :

v=$(printf 'QQBC' | base64 -d)
printf '%s' "$v" | wc -c

Vous obtenez 2, avec un avertissement sur l'erreur standard au sujet de l'octet nul ignoré. La leçon est courte : si les données peuvent être binaires, décodez-les dans un fichier, inspectez-les avec xxd, et ne les laissez jamais passer par une variable.

Où le Base64 se cache dans le vrai travail

Dès que le décodage devient confortable, vous commencez à repérer le format partout. Voici les coins du vrai travail shell où il apparaît, chacun avec le geste exact.

Secrets Kubernetes. Chaque champ sous .data d'un secret est du Base64, et la documentation officielle souligne que c'est de l'encodage, pas du chiffrement. Relire un secret est un one-liner classique :

kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d; echo

Patches binaires git. Quand un diff touche des fichiers binaires, git diff --binary émet un bloc GIT binary patch. Ne tendez pas la main vers base64 -d ici : les lignes de ce bloc sont l'encodage de style base85 de git (chaque ligne commence par un caractère de longueur, A-Z ou a-z, suivi de données base85), pas du Base64, et un décodeur ordinaire s'étouffe dessus. Le bon outil est le propriétaire du format :

git diff --binary | grep -a -A2 'GIT binary patch'

Donnez le diff à git apply ou git am, et laissez-les faire le déballage.

Data URIs. Une image embarquée dans du HTML ou du CSS a l'air de data:image/png;base64,iVBOR.... Supprimez tout jusqu'à la virgule incluse, lavez les coupures de ligne, décodez, et le fichier est à vous :

cut -d, -f2- icon.uri | tr -d '\r\n' | base64 -d > icon.png

Armure PEM. Les certificats et les clés privées enveloppent leur Base64 dans des lignes de cadre qui ne sont pas du tout du Base64. Sélectionnez le bloc armuré, jetez les deux lignes de cadre, et décodez vers le binaire DER brut :

awk '/BEGIN CERTIFICATE/{f=1;next} /END CERTIFICATE/{f=0} f' cert.pem | tr -d '\r\n' | base64 -d > cert.der

Remplacez CERTIFICATE par PRIVATE KEY ou l'étiquette que porte votre fichier ; la forme est la même.

E-mails MIME. Toute partie d'un e-mail avec Content-Transfer-Encoding: base64 est enveloppée à 76 caractères avec des fins de ligne CRLF, parce que c'est ce que la RFC 2045 prescrit. La combinaison portable est le lavage plus le décodage :

tr -d '\r\n' < attachment.b64 | base64 -d > attachment.bin

Collages du presse-papiers. Le texte copié depuis un navigateur, une fenêtre de chat ou un document arrive avec des espaces égarés et un saut de ligne final. Les espaces ne sont pas des caractères de l'alphabet, donc les décodeurs stricts rejettent le collage ; le sauvetage standard est de les jeter d'abord :

tr -d ' \r\n' < pasted.b64 | base64 -d

Les octets d'abord : charsets et Unicode

L'erreur la plus répandue dans le travail Base64 est de penser en caractères alors que le format ne connaît que les octets. base64 -d vous remet des octets bruts, et le fait qu'ils deviennent du texte lisible est une décision prise par ce qui les lit ensuite. Cette décision est un charset, et elle a lieu après le décodage, jamais à l'intérieur.

UTF-8 est l'hypothèse par défaut et, d'habitude, la bonne. Le mot café en UTF-8 fait cinq octets, et le décodage est un plaisir :

printf 'Y2Fmw6k=' | base64 -d | xxd -p

C'est 636166c3a9 : caf plus les deux octets UTF-8 c3 a9 du é. Les systèmes plus anciens, eux, vous remettront des octets Latin-1 (ISO-8859-1), où la même lettre est l'unique octet e9. Décoder un tel blob et l'afficher tel quel dans un terminal UTF-8 vous donne un caractère déformé ; la solution est de réinterpréter les octets avec iconv avant que quoi que ce soit d'autre ne les voie :

base64 -d latin1.b64 | iconv -f ISO-8859-1 -t UTF-8 > utf8.txt

Quelques faits de niveau octet qui font gagner un vrai temps de débogage :

  • Le BOM UTF-8, c'est les trois octets ef bb bf, qui s'encodent en 77u/. Remarquez le /, un caractère hostil pour les URL, exactement le genre de chose que base64url existe pour corriger. Si un fichier décodé « a des déchets invisibles devant », vérifiez les trois premiers octets avec xxd.
  • Un emoji comme 😀 est quatre octets UTF-8 et devient huit caractères Base64, 8J+YgA==. Le + là-dedans est l'alphabet standard qui fonctionne exactement comme prévu ; en tenue base64url, il devient 8J-YgA.
  • Les suites UTF-8 invalides se décodent parfaitement bien en octets, puis s'affichent en charabia ou en caractères de remplacement. Ce n'est pas un échec du Base64 ; le décodage a fait son travail. xxd ou hexdump -C vous montre à quoi ressemblent vraiment les octets.
  • La locale de votre terminal décide comment il affiche ces octets. « Le terminal affiche du charabia » est une affirmation sur l'affichage, pas sur les données. Les octets n'ont pas changé en route.

Gros blobs : flux, coupes et vitesse

Le Base64 est un format de flux, et le décodeur fonctionne comme un vrai tuyau de streaming : un fichier de 10 GB ne repose jamais en mémoire ; il passe juste au travers. Cela rend le côté décodage presque ennuyeux à grande échelle, ce qui est exactement ce qu'on veut.

Quand un gros blob a été coupé en morceaux pour le transfert (une limite d'e-mail, une taille d'attachement de ticket, un message IM), le réassemblage est juste un cat dans le bon ordre, suivi du lavage habituel :

cat part_* | tr -d '\r\n' | base64 -d > big.bin

Gardez un modèle mental pour les tailles : la forme encodée est toujours environ un tiers plus grosse que l'original, quatre caractères pour trois octets. Donc quand quelqu'un vous dit que le fichier .b64 devrait faire la même taille que ce qu'il cache, il a tort, et vous pouvez maintenant lui dire de combien : un payload de 300 MB arrive sous forme d'environ 400 MB de texte.

La vitesse n'est pas un souci en pratique. Ce sont des boucles pilotées par table sur de la mémoire ordinaire, et sur une machine moderne un blob de 200 MB se décode en une dixième de seconde environ avec GNU, et même la plus lente des implémentations courantes (BusyBox) n'est que quelques fois plus lente que ça, encore bien en deçà de deux secondes. L'usage mémoire reste plat quelle que soit la taille de l'entrée, parce que rien n'est tamponné.

Quand la machine n'a pas de base64

La plupart des systèmes ont au moins un des outils ci-dessus, et la plupart en ont plusieurs. Si votre plateforme n'a tout simplement pas de coreutils (un container amputé, un boîtier atypique), les chemins d'installation ressemblent à ça :

Plateforme Comment l'obtenir Notes
Debian / Ubuntu préinstallé ; apt install coreutils si amputé Les sorties Ubuntu actuelles ont la famille uutils par défaut (depuis 25.10), et le jumeau GNU est accessible sous gnubase64 ; Debian 13 embarque encore GNU coreutils par défaut
RHEL / Fedora dnf install coreutils préinstallé sur pratiquement toutes les images
Alpine apk add busybox (présent d'habitude déjà) busybox base64 -d ; pas de drapeau -i
macOS intégré ; brew install coreutils pour la version GNU Drapeaux BSD : -D pour décoder, -b pour la largeur de ligne ; brew vous donne gbase64

Et si aucun gestionnaire de paquets n'est une option du tout, les plans B universels ci-dessous lisent tous de l'entrée standard et écrivent des octets vers la sortie standard, donc ils se glissent dans les mêmes pipelines :

openssl base64 -d -A -a < in.b64 > out.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out.bin
python3 -c 'import base64, sys; sys.stdout.buffer.write(base64.b64decode(sys.stdin.buffer.read()))' < in.b64 > out.bin

Sur un système BusyBox dont l'applet base64 est compilée hors du binaire, l'ancienne garde fonctionne encore : uuencode -m produit du MIME Base64, et son frère le relit :

busybox uudecode -o out.bin in.uu

Les pièges qui mangent des après-midis entiers

Tout ce qui suit est des comportements que vous croisez sur le terrain, rassemblés au même endroit :

Le piège Ce qui se passe Le correctif
openssl base64 -d tout seul Ne fait rien en silence et sort avec 0, parce que -d signifie déchiffrer là-bas openssl base64 -d -A -a, et traitez un résultat de zéro octet comme une erreur
Décoder sur macOS avec -d Le base64 BSD rejette le drapeau (ou le prend pour du debug) -D, ou installez coreutils pour gbase64
Fins de ligne CRLF dans l'entrée GNU affiche un résultat partiel puis échoue ; uutils et BusyBox l'acceptent tr -d '\r\n' d'abord, toujours pour une entrée étrangère
Queues sans remplissage BusyBox rejette tout reste ; GNU et uutils n'acceptent que les queues canoniques rétablissez le remplissage = manquant avant de décoder
Déchets après le remplissage, par ex. TQ==junk uutils et GNU continuent à décoder et sortent avec 0 ; BusyBox fait une erreur validez la forme de l'entrée avant de faire confiance à la sortie
Bits de réserve non canoniques, par ex. SGV= uutils et GNU rejettent (GNU après sortie partielle) ; BusyBox décode quand même l'entrée est corrompue ; régénérez l'encodage en amont
Lire la sortie décodée dans une variable $(...) retire les sauts de ligne finaux et ne peut tout simplement pas retenir d'octets NUL décodez dans un fichier, inspectez avec xxd
-i sur une entrée non fiable la corruption devient une réussite silencieuse ; les caractères ignorés peuvent porter des données cachées décodez en strict, lisez l'erreur, corrigez la source
Mélanger les deux alphabets décoder du base64url en standard (ou l'inverse) donne de mauvais octets ou une erreur connaissez le format avant de décoder ; l'échange est tr '_-' '/+'
Traiter un secret décodé comme un secret une seule commande le défait ; la RFC recense de vrais incidents de données de connexion fuitées du chiffrement de vrai, pas de l'encodage

La ligne OpenSSL mérite un paragraphe à elle seule, parce que l'échec est si silencieux. Sur OpenSSL 3.x, le -d de l'app standalone est son option générale « déchiffrer », et le traitement Base64 est un mode à part sélectionné avec -a. Donc openssl base64 -d lit votre entrée, ne fait rien, n'affiche rien, et sort avec 0. Le décodage qui marche est openssl base64 -d -A -a, où -A lui dit que l'entrée est une ligne continue. Si vous devez utiliser OpenSSL pour décoder, traitez une sortie de zéro octet comme une erreur, à chaque fois.

Une routine bien serrée

Les habitudes qui empêchent le Base64 de jamais avoir le dessus sur vous :

  • Échouez à voix haute. Lancez vos scripts avec set -euo pipefail et vérifiez les codes de sortie. Tous les décodeurs courants sortent avec 1 sur une mauvaise entrée ; OpenSSL est le bruyant qui est devenu silencieux, donc pour lui, vérifiez aussi que la sortie n'est pas vide.
  • Prouvez l'aller-retour. Un cmp ou sha256sum entre les octets attendus et les octets restaurés est la seule preuve qui compte. Ne jugez jamais du binaire à l'œil.
  • Binaire vers fichiers, jamais vers variables. Les sauts de ligne finaux et les octets NUL sont toutes deux des victimes de la substitution de commande.
  • Lavez les entrées étrangères. tr -d ' \r\n' avant de décoder quoi que ce soit qui a franchi une frontière de plateforme.
  • Restez strict par défaut. Gardez -i éteint jusqu'à avoir vu ce qui était exactement le problème ; un échec strict vous donne l'endroit, un échec tolérant ne vous donne rien.
  • Nommez l'alphabet. Base64 standard et base64url sont des encodages différents selon la RFC 4648. Décodez un JWT en base64url, une pièce jointe d'e-mail en standard, et ne changez jamais les caractères sans savoir pourquoi.
  • N'affichez jamais ce que vous venez de décoder. Tout l'intérêt du format dans le monde des secrets est l'invisibilité ; tout l'intérêt d'un log est la visibilité. Ces deux buts ne se mélangent pas.

Un petit shim portable pour la question « quel drapeau cette machine veut-elle » :

case "$(base64 --version 2>&1 | head -2)" in
  *uutils*|*GNU*|*BusyBox*) b64d () { base64 -d; } ;;
  *)                        b64d () { base64 -D; } ;;
esac
b64d < in.b64 > out.bin

La case attrape les familles GNU, uutils et BusyBox par leur bannière de version - les trois acceptent le drapeau minuscule - et retombe sur le drapeau BSD pour tout le reste, ce qui est exactement le partage en quatre du monde réel : GNU, uutils, BusyBox, BSD.

Comment la shell a appris à décoder

Le format est vieux ; la commande que vous tapez, non. Une petite frise chronologique du côté shell de l'histoire :

  • 1980, Berkeley. Mary Ann Horton écrit uuencode et uudecode à l'Université de Californie, Berkeley, pour faire voyager des fichiers binaires (d'habitude compressés) par e-mail. Le nom signifie « encodage Unix-vers-Unix », un encodage sûr pour déplacer des fichiers entre systèmes Unix qui ne partagent pas forcément un charset. Pendant des décennies, c'est cela, pas le Base64, que les utilisateurs de shell recherchent.
  • L'ère du modulateur. Les premiers encodages de base reposent sur le même problème : uuencode sur UNIX, BinHex sur le TRS-80 et l'Apple II avec le Macintosh un peu derrière, chacun ne supposant que les caractères que son propre terminal pouvait imprimer.
  • 1993. MIME standardise le Base64 pour l'e-mail (RFC 1521, plus tard RFC 2045), avec l'enveloppement à 76 caractères qui définit encore aujourd'hui la valeur par défaut de base64.
  • 2003 et octobre 2006. La RFC 3548 met de l'ordre dans la famille, et la RFC 4648 la remplace avec les alphabets et la règle de décodage strict que cet article ne cesse de citer, base64url compris.
  • 15 août 2006. coreutils 6.0 ajoute la commande base64 elle-même, et son fichier NEWS la crédite sans détour en tant que « fonctionnalité d'encodage et de décodage Base64 (RFC 3548) ». Avant cette date, les utilisateurs de shell sur Linux allaient vers openssl base64, uuencode -m, Perl ou Python, et c'est pourquoi tant d'anciens scripts supposent qu'OpenSSL est la seule option en ville.
  • OS X 10.7. macOS embarque son propre base64, la saveur BSD avec le drapeau -D et sans enveloppement par défaut, et c'est d'où vient la division entre -d et -D.
  • Mars 2024. coreutils 9.5 assouplit le décodeur : le remplissage n'est plus requis au décodage, et les encodages avec des bits de réserve non nuls sont maintenant diagnostiqués comme corruption au lieu d'être acceptés en silence.
  • 2025. La réécriture de coreutils en Rust (uutils) devient la valeur par défaut des sorties Ubuntu actuelles. Même nom de commande, mêmes drapeaux, un nouveau moteur avec ses propres opinions sur les bords, comme accepter -D comme alias.

Des curiosités qui méritent d'être gardées

  • La commande est plus jeune que le format. Le Base64 est dans l'e-mail depuis 1993, mais la commande base64 n'est apparue qu'en 2006. Treize années de scripts shell ont fait ce travail avec d'autres outils, et vous pouvez encore trouver leurs empreintes partout.
  • Un fossile dans le texte d'aide. Le base64 uutils décrit encore son alphabet comme « RFC 3548 » dans son aide, l'ancêtre retraité de la RFC 4648, tandis que son cousin GNU cite déjà la norme actuelle. Un petit fossile, visible seulement si vous lisez l'aide.
  • Le no-op silencieux le plus cher de la boîte à outils. openssl base64 -d ne fait rien et sort avec 0. Une heure de débogage entière, partie, et le test du code de sortie a passé.
  • BusyBox garde les bits de réserve. Il décode SGV= sans problème, dont les bits restants sont non nuls donc non canoniques, tandis que son cousin GNU s'emporte sur la même entrée. Même RFC, nerfs différents.
  • Le nom du format est vrai sur chaque machine. printf 'base64' | base64 donne YmFzZTY0 sur GNU, uutils, BusyBox et OpenSSL, tous pareils. C'est vrai depuis 2006 et le sera toujours.
  • Le Base85 n'est pas le Base64. Les blocs « binary patch » de git ressemblent au Base64 pour l'œil non entraîné, mais les lignes avec longueur en préfixe sont un dialecte de style base85 à part. L'imposteur qui coûte à certains un détour grep-et-décoder.
  • Onze caractères, soixante-quatre bits. Un identifiant de vidéo YouTube est une chaîne base64url de 11 caractères, un nombre de 64 bits vêtu d'un costume URL, et c'est pourquoi il peut apparaître dans une URL sans le moindre signe pourcent.
  • Les décodeurs ne s'accordent pas sur un caractère. Une seule chaîne comme SGV= divise le champ en trois camps, comme le montre le tableau de comportement ci-dessus. Si un décodage échoue sur une entrée « manifestement valide », vous êtes probablement sur la ligne des bits de réserve, et l'encodage n'a jamais été canonique pour commencer.

Alors la prochaine fois qu'une chaîne de lettres, de chiffres, de plus et de barre oblique atterrit dans votre terminal, vous connaissez toute l'histoire : quel décodeur regarde, quel alphabet parle, où se cachent les sauts de ligne, et exactement comment récupérer les octets, intacts et prouvés octet par octet, sans perdre une heure à un retour chariot. Et quand le travail pointe dans l'autre sens, emballer votre binaire dans une enveloppe de texte pour le voyage, l'article lié sur l'encodage Base64, en lien ci-dessous, couvre ce rituel avec la même profondeur.

Dernière mise à jour : 2026-09-08

Article associé : Encodage Base64 en Bash : un guide complet