Décodage Base64 en Perl : un guide complet
On vous a confié une chaîne de lettres, de chiffres, et, de temps en temps, un + ou un /, et vous savez au fond de vous que ce n'est pas ce que ça semble être. Peut-être est-ce un token qui voyage dans un en-tête Authorization, un fichier .b64 exhumé d'un ticket de support, un certificat revêtu de son armure -----BEGIN, ou un blob bien tranquille au fond d'un fichier de configuration. Vous ouvrez un terminal, vous tapez perl, et une seule question s'empare de tout le reste : comment récupérer les vraies données ?
La réponse est courte et rassurante. Perl livre un module Base64 avec le langage lui-même depuis 2002, et un seul appel de fonction, decode_base64, fait tout le travail : rien à installer, rien à configurer. Un petit rappel pendant que le café infuse : le Base64 réécrit tous les trois octets de données en quatre caractères tirés d'un alphabet de 64 symboles, en complétant la fin avec un ou deux signes = pour que le résultat tombe toujours sur un multiple de quatre, ce qui explique que la forme encodée soit typiquement environ 33 pour cent plus longue que l'origine. La page d'accueil de ce site détaille le format en profondeur, donc ce guide passe tout son temps du côté Perl : les règles du décodeur, les dialectes, et les formats du monde réel que vous croiserez vraiment.
La boîte à outils : cinq fonctions, zéro installation
Tous les appels dont vous avez besoin vivent dans MIME::Base64, qui fait partie de la distribution core de Perl depuis la 5.8, si bien que le module est présent sur toute installation sérieuse, de celle embarquée dans le firmware d'un routeur à celle d'un serveur de base de données. La vérification tient en une ligne :
perl -MMIME::Base64 -e 'print $MIME::Base64::VERSION, "\n"'
# 3.16_01
Voici le côté décodage du module, dans sa totalité :
| Fonction | Ce qu'elle fait | Notes |
|---|---|---|
decode_base64($str) |
l'étoile de cet article : transforme un blob Base64 en octets bruts | ignore silencieusement chaque caractère hors alphabet, pour toujours |
MIME::Base64::decode($str) |
le même décodeur, appelé sans import | la forme que vous croiserez dans pas mal de scripts plus anciens |
decode_base64url($str) |
décode le dialecte sûr pour les URL avec - et _, avec ou sans padding |
ajoutée en 3.11 en 2010 ; c'est elle qui lit les JWT |
MIME::Base64::decoded_base64_length($str) |
annonce la taille des données une fois décodées, sans décoder | non exportée par défaut, pratique pour prédimensionner les buffers |
unpack("u", $data) |
décode les données uuencodées, le format d'avant le Base64 | intégrée à Perl lui-même, aucun module requis |
La carte des versions de ces fonctions, au cas où vous entretieneriez une flotte de vieilles machines :
| Fonctionnalité | Disponible depuis |
|---|---|
decode_base64() avec le chemin rapide en C |
Perl 5.8 en 2002, quand le module a rejoint le core |
decoded_base64_length() |
module 3.10 en 2010 |
decode_base64url() |
module 3.11 en 2010 |
| Décodage silencieux, aucun avertissement sur les entrées suspectes | module 3.11 en 2010 |
| La ligne 3.16 actuelle | 2020, exige Perl 5.6 ou plus récent |
Si le Perl de votre système n'a pas le module pour une raison quelconque, et il ne devrait pas, la correction tient en une de ces deux lignes : le paquet de distribution libmime-base64-perl sur Debian et Ubuntu, ou cpanm MIME::Base64 pour tirer la version actuelle de CPAN, où le module vit en paquet dual-life depuis ses jours de core. Pour la machine curieuse sans compilateur C, la jumelle purement Perl MIME::Base64::Perl sur CPAN fournit la même interface de base, quelques fois plus lente mais largement suffisante pour tout sauf le travail de masse. C'est toute l'histoire des dépendances : rien d'autre.
Un décodeur qui ne dit jamais non
Le contrat tient en une ligne. Donnez-lui une chaîne, et il vous rend les octets décodés en guise de chaîne Perl ordinaire contenant des octets bruts. Pas d'objets, pas d'exceptions, pas de drapeaux. La documentation résume en une seule phrase les deux règles qui définissent son caractère : tout caractère qui ne fait pas partie du sous-ensemble Base64 de 65 caractères est ignoré silencieusement, et tout caractère apparaissant après un caractère de padding = n'est jamais décodé. Cette politesse est le point le plus important de cet article, alors laissez-la travailler pour vous une fois :
use MIME::Base64 qw(decode_base64);
print decode_base64("TWFu!"), "\n"; # Man - le point d'exclamation s'évapore sans laisser de trace
print decode_base64("TWFu=XX"), "\n"; # Man - tout ce qui suit le = est sauté
print decode_base64("TQ"), "\n"; # M - pas d'avertissement, pas de commentaire
print decode_base64("T"), "\n"; # la chaîne vide, et toujours pas de plainte
Les deux dernières lignes sont l'indulgence à son extrême. TQ porte un octet complet plus quatre bits en trop, et le décodeur garde simplement l'octet et jette le reste. T ne porte même pas un octet complet, d'où un résultat vide. Il n'y a ni mode strict ni validateur dans le module pour réinstaurer la maniaquerie old school : depuis la version 3.11 en 2010, decode_base64 ne prévient même plus sur une entrée tronquée, et les versions plus anciennes marmonnaient un avertissement Premature end of base64 data sous -w. Si le blob est faussé, il décode quand même, ce qui fait de vous le contrôleur qualité.
Voici la politique d'indulgence réunie en un seul endroit, pour la voir toute entière d'un coup d'œil :
| Entrée | Résultat | Pourquoi |
|---|---|---|
"TWFu" |
Man |
entrée propre, le chemin sans accroc |
"TWFu!" |
Man |
le point d'exclamation n'est pas dans l'alphabet, donc il est sauté |
"TWFu=XX" |
Man |
rien n'est décodé après le padding |
"TWFuIFdvcmxkIQ==" |
Man World! |
les espaces sont bienvenus n'importe où |
"TQ" |
M |
un octet complet passe, les bits en trop sont jetés discrètement |
"T" |
la chaîne vide | pas même un octet complet, et pas d'avertissement non plus |
"ab-cd_efgh" |
des octets faussés, silencieusement | les lettres sûres pour les URL sont jetées comme du bruit, le piège classique |
Cette dernière ligne est celle à retenir. Un segment base64url remis au décodeur standard ne plante pas : il se décode en une ordure qui a l'air plausible, parce que les caractères - et _ sont traités comme du bruit étranger tandis que les lettres restantes forment encore des groupes valides. Le décodeur est un témoin, pas un gardien, donc si l'entrée n'est pas de confiance, c'est vous qui la validez. Une petite vérification stricte suffit :
sub strict_base64 {
my ($blob) = @_;
$blob =~ s/[\r\n]//g; # le décodeur ignore ces caractères, et nous aussi
return 0 unless length($blob) % 4 == 0;
return $blob =~ /\A[0-9A-Za-z+\/]+(?:={1,2})?\z/ ? 1 : 0;
}
print strict_base64("TWFu"), "\n"; # 1
print strict_base64("TQ="), "\n"; # 0 - mauvais nombre de padding
print strict_base64("ab-cd"), "\n"; # 0 - alphabet sûr pour les URL
Un petit mot sur cette expression régulière, tiré d'une leçon payée cash : si une sub se termine par un return $x =~ /.../ tout nu et que le résultat d'un échec de correspondance est passé tel quel dans printf, Perl lève un avertissement Missing argument in printf trompeur au lieu d'un zéro propre. Forcez la correspondance avec ? 1 : 0 avant de retourner, comme le fait la fonction ci-dessus, et le tour disparaît complètement.
Les octets d'abord, les caractères ensuite
Souvenez-vous de ce que decode_base64 renvoie : des octets bruts, une chaîne ordinaire sans drapeau UTF-8. Ce que ces octets veulent dire est une décision que seul vous pouvez prendre, et c'est l'étape où l'Unicode fait trébucher tout le monde. Perl suit si une chaîne contient des caractères ou des octets, et length(), substr(), et la plupart des expressions régulières se comportent différemment selon la réponse. La solution, c'est de nommer votre encodage exprès, avec le module Encode livré avec chaque installation de Perl :
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $raw = decode_base64("SMOrbGxvIFdvcmxkIQ==");
my $text = decode("UTF-8", $raw);
print $text, "\n"; # Hëllo World!
print length($text), " chars\n"; # 12
print length($raw), " bytes\n"; # 13
Cette paire de nombres est toute la leçon. Le blob fait 13 octets mais seulement 12 caractères, parce que la lettre accentuée prend deux octets en UTF-8. Sautez l'étape du charset et les octets s'afficheront quand même sans problème dans un terminal UTF-8, ce qui est exactement la raison pour laquelle l'erreur reste cachée jusqu'à ce qu'une fonction de chaîne les compte, ou que les octets traversent une chaîne de traitement qui attend des caractères. En cas de doute, décodez avec un charset strict et laissez l'exception vous dire la vérité sur les octets : passez Encode::FB_CROAK et decode() meurt sur les séquences invalides au lieu du remplacement discret par U+FFFD de la valeur par défaut, ce qui est un vrai avantage.
La courte liste des charsets que vous utiliserez vraiment :
| Charset | Quand l'utiliser | À surveiller |
|---|---|---|
UTF-8 |
l'hypothèse par défaut : API, JSON, contenu web, texte moderne | les séquences invalides deviennent U+FFFD par défaut ; avec Encode::FB_CROAK elles meurent proprement, ce qui est exactement ce qu'on veut |
Latin-1 |
texte occidental legacy, un octet par caractère, ne peut jamais échouer | elle transformera joyeusement du UTF-8 en charabia doublement encodé |
ASCII |
des données que vous êtes sûr d'être du texte 7 bits pur | tout octet au-dessus de 127 devient U+FFFD par défaut (meurt sous Encode::FB_CROAK) |
UTF-16 |
texte Windows, où la marque d'ordre des octets décide de l'endianness | le BOM est le seul indice d'endianness, donc gardez-le dans les octets |
Et voici le piège contre lequel l'étape du charset vous protège. Si les octets que vous avez décodés sont déjà du UTF-8 et que vous les passez dans encode("UTF-8", ...) à la sortie, vous n'obtenez pas une copie : vous obtenez un double encodage, où chaque caractère accentué gonfle en deux caractères de son propre chef. Le symptôme classique, c'est un texte qui se lisait Hëllo et qui se lit désormais Hëllo, et le récepteur de l'autre bout du câble le décodera fidèlement. Des octets en entrée, des octets en sortie, une seule conversion nommée entre les deux.
base64url : l'alphabet des URL et des tokens
La moitié du Base64 qui traverse l'internet moderne n'est pas du tout l'alphabet standard. Le caractère + est la façon dont un navigateur encode une espace dans une chaîne de requête, et / est un séparateur de chemin, donc les lettres standard sont un désastre dans les URL. La RFC 4648, section 5, définit la solution : un deuxième alphabet qui échange + et / contre - et _, et qui, par convention, jette aussi le padding = et les retours à la ligne. La RFC est explicite : cet encodage ne doit pas être considéré comme identique à l'encodage base64, et Perl a un duo dédié pour ça depuis la version 3.11 en 2010 :
use MIME::Base64 qw(decode_base64url);
my $raw = decode_base64url("c3Vuc2V0LTQy");
print $raw, "\n"; # sunset-42
Deux choses à savoir. D'abord, decode_base64url est contente d'une entrée sans padding, ce qui est la forme que vous trouverez vraiment dans la nature, donc le rituel de restaurer d'abord le padding que d'autres langues exigent ne s'applique pas ici ; une entrée avec padding fonctionne aussi. Ensuite, le décodeur standard est une autre bête : nourrissez-le d'un segment base64url et vous obtenez des octets faussés, silencieusement, parce que les caractères - et _ sont jetés comme du bruit et que le reste se décode quand même. Utilisez le bon décodeur, ou normalisez à la main quand vous êtes coincé sur un chemin de code legacy :
my $seg = "ab-cd_efgh";
$seg =~ tr{-_}{+/}; # les lettres sûres pour les URL, traduites au pays
$seg .= "=" x (-length($seg) % 4); # le padding restauré pour le décodeur standard
my $raw = decode_base64($seg);
print unpack("H*", $raw), "\n"; # 69bf9c77f79f82 - sept octets, revenus dans l'alphabet standard
Vous croiserez base64url immédiatement dans les JWT, les tokens que chaque API moderne remet, et dans n'importe quel ID opaque qui vit dans une URL : des ID de vidéo de onze caractères, des UUID stockés dans l'alphabet sûr pour les URL (CPAN a Data::UUID::Base64URLSafe exactement pour ça), et des clés de base de données qui doivent survivre à une barre d'adresse. Et si vous êtes sur un vieux Perl antérieur aux fonctions du core, le module autonome MIME::Base64::URLSafe de 2006, un port du codec urlsafe de Python, fournit urlsafe_b64encode et urlsafe_b64decode ; sur tout ce qui est 3.11 ou plus, les fonctions intégrées sont le bon choix.
Les JWT : lire l'en-tête et le payload
Un JSON Web Token est, structurellement, deux morceaux de JSON déguisés plus un reçu cryptographique. La forme compacte de la RFC 7515, ce sont trois segments base64url reliés par des points : l'en-tête protégé, le payload, et la signature. Découper et lire un token tient en trois lignes :
use MIME::Base64 qw(decode_base64url);
use JSON::PP;
my $jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJob21lciJ9.uzM6l0c4...";
my ($head_b64, $claims_b64, $sig_b64) = split /\./, $jwt, 3;
my $head = decode_json(decode_base64url($head_b64));
my $claims = decode_json(decode_base64url($claims_b64));
print $claims->{sub}, " (", $head->{alg}, ")\n"; # homer (HS256)
Notez la division du travail : decode_base64url transforme chaque segment en octets, et decode_json du module core JSON::PP, présent depuis Perl 5.14, transforme les octets de l'en-tête et du payload en structures de données Perl. Le segment de signature est aussi du base64url, mais c'est un digest cryptographique, donc vous ne décodez que les deux premiers segments et laissez une vraie bibliothèque s'occuper du troisième.
Le piège, c'est celui que tout le monde oublie : lisible ne veut pas dire valide. L'en-tête et le payload sont lisibles par conception, ce qui veut aussi dire que n'importe qui peut les réécrire ; la signature est la seule preuve. Pour tout ce qui est réel, vérifiez, ne vous contentez pas de décoder. Le module CPAN Crypt::JWT, qui se construit sur CryptX, fait tout le travail :
use Crypt::JWT qw(decode_jwt);
my $claims = decode_jwt(
token => $jwt,
key => $secret,
accepted_alg => "HS256",
);
Il croak sur une mauvaise signature, et fixer accepted_alg referme le trou de confusion d'algorithmes où un attaquant bascule le token vers une variante plus faible. Un flux décodage-affichage est parfait pour inspecter un token pendant un appel de support ; ce n'est pas une authentification.
Les fichiers : du .b64 jusqu'à l'original
Les fichiers, c'est là que la culture des one-liners de Perl brille vraiment, et tout le travail tient dans une seule commande. Le drapeau -0777 est l'ingrédient secret, parce qu'il avale le fichier entier dans une seule chaîne au lieu de nourrir le décodeur ligne par ligne :
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out
La forme ligne par ligne est sûre pour une seule classe précise de fichiers : ceux où chaque ligne contient un multiple de quatre caractères Base64, ce qui est vrai de tout corps correctement enveloppé en MIME, puisque 76 est un multiple de 4. Le moment où les points d'enveloppe deviennent irréguliers - et dans les fichiers enveloppés à la main, c'est souvent le cas - le décodage ligne par ligne commence à produire du padding au milieu des données. Le mode avaleur n'a pas cette condition, ce qui en fait le choix par défaut :
perl -MMIME::Base64 -ne 'print decode_base64($_)' < in.b64 > out
Dans un script, le motif est la danse fichier standard de Perl, avec un détail discret mais important : les calques :raw sur les deux poignées, pour que Perl n'essaie jamais d'interpréter les octets comme du texte de la plateforme en entrant ou en sortant :
use MIME::Base64 qw(decode_base64);
use Digest::SHA qw(sha256_hex);
open my $in, "<:raw", $ARGV[0] or die $!;
local $/;
my $blob = <$in>;
close $in;
my $decoded = decode_base64($blob);
print sha256_hex($decoded), "\n"; # comparez avec la somme de contrôle de l'expéditeur
open my $out, ">:raw", $ARGV[1] or die $!;
print {$out} $decoded;
close $out;
La ligne de hash fait plus que faire parade. Parce que le décodeur acceptera presque n'importe quoi, une somme de contrôle qui correspond à ce que l'expéditeur a publiée est la seule preuve que le trajet a été exact à l'octet près. Pour les fichiers vraiment géants, la boucle ligne par ligne est l'alternative économe en mémoire, à condition que les enveloppes tombent sur des frontières de quatre caractères, et MIME::Base64::decoded_base64_length vous dit à l'avance quelle sera la taille de la sortie avant que vous ne vous engagiez sur un buffer.
L'armure PEM : retirer le manteau, garder le DER
Les fichiers .pem de toute pile de sécurité, c'est le même Base64 revêtu d'une armure : une ligne d'en-tête, une ligne de pied, et un corps enveloppé à 64 caractères selon l'ancienne convention Privacy Enhanced Mail. L'enveloppe est la seule partie intéressante, parce que le décodeur du module ne se soucie absolument pas des longueurs de ligne :
use MIME::Base64 qw(decode_base64);
open my $fh, "<:raw", "cert.pem" or die $!;
local $/;
my $blob = <$fh>;
close $fh;
my @body = grep { !/^-----/ && /\S/ } split /\n/, $blob;
my $der = decode_base64(join "", @body);
print length($der), " bytes of DER\n";
Les lignes BEGIN et END sont retirées, le reste est joint en une seule chaîne, et chaque retour à la ligne est ignoré en chemin. Pour le travail courant sur les certificats, l'outillage OpenSSL le fait déjà pour vous ; les huit lignes ci-dessus sont le motif à retenir quand vous avez besoin vous-même des octets DER bruts, pour un hash, une empreinte, ou une comparaison.
Les data URIs : des images qui portent leur propre adresse
Le schéma data: de la RFC 2397 place un payload directement dans une URL : data:, un type média optionnel, un drapeau ;base64 optionnel, une virgule, et les données. Les médias binaires comme les images utilisent le drapeau, donc le payload est l'alphabet standard avec padding, et le décodeur ordinaire le gère après une petite découpe :
use MIME::Base64 qw(decode_base64);
my $uri = "data:image/png;base64,iVBORw0KGgo...";
$uri =~ s/^data:[^,]+,// or die "not a data URI";
my $raw = decode_base64($uri);
print unpack("H8", $raw), "\n"; # 89504e47 : les octets magiques du PNG
Vérifier les octets magiques, c'est le coup à jouer. Si ces huit premiers caractères hexadécimaux ne sont pas 89504e47, l'image n'est pas un PNG quoi que le type média prétende, et un décodeur qui ne se plaint jamais rend précisément ce genre de mensonge discret possible.
Cousins et fossiles : uuencode et les autres alphabets
Avant que le Base64 ne gagne, la façon classique de UNIX d'envoyer un binaire par courrier était uuencode, et vous le croiserez encore dans de vieilles listes de diffusion et de vieux outils. La bonne nouvelle : Perl a un décodeur intégré pour ça, aucun module requis, grâce au modèle u dans pack et unpack :
my $uu = pack("u", "Hello, World!");
print $uu, "\n"; # -2&5L;&\L(%=O<FQD(0`` plus un retour à la ligne
my $back = unpack("u", $uu);
print $back, "\n"; # Hello, World!
Les deux appels sont des inverses exacts, c'est toute l'histoire dont vous avez besoin, et la commande classique uuencode de la boîte à outils UNIX enveloppe simplement les lignes nues dans un en-tête begin et un pied end, donc le payload que vous décodez est la partie entre les deux.
Le Base64 a aussi des cousins de dialecte, et savoir quel décodeur mange quoi vous épargne une session de débogage :
| Dialecte | Enveloppe | Où vous le croisez | Ce que fait decode_base64 |
|---|---|---|---|
| MIME (RFC 2045) | 76 caractères | corps de courrier | le décode tel quel : retours à la ligne et CRLF sont ignorés |
| PEM (RFC 1421) | 64 caractères | certificats et clés | le décode tel quel |
| PKIX (RFC 7468) | 64 caractères | structures textuelles X.509 | le décode tel quel |
| armure OpenPGP (RFC 9580) | 76 caractères plus une ligne CRC24 | clés et signatures PGP | le décode tel quel, la ligne de somme de contrôle est simplement ignorée |
| IMAP (RFC 3501) | aucun | noms de boîtes aux lettres | pas cet alphabet : le slash devient une virgule, traduisez d'abord les lettres |
À retenir : pour chaque variante de l'alphabet standard qui ne fait qu'envelopper différemment, un seul décodeur indulgent les couvre toutes. C'est seulement quand l'alphabet lui-même change qu'il faut traduire les caractères d'abord.
Configuration, bases de données et variables d'environnement
Les plateformes de conteneurs, les consoles cloud, et un nombre surprenant de fichiers de configuration stockent des identifiants et de petits documents sous forme de chaînes Base64 opaques, parce qu'un blob de lettres et de chiffres a l'air moins dangereux que le mot de passe qu'il est. Le décodage, c'est toujours les mêmes deux étapes : decode_base64 plus une décision de charset :
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $secret = decode("UTF-8", decode_base64($config->{api_key}));
La raison pour laquelle le format est si populaire à cet endroit est exactement celle dont la RFC 4648 met en garde : les humains finissent par ne plus remarquer que les données sont lisibles. Donc traitez la sortie décodée comme confidentielle dès le moment où elle est rendue, et gardez à la fois le blob et son résultat hors des fichiers de log, des alertes, et des dévers de débogage.
La même forme apparaît dans les bases de données, où les données binaires voyagent souvent dans une colonne TEXT en Base64 parce que la colonne ne peut pas promettre de laisser passer des octets arbitraires intacts :
use MIME::Base64 qw(decode_base64);
my $icon = decode_base64($row->{icon_data});
open my $fh, ">:raw", "icon.png" or die $!;
print {$fh} $icon;
close $fh;
Le courrier : parties MIME et pièces jointes
Le courrier électronique, c'est là que le Base64 a pris son nom, et l'indulgence du module est conçue exactement pour ce trafic. Une partie MIME avec Content-Transfer-Encoding: base64 arrive en lignes de 76 caractères de texte terminé par CRLF, et le décodeur avale l'enveloppe entière telle quelle, retours à la ligne compris :
use MIME::Base64 qw(decode_base64);
my $part_body = "SGVsbG8sIHF1ZXJ5IQpUaGlzIE1JTUUgcGFydCB0cmF2ZWxsZWQgYXMgYmFzZTY0LCB3cmFwcGVk";
$part_body .= "\r\nIGF0IDc2IGNoYXJhY3RlcnMsIENSTEYgYmV0d2VlbiBsaW5lcy4=";
my $text = decode_base64($part_body);
print $text; # le corps du message original, sur deux lignes
Si vous construisez ou parsez du courrier avec un framework, vous ne faites rien de tout ça à la main : MIME::Lite encode en base64 une pièce jointe pour vous quand vous passez Encoding => "base64" à attach, et Email::MIME fait de même automatiquement. La version faite main ci-dessus est pour le courrier qui arrive en texte brut dans un log, un ticket, ou un message transféré, ce qui, en pratique, en fait une bonne partie.
Les pièges, collectés et classés
Le module est assez petit pour être mémorisé, donc voici toute la liste des pièges en un seul endroit, classée à peu près par fréquence de morsure :
| Piège | Ce qui se passe | Correction |
|---|---|---|
| Nourrir le décodeur standard d'un segment base64url | les - et _ sont jetés comme du bruit, et le reste se décode en octets faussés, silencieusement |
utilisez decode_base64url, ou traduisez l'alphabet et restaurez le padding d'abord |
| Se fier au silence sur une entrée corrompue | caractères étrangers, troncature, et mauvais alphabet se décodent tous sans le moindre avertissement | passez la vérification stricte d'abord, et vérifiez avec un hash quand l'original est disponible |
| Caractères non ASCII dans le blob | une lettre accentuée égarée ou une espace Unicode collée est ignorée silencieusement, rétrécissant le résultat sans un mot | la même vérification stricte rejette tout ce qui est hors de l'alphabet 7 bits |
| Traiter le résultat comme du texte | les octets ne portent pas de drapeau UTF-8, donc length() compte des octets et les fonctions de chaîne ont le mauvais tableau |
enchaînez decode("UTF-8", $raw) ou votre charset choisi avant tout traitement de texte |
| Double encodage à la sortie | faire passer des octets déjà UTF-8 dans encode("UTF-8", ...) transforme Hëllo en Hëllo |
encodez des caractères, jamais des octets bruts, et vérifiez le drapeau avec utf8::is_utf8() en cas de doute |
| Décoder un fichier ligne par ligne avec des enveloppes irrégulières | les lignes qui ne finissent pas sur des frontières de quatre caractères produisent du padding au milieu de la sortie | avalez avec -0777, ou gardez des points d'enveloppe de quatre caractères |
| Retourner une correspondance d'expression régulière échouée dans un contexte numérique | une sub qui se termine par return $x =~ /.../ et la passe à printf lève un avertissement Missing argument in printf trompeur |
forcez la correspondance : return $x =~ /.../ ? 1 : 0 |
| Vieux code qui attend l'ancien avertissement | les scripts d'avant 3.11 qui comptaient sur le marmonnement Premature end of base64 data sous -w ne voient plus rien |
ajoutez votre propre vérification stricte ; l'avertissement est parti pour de bon |
| Penser que décoder c'est vérifier | le décodeur accepte presque n'importe quoi et n'en dit rien | une somme de contrôle qui correspond ou une signature vérifiée est la seule preuve qui compte |
| Journaliser ce que vous décodez | le format ne cache rien, et le fichier de log est exactement là que la personne suivante le trouvera | gardez les secrets décodés hors des logs, des alertes, et des dévers de débogage |
Les bonnes habitudes
Les habitudes qui assurent que le Base64 ne prend jamais le dessus sur vos scripts :
- Attendez des octets, toujours. Écrivez du code qui sait que
decode_base64renvoie des octets bruts, et enchaînez ledecodedu charset explicitement au lieu d'espérer que le terminal fera le bon choix. - Nommez votre charset. Partez sur
UTF-8par défaut et ne changez que si les données disent le contraire. L'échec strict dedecode("UTF-8", ..., Encode::FB_CROAK)est une qualité : il vous dit que les octets ne sont pas ceux que vous supposiez. - Alignez l'alphabet sur la source.
decode_base64urlpour les URL, les tokens et les IDs ;decode_base64pour tout le reste. Les deux alphabets ne sont pas interchangeables, et le décodeur ne vous dira pas quand vous vous trompez. - Validez avant de décoder. Il n'y a pas de drapeau de mode strict dans ce module, donc une petite vérification est le vigile.
- Avalez les fichiers par défaut.
-0777oulocal $/ = undefsupprime une classe entière de bogues de points d'enveloppe, et le coût mémoire n'est pas un problème pour les fichiers que vous décodez vraiment. - Utilisez
:rawsur chaque poignée de fichier. Binaire en entrée, binaire en sortie. Les calques texte sont pour les humains, pas pour les octets. - Vérifiez avec un hash. Quand l'original est disponible, une somme de contrôle qui correspond est la seule preuve d'un décodage exact à l'octet près.
- Ne journalisez jamais ce que vous décodez. Le format ne cache rien.
Une brève histoire, racontée par le changelog
Le format est ancien, et la relation de Perl avec lui est plus ancienne qu'elle n'en a l'air. Quelques dates vérifiées, dans l'ordre :
- Le code C est antérieur à Perl 5. Le décodeur rapide à l'intérieur du module descend de code de metamail, le programme de courrier de Bellcore, sous copyright en 1991, trois ans avant la première sortie de Perl 5. Quand vous appelez
decode_base64aujourd'hui, un bout des années 90 fait le travail. - Né dans les outils web. Le module a commencé comme
LWP::Base64dans libwww-perl au milieu des années 90, écrit par Martijn Koster et Joerg Reichelt, et il a fait son entrée en distribution CPAN à part,MIME::Base64, en avril 1997, version 2.00, avec une entrée de changelog qui dit based on libwww-perl-5.08. - L'ère des avertissements. Depuis la 2.03 en 1997, une entrée tronquée produisait un avertissement Premature end of base64 data sous
-wau lieu d'un croak, et la 2.11 en 1999 a corrigé les builds qui avertissaient sur des données sans problème. C'était une décennie plus nerveuse pour les décodeurs. - Dans le core depuis 2002. Perl 5.8 a tiré le module dans la distribution core, et la synchronisation 2.13 avec le core ce même mois de décembre a apporté le support EBCDIC, c'est pourquoi l'encodeur et le décodeur fonctionnent encore sur mainframes.
- Le dialecte sûr pour les URL est arrivé dans le core de Perl en 2010. La version 3.11 a ajouté
decode_base64urlet sa sœur, quatre ans après que le module autonomeMIME::Base64::URLSafeait posé ses valises sur CPAN en 2006, la même année où la RFC 4648 a codifié le dialecte. - Le grand silence. Cette même sortie 3.11 a retiré jusqu'à l'ancien avertissement de troncature, au cas où l'entrée suspecte serait délibérée - et chaque sortie depuis, y compris la ligne 3.16 actuelle de 2020, a gardé le décodeur poli et silencieux.
Faits insolites, version Perl
Pour boucler la visite, les trucs qui rendent cette histoire bonne :
- Décodez le nom du format lui-même.
decode_base64("YmFzZTY0")renvoiebase64. C'est vrai depuis 1997 et le sera toujours. - Le décodeur est un fantôme poli. Dans son histoire 3.x, il n'a jamais levé d'exception sur une mauvaise entrée. Corrompu, tronqué, mauvais alphabet : il décode tout et ne se plaint de rien, un comportement que le changelog a scellé exprès en 2010.
- L'enveloppe MIME est un multiple de quatre exprès. La limite de 76 caractères, ce sont dix-neuf groupes de trois octets, 57 octets au total, multipliés par quatre caractères, c'est pourquoi un décodage ligne par ligne est sûr sur n'importe quel corps MIME correctement enveloppé et risqué sur tout le reste.
- uuencode n'imprime jamais de lettre minuscule. Son alphabet s'arrête au tiret bas, c'est pourquoi les vieux fichiers uuencodés ont l'air tapés par une machine toutes majuscules, et pourquoi Perl porte encore un décodeur intégré pour un format plus vieux que l'internet.
- Perl a un temps livré sa propre commande decode-base64. Les sorties de la 2.14 en 2003 à la 3.05 en 2004 incluaient
encode-base64,decode-base64, et leurs jumelles quoted-printable en scripts ; la 3.06 en 2005 les a déplacées dans la distribution séparée MIME-Base64-Scripts. Si vous trouvez une vieille installation avec cette commande sur le PATH, vous savez maintenant d'où elle vient. - Les ID de vidéo YouTube sont du base64url déguisé. L'ID de onze caractères dans votre barre d'adresse est un nombre de 64 bits dans l'alphabet sûr pour les URL avec le padding retiré, donc chaque vidéo que vous avez jamais regardée a une chaîne Base64 dans son URL, et
decode_base64urlen lit une. - L'indulgence est une norme, pas un bogue. La règle MIME d'être libéral dans ce qu'on accepte est la raison pour laquelle ce décodeur survit à trois décennies de données brouillon, et la raison pour laquelle la RFC 4648 met en garde : la même indulgence peut se muer en canal couvert si vous faites confiance à des entrées non fiables.
Alors la prochaine fois qu'une chaîne de lettres, de chiffres, de plus et de slash atterrit dans votre terminal, vous connaissez toute l'histoire. Un appel de fonction fait le travail, le décodeur est un fantôme poli qui ne vous refusera jamais rien, le base64url a son propre décodeur, le charset est une décision que vous prenez exprès, les fichiers entrent bruts et sortent bruts, et un hash est la seule preuve qui compte. Et si un jour vous devez faire le trajet dans l'autre sens, envelopper vos propres données brutes dans une enveloppe de texte et les envoyer au monde, l'article lié sur l'encodage Base64 en Perl, ci-dessous, couvre ce rituel avec la même profondeur.
Dernière mise à jour : 2026-09-08
Article associé : Encodage Base64 en Perl : un guide complet