Décodage Base64 en C++ (Cpp) : un guide complet
Vous avez la chaîne. Un long ruban de lettres, de chiffres, un + ou un / de-ci de-là, peut-être un = ou deux à la fin, et quelque part dans votre ticket, votre contrat ou votre colonne de base de données, la promesse que c'est du Base64. Maintenant il vous faut les octets d'origine, en C++, et il vous faut les bons. La page d'accueil de ce site passe le format en détail, alors voici seulement la version courte : quatre caractères de l'alphabet portent trois octets, une queue d'un ou deux = marque où les vraies données s'arrêtaient, et la forme encodée fait environ 33 pour cent de plus que l'original. Le décodage est la direction qui rétrécit, donc un décodeur n'a jamais besoin de plus de mémoire que le payload qu'il détient déjà. C'est une propriété vraiment agréable, et l'un des plaisirs discrets de travailler dans ce sens.
La vraie nouvelle, c'est que C++ lui-même ne vous décodera pas le moindre caractère. La bibliothèque standard a eu trente ans pour faire pousser une fonction base64 et elle a passé tout ce temps sur d'autres choses, alors chaque programme C++ apporte son propre décodeur, choisi parmi trois personnalités très différentes, plus l'option d'en écrire une quarantaine de lignes vous-même. L'un est une bête de somme qui porte internet depuis les années 1990, l'autre est un type silencieux qui s'arrête en pleine phrase et n'en dit jamais un mot, et le dernier est un maniaque qui lève une exception devant un seul espace égaré. Quand vous savez ce que chacun pardonne, ce qu'il refuse et ce qu'il fait discrètement dans votre dos, le décodage cesse d'être une source de bugs mystérieux. Ouvrons quelques paquets.
La boîte à outils : quatre décodeurs, quatre tempéraments
Voici la situation d'un coup d'œil. Tous les quatre gèrent l'alphabet standard ; les différences sont aux bords, et les bords sont là où vivent les bugs.
| Décodeur | D'où il vient | Modèle d'erreurs | Particularité à retenir |
|---|---|---|---|
| OpenSSL EVP | <openssl/evp.h>, lien -lcrypto |
Renvoie -1 sur une entrée corrompue |
La version one-shot remplit la queue de zéros |
| Boost.Beast | <boost/beast/core/detail/base64.hpp>, header-only |
Aucun : il s'arrête, tout simplement | Aucun canal d'erreur sous quelque forme que ce soit |
| Les itérateurs Boost.Serialization | <boost/archive/iterators/binary_from_base64.hpp>, header-only |
Lève dataflow_exception |
Traite = comme une vraie valeur nulle |
| Vos quarante lignes à vous | Nulle part : c'est à vous | Votre choix, jusqu'à la position de l'octet | Vous possédez chaque cas limite pour toujours |
L'installation tient dans un nom de paquet par distribution. Pour OpenSSL : libssl-dev sur Debian et Ubuntu, openssl-devel sur Fedora et RHEL, openssl sur Arch, et brew install openssl sur macOS. Pour Boost, dont la version actuelle est la 1.92.0 d'août 2026, dans un projet qui construit des bibliothèques depuis 1998 : libboost-dev ou boost-devel. Les deux décodeurs Boost ci-dessous sont header-only, il n'y a donc rien à lier du tout. Si votre projet est basé sur CMake, tout le setup tient en trois lignes :
find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)
Une note de version avant le code, parce qu'elle change ce que votre décodeur renvoie. OpenSSL 3.5, publiée en avril 2025 comme une ligne de support à long terme, a corrigé un vrai bug du décodeur en streaming (plus de détails dans une minute), et la plus récente version 4.0 de fonctionnalités, d'avril 2026, a hérité de la correction. Si votre build est calé sur une vieille 3.0 ou 3.3, lisez le paragraphe de version de la section OpenSSL ci-dessous avant de faire confiance à une longueur de queue.
OpenSSL : le décodeur que vous liez probablement déjà
Si votre programme C++ touche au TLS, au hachage ou aux certificats, OpenSSL est déjà dans le binaire, et ses routines base64 EVP sont les décodeurs les plus rodés du métier. La fonction one-shot tient en un seul appel :
int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);
Donnez-lui un buffer de caractères base64 et sa longueur, et elle écrit les octets décodés dans t. Elle retire les espaces blancs au début, retire les espaces blancs, retours à la ligne et retours chariot à la fin, puis elle applique des règles sans compromis : aucun espace blanc interne, et la longueur après nettoyage doit être un multiple de 4. Tous les quatre caractères d'entrée produisent exactement trois octets de sortie, et voici ce qui surprend : les caractères de padding sont décodés en six bits à zéro, et la page de manuel note calmement que c'est à l'appelant de tenir compte du padding final. Autrement dit, la fonction fait l'arithmétique à votre place, puis ajoute discrètement jusqu'à deux octets bonus de zéros à la fin. Le wrapper C++ idiomatique cache la gestion des buffers derrière un std::string :
#include <cstddef>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>
std::string openssl_decode(const std::string &b64) {
std::vector<unsigned char> out(b64.size() * 3 / 4 + 4);
int n = EVP_DecodeBlock(out.data(),
reinterpret_cast<const unsigned char *>(b64.data()),
static_cast<int>(b64.size()));
if (n < 0) return {};
size_t pads = 0;
for (size_t i = b64.size(); i > 0 && b64[i - 1] == '='; --i) ++pads;
return std::string(reinterpret_cast<const char *>(out.data()),
static_cast<size_t>(n) - pads);
}
int main() {
std::string one = openssl_decode("TQ==");
std::printf("%zu bytes: %02x\n", one.size(),
static_cast<unsigned char>(one[0]));
std::string word = openssl_decode("TWFuZQ==");
std::printf("%zu bytes: %s\n", word.size(), word.c_str());
}
Exécutez-le et le remplissage de zéros apparaît exactement là où la documentation l'avait promis : décoder la chaîne de quatre caractères TQ== donne trois octets, la lettre M plus deux zéros, avant que le wrapper ne les retire. Donnez-lui TWFuZQ== et vous obtenez les quatre octets impeccables de « Mane ». Donnez-lui un caractère hors alphabet et vous obtenez une chaîne vide, parce que la fonction a répondu -1. Remarquez ce que std::string fait discrètement dans ce wrapper : il suit sa propre longueur et contient gaiement des octets nuls, si bien qu'un JPEG décodé peut vivre dans votre type texte et y être comparé, haché et passé partout. En C vous seriez reconnaissant d'avoir une variable de longueur ; ici la chaîne marche, point.
Pour les données qui arrivent par morceaux, OpenSSL vous remet un contexte dans lequel vous enfoncez des blocs et d'où vous tirez les résultats, et les trois fonctions ont un vocabulaire de valeurs de retour compact :
| Appel | Renvoie | Ce que ça veut dire |
|---|---|---|
EVP_DecodeUpdate |
-1 |
Caractère invalide, ou un caractère de padding au milieu des données |
EVP_DecodeUpdate |
1 |
Plus d'entrée est attendue |
EVP_DecodeUpdate |
0 |
Fin des données : le dernier groupe portait du padding, ou le marqueur doux de fin d'entrée est apparu |
EVP_DecodeFinal |
1 / -1 |
Le flux s'est terminé proprement / les caractères restants ne formaient pas un multiple de 4 |
Deux comportements font du décodeur en streaming le plus indulgent de la boîte à outils. Il saute les espaces, tabulations, retours chariot et retours à la ligne n'importe où dans le flux, si bien qu'un bloc d'e-mail MIME aux lignes CRLF de 76 caractères passe exactement comme une chaîne d'une seule ligne, et il rapporte le vrai nombre d'octets : le même TQ== qui a trompé la fonction one-shot vous donne ici exactement un octet, sans aucune arithmétique. Il mâche l'entrée par blocs de 64 caractères base64 au maximum, en travaillant depuis un buffer interne de 80 octets, et met en buffer tout ce qui ne forme pas un groupe de quatre, c'est pourquoi vous pouvez l'alimenter par des morceaux de taille arbitraire. Le wrapper :
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>
std::string openssl_decode_stream(const std::string &b64) {
EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
EVP_DecodeInit(ctx);
std::string out;
std::vector<unsigned char> chunk(1024);
int outl = 0;
for (size_t pos = 0; pos < b64.size(); pos += chunk.size()) {
size_t take = std::min(chunk.size(), b64.size() - pos);
int ret = EVP_DecodeUpdate(ctx, chunk.data(), &outl,
reinterpret_cast<const unsigned char *>(b64.data()) + pos,
static_cast<int>(take));
if (ret < 0) {
EVP_ENCODE_CTX_free(ctx);
return {};
}
out.append(reinterpret_cast<const char *>(chunk.data()), outl);
if (ret == 0) break;
}
unsigned char tail[3];
int tail_l = 0;
int fin = EVP_DecodeFinal(ctx, tail, &tail_l);
EVP_ENCODE_CTX_free(ctx);
if (fin != 1) return {};
out.append(reinterpret_cast<const char *>(tail), tail_l);
return out;
}
Et maintenant la note de version du tableau de la boîte à outils, parce que c'est exactement le genre de chose qui rouvre un ticket « résolu » : sur toutes les versions d'OpenSSL antérieures à 3.5, le chemin en streaming avait la même habitude de remplissage de zéros que le décodeur par blocs. Il a été signalé en février 2025 en tant qu'issue 26677 ; le commit de correction est arrivé sur master le 27 février 2025, et la pull request associée a été fermée le même jour. La page de manuel officielle l'enregistre désormais dans sa section historique : à partir d'OpenSSL 3.5, EVP_DecodeUpdate produit le nombre d'octets que la documentation revendiquait toujours et ne décode plus le padding en bits à zéro. Si votre base de code est calée sur une vieille version d'OpenSSL et que vos longueurs de queue semblent décalées d'un ou deux octets, c'est la première chose à vérifier. Et il y a une excentricité héritée de l'ère PEM : le tiret - n'est pas un caractère de l'alphabet du tout - c'est un marqueur doux de fin d'entrée. Si votre flux en contient un après un multiple de 4 caractères valides, le décodeur renvoie 0 et vous demande d'arrêter, c'est pourquoi une chaîne base64url qui a vraiment besoin de ses caractères - ne se décodera pas ainsi d'emblée - transcodez d'abord, dans la section ci-dessous, et le voyageur dans le temps disparaît.
Boost.Beast : le décodeur rapide qui ne se plaint jamais
Si Boost est déjà dans le projet, sa bibliothèque HTTP livre un codec base64 à l'adresse improbable boost/beast/core/detail/base64.hpp. L'espace de noms detail:: est la façon qu'a Boost de dire « c'est notre affaire interne », et les mainteneurs ont refusé de le promouvoir en API publique. Tout le monde l'utilise quand même, parce qu'il est petit, rapide, et header-only : définissez BOOST_BEAST_HEADER_ONLY avant l'include et il n'y a rien à lier. C'est aussi, incidemment, le codec que le propre handshake WebSocket de Boost.Beast utilise pour le calcul de Sec-WebSocket-Accept, si bien qu'il mâche du trafic réel depuis des années.
La personnalité de la fonction de décodage est la surprise. Elle prend votre buffer de sortie, l'entrée, et sa longueur, et renvoie un couple : le nombre d'octets écrits et le nombre de caractères lus. Elle s'arrête au premier =, et au premier caractère invalide - et dans les deux cas elle le fait sans vous prévenir. Il n'y a pas de code d'erreur, pas d'exception, pas de drapeau de statut. Un payload corrompu, un payload coupé en lignes, et une queue tronquée produisent tous un résultat partiel réussi :
| Entrée | Octets décodés | Caractères lus | Pourquoi il s'est arrêté |
|---|---|---|---|
"TWFuZQ==" |
4 octets "Mane" |
6 | Arrêté au premier = |
"TWF!ZQ==" |
2 octets "Ma" |
3 | Arrêté sur ! |
"TWFu\nZQ==" |
3 octets "Man" |
4 | Arrêté sur \n |
"TWFuZQ" |
4 octets "Mane" |
6 | La queue était tronquée |
"TQ==" |
1 octet "M" |
2 | Le padding est géré sans souci |
Relisez ce tableau une seconde fois, parce que c'est tout le modèle de menace d'un décodeur qui ne se plaint jamais : il a fait de son mieux, il s'est arrêté là où il s'est arrêté, et c'est à vous de le remarquer. Le contrôle a un pli : pour une entrée avec padding, le décompte des « caractères lus » s'arrête au premier =, donc vous rajoutez les pads avant de comparer, et vous exigez aussi que le total soit un multiple de quatre, parce que c'est la seule forme qu'a un vrai payload :
#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <string>
namespace b64 = boost::beast::detail::base64;
std::string beast_decode(const std::string &in) {
std::string out;
out.resize(in.size() / 4 * 3 + 3); /* marge pour les longueurs impaires */
auto result = b64::decode(out.data(), in.data(), in.size());
out.resize(result.first);
size_t pads = 0;
for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
if (result.second + pads != in.size() || in.size() % 4 != 0)
return {}; /* il s'est arrêté trop tôt, ou la queue était impossible */
return out;
}
Deux détails à ranger. D'abord, l'outil decoded_size(n) vers lequel l'en-tête vous oriente n'est une borne supérieure valide que si n est un multiple de 4 - le propre commentaire de la fonction le dit - c'est pourquoi le wrapper ci-dessus ajoute quelques octets de marge au lieu de lui faire confiance pour des longueurs arbitraires. Ensuite, la provenance : les fichiers source portent un copyright 2016-2019 de Vinnie Falco, avec un pied de page qui attribue des portions à un extrait de 2004-2008 de Rene Nyffenegger. Cet extrait est le duo base64 copié-collé à travers l'internet anglophone depuis deux décennies, et il est désormais livré dans Boost, dans votre binaire, en train de faire de l'authentification HTTP Basic pour tout le web.
Boost.Serialization : le décodeur qui lève l'exception au premier espace égaré
La bibliothèque de sérialisation de Boost porte le base64 le plus ancien de l'écosystème C++ : une série d'adapteurs d'itérateurs composables de 2002, écrits par Robert Ramey, qui traitent le « soyez généreux dans ce que vous acceptez » comme une insulte personnelle. La direction de décodage se trouve dans binary_from_base64.hpp (oui, le nom est du point de vue de la sortie) et s'associe à un transformateur de largeur qui re-emballe des valeurs de six bits en octets de huit bits :
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <cstddef>
#include <string>
namespace it = boost::archive::iterators;
std::string boost_decode(const std::string &in) {
using dec =
it::transform_width<it::binary_from_base64<const char *>, 8, 6>;
std::string out(dec(in.data()), dec(in.data() + in.size()));
size_t pads = 0;
for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
out.resize(out.size() - pads);
return out;
}
L'itérateur interne convertit chaque caractère base64 en sa valeur de six bits, et l'itérateur externe regroupe ces valeurs en octets. Sa rigueur est totale : tout caractère hors alphabet - y compris un seul espace - fait jeter à l'itérateur boost::archive::iterators::dataflow_exception avec le message « attempt to decode a value not in base64 char set ». C'est le comportement « refuser sauf instruction contraire » que les RFC ont rendu explicite plus tard - implémenté en 2002, un an tout entier avant que la RFC 3548 ne codifie la même règle. La conséquence pratique est qu'une entrée enveloppée en MIME doit se voir retirer ses sauts de ligne avant de toucher cet itérateur. La seconde particularité est plus subtile : dans la table de correspondance, le caractère de padding = n'est pas sauté - il est rendu sous forme de valeur zéro. Décoder TWFuZQ== produit donc six octets - 4d 61 6e 65 00 00 - parce que les deux caractères de padding ont fourni de vraies données (nulles), et la ligne resize(size - pads) de l'extrait porte la charge, elle n'est pas décorative. Décodez TQ== et vous obtenez trois octets qui se réduisent à la lettre M seule, exactement là où vous voulez atterrir.
Quarante lignes qui vous appartiennent
Le Base64 est assez petit pour qu'un décodeur correct mérite d'être possédé, et en C++ le gain est meilleur que dans n'importe quelle autre langue : std::string rend la gestion des buffers agréable, et un décodeur fait main peut faire ce qu'aucune des versions de bibliothèque ci-dessus ne fait, à savoir pointer l'octet exact qui a fait mal. Cette version suit la lecture stricte de la RFC 4648 - nettoyer les extrémités, refuser les espaces blancs internes, refuser le padding au milieu, appliquer les règles de longueur, et même vérifier les bits de padding que la RFC dit qu'un encodeur conforme doit avoir mis à zéro :
#include <cstddef>
#include <cstring>
#include <string>
std::string strict_decode(const std::string &in, size_t *error_pos = nullptr) {
static const char *table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
auto fail = [&](size_t pos) {
if (error_pos) *error_pos = pos;
return std::string();
};
size_t start = 0;
size_t end = in.size();
while (start < end && (in[start] == ' ' || in[start] == '\t' ||
in[start] == '\r' || in[start] == '\n'))
++start;
while (end > start && (in[end - 1] == ' ' || in[end - 1] == '\t' ||
in[end - 1] == '\r' || in[end - 1] == '\n'))
--end;
size_t pads = 0;
while (end > start && in[end - 1] == '=') {
--end;
++pads;
}
size_t body = end - start;
if (pads > 2 || (pads == 1 && body % 4 != 3) ||
(pads == 2 && body % 4 != 2) || (pads == 0 && body % 4 == 1))
return fail(in.size());
if (body >= 2 && body % 4 != 0) {
int leftover = static_cast<int>((body % 4) * 6 % 8);
int last = static_cast<int>(std::strchr(table, in[end - 1]) - table);
if ((last & ((1 << leftover) - 1)) != 0)
return fail(end - 1); /* bits de padding non canoniques */
}
int value = 0;
int bits = -8;
std::string out;
out.reserve(body / 4 * 3);
for (size_t i = start; i < end; ++i) {
const char *p = std::strchr(table, in[i]);
if (!p)
return fail(i);
value = (value << 6) + static_cast<int>(p - table);
bits += 6;
if (bits >= 0) {
out.push_back(static_cast<char>((value >> bits) & 0xFF));
bits -= 8;
}
}
return out;
}
Parcourons ce qu'il impose. Les espaces blancs au début et à la fin sont nettoyés, parce qu'un payload copié depuis un en-tête d'e-mail arrive très probablement avec des espaces blancs. Les espaces blancs internes sont refusés, parce que la RFC 4648 dit qu'un décodeur DOIT refuser les caractères non alphabétiques sauf si la spécification environnante dit le contraire, et à une frontière de sécurité vous voulez la lecture stricte. Un caractère de padding au milieu des données est refusé, de même que toute longueur qui ne peut pas correspondre à un vrai payload : un groupe manquant d'un caractère est impossible, et un pad unique n'est légal qu'après trois caractères du corps. Le contrôle de canonicité à la fin est celui que la plupart des implémentations sautent : si le dernier groupe avait un ou deux caractères de padding, les bas bits inutilisés du dernier caractère de l'alphabet doivent être à zéro, sinon les mêmes octets pourraient s'écrire sous forme de deux chaînes visiblement différentes. C'est cette malléabilité qui fait exister ce contrôle, et il coûte quatre lignes. Enfin, le décodeur accepte une entrée sans padding, ce qui est exactement ce que sont les segments de JWT. Et en cas d'échec, il vous remet la position : donnez-lui TWF!ZQ== et l'erreur est à l'indice 3, sur le point d'exclamation, ce qui fait toute la différence entre un rapport de bug et une correction.
Base64url : l'alphabet que parlent les jetons
L'alphabet standard a deux caractères qui ne survivent pas à une URL : + signifie espace dans une chaîne de requête, et / signifie répertoire dans un chemin. La section 5 de la RFC 4648 corrige cela avec deux échanges de caractères - + devient - et / devient _ - et est nette sur le résultat : cet encodage « ne devrait pas être considéré comme identique à l'encodage base64 ». C'est l'alphabet des JWT, des défis de code OAuth PKCE, des identifiants de vidéo YouTube, et de la plupart des jetons d'API, et il supprime couramment le padding = aussi, parce que dans un jeton la longueur est connue implicitement et le padding ne serait qu'un échappement en pourcentage en attente de se produire. Aucun des décodeurs C++ ci-dessus ne le parle nativement - OpenSSL traite même un - comme ce marqueur doux de fin d'entrée - si bien que la solution est un petit transcodage avant de décoder. C'est si court qu'il est facile de le garder en tête :
#include <string>
std::string url_to_standard(std::string in) {
for (char &c : in) {
if (c == '-') c = '+';
else if (c == '_') c = '/';
}
switch (in.size() % 4) {
case 2: in += "=="; break; /* restaurer le padding retiré */
case 3: in += '='; break;
default: break;
}
return in;
}
Appliquez-le à un vrai JWT et les deux premiers segments sont du JSON pur qui n'attend que d'exister. Un jeton d'exemple classique se décode en un en-tête de {"alg":"HS256","typ":"JWT"} et un jeu de revendications contenant un sujet, un nom, et un horodatage d'émission. Le troisième segment se décode de la même façon et vous donne les octets bruts de la signature - pas du texte, et pas la preuve de quoi que ce soit. Décoder un jeton vous dit ce qu'il revendique ; vérifier la signature vous dit s'il faut y croire, et c'est un travail de cryptographie qu'aucune bibliothèque base64 ne fera pour vous. Sous Windows la situation est à sens unique dans le même sens : CryptBinaryToStringA a un drapeau CRYPT_STRING_BASE64URI pour le côté encodage, mais la direction de décodage n'a aucun drapeau URL-safe, si bien que le transcodage ci-dessus mérite sa place dans votre mémoire musculaire sur toutes les plateformes.
Des octets de retour au texte
Demandez à un décodeur C++ « quel charset vient-il de décoder ? » et vous obtenez la réponse la plus honnête que la langue ait : aucune. Les décodeurs sont orientés octets, de bout en bout. Ils ne voient pas de texte ; ils voient des octets, et ils vous rendent exactement les octets qui avaient été emballés. Si l'original était en UTF-8, vous tenez désormais du UTF-8, et rien de plus n'est nécessaire. La belle astuce C++, c'est que std::string lui-même est un conteneur d'octets avec un membre de longueur, si bien que le mode d'échec classique du C - une fonction de chaîne qui s'arrête au premier NUL - s'évapore presque. Un fichier décodé, un certificat décodé, une image décodée : tous peuvent vivre dans un string, y être comparés avec ==, hachés, et passés par valeur, et les octets nuls à l'intérieur sont simplement des octets. Il ne faut juste pas convertir en chaîne C et mesurer ensuite avec strlen ; utilisez size().
Pour les encodages hérités qui rôdent encore dans les vieilles bases de données, les exports et les outils écrits à la main - ISO-8859-1, Windows-1252 et leurs parents - l'outil standard est le iconv POSIX, livré avec glibc. Décodez d'abord en octets, puis convertissez ces octets en UTF-8 avec le codec qui correspond à la source :
#include <iconv.h>
#include <cstddef>
#include <string>
#include <vector>
std::string to_utf8(const std::vector<unsigned char> &raw,
const char *source_charset) {
iconv_t cd = iconv_open("UTF-8", source_charset);
if (cd == (iconv_t)-1) return {};
char *inptr = reinterpret_cast<char *>(const_cast<unsigned char *>(raw.data()));
size_t inleft = raw.size();
std::vector<char> utf8buf(raw.size() * 4 + 8);
char *outptr = utf8buf.data();
size_t outleft = utf8buf.size();
if (iconv(cd, &inptr, &inleft, &outptr, &outleft) == (size_t)-1) {
iconv_close(cd);
return {};
}
iconv_close(cd);
return std::string(utf8buf.data(),
static_cast<size_t>(outptr - utf8buf.data()));
}
Le va-et-vient est sans perte dans les deux sens : empaquetez une chaîne en ISO-8859-1, mettez-la en base64, expédiez-la, décodez-la, convertissez-la, et vous obtenez exactement ce avec quoi vous aviez commencé, caractères accentués intacts. Et les données binaires n'ont tout simplement pas de charset - un PNG est un PNG que vous le vouliez ou non, ce qui est la réponse la plus libératrice de tout l'article.
Décoder des fichiers
Les petits fichiers sont une danse en quatre temps : ouvrir en binaire, lire dans un vector, décoder, réécrire le résultat en binaire. Mode binaire, à chaque fois, sur chaque plateforme - sous Windows, une lecture en mode texte traduirait les paires CRLF en retours à la ligne simples et modifierait discrètement vos données avant même que le décodeur ne les voie :
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
std::vector<unsigned char> read_file(const std::string &path) {
std::ifstream in(path, std::ios::binary);
return {std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>()};
}
Remarquez les accolades dans le code ci-dessus. Avec de simples parenthèses, une ligne de la forme std::vector<unsigned char> bytes(istreambuf_iterator<char>(file), istreambuf_iterator<char>()) est le fameux « most vexing parse » : le compilateur la lit comme la déclaration d'une fonction qui renvoie un vector, et il a tout à fait raison de le faire. La forme avec initialisation en accolades ci-dessus contourne la grammaire entièrement. Une fois les octets en main, passez-les dans n'importe quel décodeur de cet article et écrivez le résultat avec std::ofstream en std::ios::binary, en utilisant write(data.data(), data.size()) plutôt que l'opérateur de flux, pour que tout octet nul embarqué survive au voyage jusqu'au disque. Un fichier .b64 et son jumeau décodé diffèrent alors d'exactement l'impôt de 33 pour cent payé à l'aller, ce qui donne un moment de contrôle de somme bien agréable.
Grands fichiers : streamer dans les deux sens
Pour les fichiers trop gros pour tenir en mémoire, le décodeur en streaming de la section OpenSSL fait tout le travail : lire un bloc, le pousser dans le contexte, écrire ce qui en sort, recommencer. À chaque instant, seul un petit buffer vit en RAM, si bien qu'un fichier base64 de 10 Go se décode avec le même code qu'un de 10 Ko, et l'enveloppement de lignes façon MIME ne demande aucun prétraitement en cours de route, parce que le décodeur en streaming hausse les épaules devant les retours à la ligne :
#include <fstream>
#include <string>
#include <vector>
#include <openssl/evp.h>
bool decode_stream_to_file(const std::string &in_path,
const std::string &out_path) {
std::ifstream in(in_path, std::ios::binary);
std::ofstream out(out_path, std::ios::binary);
if (!in || !out) return false;
EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
EVP_DecodeInit(ctx);
std::string chunk(65536, '\0');
std::vector<unsigned char> decoded(49152);
bool ok = true;
for (;;) {
std::streamsize got = in.read(chunk.data(), chunk.size()).gcount();
if (got < 0) { ok = false; break; }
if (got == 0) break;
int outl = 0;
int ret = EVP_DecodeUpdate(ctx, decoded.data(), &outl,
reinterpret_cast<const unsigned char *>(chunk.data()),
static_cast<int>(got));
if (ret < 0) { ok = false; break; }
out.write(reinterpret_cast<const char *>(decoded.data()), outl);
if (ret == 0) break;
}
unsigned char tail[3];
int tail_l = 0;
if (ok && EVP_DecodeFinal(ctx, tail, &tail_l) != 1)
ok = false;
if (ok)
out.write(reinterpret_cast<const char *>(tail), tail_l);
EVP_ENCODE_CTX_free(ctx);
return ok;
}
Les tailles des buffers ne sont pas arbitraires : les paramètres de longueur des fonctions EVP sont des int, donc un appel unique est sûr jusqu'à 2 Go, et les chiffres ci-dessus gardent chaque bloc à 64 Ko d'entrée avec un buffer de sortie de 48 Ko, ce qui est exactement 3 sur 4. Ce plafond en int est toute la raison d'être du chemin en streaming, et il vaut mieux le connaître comme un fait établi que de le découvrir comme un bug de plateforme. Si l'entrée s'avère corrompue, la fonction renvoie false au premier bloc qui ne peut pas être décodé, et le fichier de sortie retient tout ce qui était valide avant - ce qui, selon votre pipeline, peut être exactement le résultat partiel que vous vouliez.
HTTP, API et les champs JSON qui cachent des octets
Le Base64 apparaît dans HTTP sous deux formes. La première est des données : une réponse JSON avec un champ "certificate" ou "avatar" plein de base64, un point d'endpoint de téléversement qui accepte les octets dans une colonne sûre pour le texte, un point d'endpoint de téléchargement qui vous remet un fichier .b64. Le schéma est toujours le même - analyser le JSON, extraire la chaîne, la décoder, traiter le résultat comme des octets - et le côté décodage de cet article est toute l'implémentation. La seconde forme est des identifiants : l'en-tête Authorization: Basic est le base64 de user:password, et c'est le seul cas d'usage base64 de la norme depuis trente ans. L'analyser tient en deux étapes, et la première est celle où les gens saisissent une chaîne C terminée par NUL au milieu de données voisines du binaire et se demandent pourquoi :
#include <optional>
#include <string>
/* strict_decode depuis la section "Quarante lignes qui vous appartiennent" */
std::optional<std::pair<std::string, std::string>> parse_basic_auth(
const std::string &b64) {
std::string raw = strict_decode(b64);
size_t colon = raw.find(':');
if (colon == std::string::npos)
return std::nullopt;
return std::make_pair(raw.substr(0, colon), raw.substr(colon + 1));
}
Donnez-lui la valeur de l'en-tête après le préfixe Basic et il vous remet l'utilisateur et le mot de passe sous forme de vraies chaînes avec suivi de longueur, ou rien du tout si le payload n'est pas une paire user:pass. La note de sécurité a sa place ici même si ce n'est pas un sujet C++ : l'authentification Basic est de l'obfuscation, pas de la protection. L'en-tête voyage en clair pour quiconque peut lire le réseau, donc il n'est acceptable que derrière TLS, et même là, c'est le choix pour les appels machine à machine, pas pour des gens.
JWT : lire ce qu'un jeton revendique
Un JSON Web Token est trois segments base64url collés avec des points : en-tête, revendications, signature. Les deux premiers sont des objets JSON ; le troisième est une signature cryptographique sur la chaîne header.claims, calculée avec l'algorithme nommé dans l'en-tête. C++ n'a pas de type JWT intégré, mais lire un jeton ne demande rien de plus que le transcodage de la section base64url et un décodeur, parce que la partie intéressante est la lecture :
#include <cstddef>
#include <cstdio>
#include <string>
/* url_to_standard et strict_decode depuis les sections précédentes */
int main() {
const std::string token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
"SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
size_t dot1 = token.find('.');
size_t dot2 = token.find('.', dot1 + 1);
std::string header = strict_decode(
url_to_standard(token.substr(0, dot1)));
std::string claims = strict_decode(
url_to_standard(token.substr(dot1 + 1, dot2 - dot1 - 1)));
std::printf("header: %s\n", header.c_str());
std::printf("claims: %s\n", claims.c_str());
}
L'en-tête revient sous forme de {"alg":"HS256","typ":"JWT"} et les revendications sous forme de {"sub":"1234567890","name":"John Doe","iat":1516239022} - le sujet, le nom, et un horodatage d'émission. C'est tout le côté lecture, et c'est vraiment utile : consigner ce qu'un jeton revendique, déboguer un 401 en regardant le champ d'expiration, ou décider quelles revendications croire, tout est à un décodage près. Ce que ce n'est pas, c'est de la vérification. Le segment de signature est aussi du base64url, et le décoder vous donne 32 ou 64 octets bruts qui ne prouvent rien à eux seuls ; la signature n'a de sens que lorsque vous recomputez le hachage de header.claims avec le secret partagé ou la clé publique et que vous comparez. Traitez un JWT décodé comme vous traitez une lettre : il dit ce qu'il dit, et vérifier le sceau est un travail séparé, cryptographique.
Les data URIs : des fichiers qui se sont collés dans une page
Une data URI est une URL dont le payload est là, dans l'adresse : data: suivi d'un type de média facultatif, d'un marqueur ;base64 facultatif, d'une virgule, puis des données elles-mêmes - tout le schéma de la RFC 2397. Les navigateurs s'en servent pour embarquer des images, des polices et de petits scripts directement dans le HTML et le CSS sans requête supplémentaire, et si vous voyez un jour une page qui continue de fonctionner avec le réseau désactivé, une data URI est une forte suspecte. Côté C++, le travail de décodage est de découper l'URI puis de passer le payload dans votre décodeur habituel, parce que quand le marqueur ;base64 est présent, le payload est du base64 standard tout simple - généralement avec padding, généralement sur une seule ligne :
#include <cstddef>
#include <string>
std::string data_uri_payload(const std::string &uri, bool *is_base64) {
const std::string prefix = "data:";
if (uri.rfind(prefix, 0) != 0)
return {};
size_t comma = uri.find(',');
if (comma == std::string::npos)
return {};
std::string meta = uri.substr(prefix.size(), comma - prefix.size());
*is_base64 = meta.size() >= 7 &&
meta.compare(meta.size() - 7, 7, ";base64") == 0;
return uri.substr(comma + 1);
}
Appelez-la avec data:image/png;base64,iVBORw0KGgo=... et elle vous remet le payload plus le drapeau qui vous dit quel chemin de décodage prendre. Deux pièges. D'abord, quand le marqueur est absent, le payload est du texte encodé en URL, pas du base64, si bien que le drapeau n'est pas une formalité - une URI qui a l'air du base64 mais qui a été générée comme du texte en échappement percent se décodera en n'importe quoi. Ensuite, certains générateurs enveloppent les longues data URIs avec des retours à la ligne, comme le fait le MIME ; votre décodeur strict les refusera, donc retirez les sauts de ligne avant de décoder si la source ne dépend pas de vous. Décoder le payload d'une URI data:image/png vous donne les octets PNG exacts, en-tête compris, ce qui est la satisfaction discrète de tout l'exercice.
Courriels, MIME et l'habitude des 76 caractères
Le courriel est la raison pour laquelle le base64 a appris à envelopper ses lignes. SMTP, dans sa forme originale, était conçu pour transporter du ASCII sur sept bits, si bien que tout ce qui était binaire devait être réécrit en texte imprimable avant de pouvoir voyager. Privacy-Enhanced Mail l'a fait en 1987 avec des lignes de 64 caractères, et le MIME, en standardisant l'encodage pour le courriel en 1993, a assoupli la limite à 76 caractères et ajouté la règle selon laquelle un décodeur conforme doit simplement ignorer les sauts de ligne. L'habitude a survécu : une pièce jointe de courriel est toujours du base64 aujourd'hui, enveloppée à 76, et l'arithmétique exacte donne 4/3 fois 78/76 - environ 137 pour cent de la taille d'origine, plus quelques centaines d'octets d'en-têtes. Votre décodeur C++ le rétrécit jusqu'à revenir à 100 pour cent, ce qui est le but de tout le format.
Le pli en C++, c'est que les décodeurs de cet article ne sont pas d'accord sur les sauts de ligne, et chacun a une raison. Le décodeur en streaming d'OpenSSL les saute n'importe où dans le flux, ce qui est exactement la règle du MIME. La fonction one-shot d'OpenSSL refuse tout espace blanc à l'intérieur du payload. Boost.Beast s'arrête au premier retour à la ligne sans le dire. Les itérateurs de Boost lèvent l'exception sur un seul espace. Alors quand un payload vient du courriel, votre première décision est quel décodeur utiliser, ou vous retirez les sauts de ligne vous-même - un passage erase-remove d'une ligne sur \r et \n - et vous laissez le décodeur de votre choix faire le vrai travail. Retirer à l'avance est le choix ennuyeux, fiable, et c'est celui qui garde votre choix de décodeur indépendant de l'histoire de votre payload.
Bases de données, fichiers de configuration et variables d'environnement
La troisième maison du base64 est la couche de stockage : une colonne d'une base de données héritée dont la documentation dit « base64 » et rien de plus, un blob de configuration dans un fichier JSON, un payload dans une variable d'environnement qu'un service a mis en base64 pour qu'il survive à un shell. Le schéma de décodage est le même que partout ailleurs - lire la chaîne, décoder, traiter comme des octets - mais l'entrée sans étiquette mérite un paragraphe spécial, parce que parfois vous ne savez vraiment pas quel alphabet a été utilisé. Vous ne pouvez pas le savoir, mais vous pouvez tester, parce que quatre caractères font l'essentiel du travail :
- Contient
+ou/- seul l'alphabet standard peut avoir raison. - Contient
-ou_- seul l'alphabet URL-safe peut avoir raison. - Aucun des deux, mais finit par
=- standard avec padding, ou une chaîne URL-safe avec padding dont le payload n'avait tout simplement pas besoin des deux caractères échangés. - Aucun des deux, pas de padding - ça peut être l'un ou l'autre ; la forme URL-safe brute est la plus courante sur le web, donc pour des données nées dans une URL ou un jeton, essayez celle-là d'abord.
Les chaînes qui n'utilisent aucun des quatre caractères distinctifs se décodent de façon identique sous les deux alphabets, donc pour elles l'ordre dans lequel vous les essayez est une question de savoir d'où viennent les données : les choses nées dans un courriel veulent l'alphabet standard, les choses nées dans une URL veulent celui de l'URL. Et n'oubliez pas d'essayer la lecture avec et sans padding de la même chaîne - un = manquant est la différence entre « refusé » et « résolu », c'est pourquoi le décodeur strict ci-dessus accepte les deux.
La ligne de commande a deux base64
Pour les travaux ponctuels, une machine Linux a généralement deux décodeurs base64, et ils se comportent différemment, de la manière même qui mord. Le premier est base64 de GNU coreutils (certaines distributions plus récentes livrent à la place la réimplémentation uutils, et les deux parlent les mêmes drapeaux - vérifiez avec base64 --version). Il se conforme à la RFC 4648, enveloppe à 76 caractères lors de l'encodage (-w 0 l'éteint), et au décodage il accepte gaiement les retours à la ligne n'importe où ; son drapeau -i rend la tolérance aux caractères indésirables explicite au lieu d'accidentelle. Le second est celui d'OpenSSL, et voici le retournement : openssl base64 n'est pas une application à part entière du tout. Depuis la série 1.1.0 (2016), le programme enc vérifie son propre nom d'invocation, et s'il a été appelé « base64 », il se bascule lui-même en mode base64 - une comparaison de chaînes sur argv[0], ce qui est la façon C de livrer un alias. Sans -A, il s'attend à un retour à la ligne quelque part dans les 1024 premiers octets d'entrée, si bien qu'une longue chaîne d'une seule ligne revient vide, avec le code de sortie 0. Avec -A il lit une ligne, et la liste des bugs documentés de la commande enc est un musée à deux pièces : l'option -A ne fonctionne pas correctement avec les gros fichiers, et sans -A, si les 1024 premiers octets ne contiennent pas de retour à la ligne, les deux premières lignes d'entrée sont ignorées. Dans un pipeline, un fichier vide silencieux a exactement l'air d'un décodage réussi d'un payload vide.
# les one-liners honnêtes
base64 -d < payload.b64 > payload.bin
openssl base64 -d -A < payload.b64 > payload.bin
Aucun des deux ne parle base64url nativement, ce qui est une raison de plus de garder l'extrait de transcodage dans votre mémoire musculaire. Pour tout ce qui compte, décodez dans votre programme, là où les erreurs reviennent sous forme de nombres que vous pouvez tester et où le code de sortie d'un outil silencieux n'est pas votre seul signal.
Les pièges qui mordent spécifiquement le C++
- Le remplissage de zéros.
EVP_DecodeBlockrenvoie trois octets pourTQ==: la lettre M plus deux zéros. Récupérez la vraie longueur depuis le padding, ou utilisez l'API en streaming, qui est honnête sur le décompte. - La particularité en streaming d'avant 3.5. Sur les versions d'OpenSSL antérieures à 3.5.0 (avril 2025),
EVP_DecodeUpdateavait la même habitude de remplissage de zéros. Un code écrit contre un calage 3.0 ou 3.3 peut vous mentir sur les longueurs de queue ; la correction est enregistrée dans la section historique de la page de manuel. - L'arrêt silencieux. Le
decodede Boost.Beast n'a pas de canal d'erreur : il s'arrête sur n'importe quel caractère invalide, n'importe quel retour à la ligne, et n'importe quelle longueur de queue impossible, et renvoie un résultat partiel sans changer d'expression. Vérifiez queconsumed + pads == input.size()et que le total est un multiple de 4, sinon vous décodez ce qu'il a décidé de décoder. - Le piège de decoded_size.
b64::decoded_size(n)suppose quenest divisible par 4. Deux caractères d'entrée peuvent donner un octet, tandis quedecoded_size(2)dit zéro - ajoutez de la marge pour les longueurs impaires. - Les pads en octets nuls. Les itérateurs de Boost décodent
=comme la valeur zéro, doncTWFuZQ==devient six octets dont deux zéros en fin. Soustrayez le nombre de pads, ou appréciez vos fantômes. - Les espaces blancs, en quatre façons. Le streaming d'OpenSSL les saute, le one-shot d'OpenSSL les refuse à l'intérieur, les itérateurs d'archive lèvent l'exception dessus, et Beast s'y arrête. Les chaînes collées adorent porter des espaces blancs, et chaque décodeur a son opinion là-dessus.
- Indexation par char signé. Si vous fabriquez votre propre table de décodage indexée par caractère, indexez avec
unsigned char. Sur les plateformes oùcharest signé, un octet au-dessus de 127 devient un indice négatif, ce qui est un comportement indéfini en blouse blanche. - Le tiret est un voyageur dans le temps. Dans OpenSSL,
-est un marqueur doux de fin d'entrée de l'ère PEM, pas un caractère de l'alphabet. Transcodez le base64url avant de décoder. - int, pas size_t. Les paramètres de longueur EVP sont des
int. Au-dessus de 2 Go, seul le chemin en streaming par blocs est sûr, c'est pourquoi il existe. - Le mode texte sous Windows. Ouvrir un fichier en texte traduit CRLF en LF et corrompt votre entrée avant le décodage.
std::ios::binary, à chaque fois, sur chaque plateforme. - Le most vexing parse.
std::vector<char> v(istreambuf_iterator<char>(f), istreambuf_iterator<char>())est une déclaration de fonction. Utilisez une initialisation en accolades ou une paire de pointeurs. - Les bits de padding non canoniques. Un décodeur indulgent peut accepter des chaînes dont les bits de padding inutilisés ne sont pas nuls, si bien que deux chaînes visiblement différentes se décodent en les mêmes octets (malléabilité du base64). À une frontière de sécurité, refusez ce dont vous n'avez pas besoin - la RFC 4648 dit que les décodeurs peuvent faire exactement cela.
- La ligne de commande échoue silencieusement.
openssl base64 -dsans-Aavale l'entrée d'une seule ligne (sortie vide, code de sortie 0) ; les bugs documentés couvrent les gros fichiers et l'entrée sans retour à la ligne dans les deux sens. Vérifiez votre sortie dans les pipelines. - strlen sur du binaire.
std::stringgarde les octets nuls contents, mais dès que vous remettez une chaîne C à une API héritée,strlens'arrête au premier NUL. Passez la longueur et le pointeur, jamais un pointeur nu.
Une courte histoire du Base64 en C++
Le format est plus vieux que l'ère moderne de la langue. Le premier usage standardisé de l'encodage désormais appelé base64 MIME était le protocole Privacy-Enhanced Mail, proposé en 1987 avec des lignes de 64 caractères et un contrôle d'intégrité de message RSA-MD2/MD5 collé à la fin ; le nom « base64 » lui-même n'est arrivé qu'en 1993, quand les normes MIME l'ont nommé. C++ est arrivé sur la scène sous le nom de C++98 en 1998 - cinq ans après le MIME - et le premier code base64 vers lequel les développeurs de la langue ont tendu la main était le duo C de Rene Nyffenegger (2004-2008), qu'une question de Stack Overflow du 4 décembre 2008 a répandu sur le web. La plus belle partie de cette histoire est de savoir qui n'est pas venu : l'auteur n'a jamais posté de réponse lui-même, mais son extrait est devenu le chant populaire que tout le monde a copié. Une réponse dans le fil a réimprimé son implémentation complète depuis son propre site, au cas où le site tomberait.
Puis l'écosystème a fait ce que les écosystèmes font. En 2002, le Boost.Serialization de Robert Ramey a livré les adapteurs d'itérateurs - le base64 le plus ancien de la boîte à outils C++, strict au point de lever l'exception sur un seul espace, un an avant que la RFC 3548 ne codifie la règle qu'il appliquait déjà. En 2017, Boost 1.66 a apporté Beast, et avec lui le codec header-only qui est encore livré aujourd'hui avec l'attribution à Nyffenegger dans son pied de page. Pendant ce temps, la norme elle-même est passée par C++11, C++14, C++17, C++20 et C++23 (publié en 2024), et chacune d'elles a regardé l'alphabet de 64 caractères et est passée à autre chose. C++26 ajoute un nouvel en-tête <text_encoding> pour le travail de codec de texte, approuvé dès 2022 ; son contenu technique a été terminé à la réunion ISO de mars 2026 à Londres, où le comité a voté 114-12-3 pour l'envoyer à la publication, et les réunions suivantes du comité en 2026 - y compris celle de Búzios, Brésil, du 16 au 21 novembre - servent à ouvrir le prochain projet de travail, le C++29, plutôt qu'à voter sur celui-ci. Le base64 n'a jamais été dans le projet de travail. Sept normes, trois décennies, un en-tête pour l'encodage de texte - et le comité a maintenant eu toutes les excuses possibles d'ajouter le base64, mais a refusé à chaque fois. L'histoire pratique du base64 en C++ est, et reste, l'histoire de ses bibliothèques : les routines EVP d'OpenSSL, deux saveurs Boost, un appel d'API Windows, et un extrait de quarante lignes qui vous appartient.
Faits insolites, édition C++
- Le même duo de fonctions apparaît dans les réponses à une question de Stack Overflow de 2008, dans le source de Boost.Beast avec un pied de page d'attribution, et dans les fichiers en-tête d'innombrables bases de code privées. Demandez à un développeur C++ d'où vient son base64 et la réponse la plus honnête est « je ne sais pas, et internet non plus ».
- Les itérateurs d'archive de Boost sont le base64 le plus ancien de cet article, copyright 2002 - la même année où le SDK du .NET Framework 1.0 est sorti. Ils lèvent l'exception sur un seul espace, ce qui veut dire qu'ils appliquaient la règle « refuser les caractères non alphabétiques » avant que les RFC ne rattrapent leur retard : la RFC 3548 l'a codifiée en 2003, et la RFC 4648 l'a répétée en 2006.
- Le décodeur en streaming d'OpenSSL travaille depuis un buffer interne de 80 octets, mais il vide à tous les 64 caractères base64, la même largeur de ligne que l'armure PEM utilise depuis 1987. Ce 64 discret est l'un des derniers endroits où le vieux format fait encore un travail porteur en 2026.
- Le base64 avec padding le plus petit possible fait quatre caractères,
TQ==: un octet déguisé en deux caractères. Le plus petit sans padding fait deux caractères,TQ. Celui que vous aurez à décoder dépend entièrement de celui qui l'a encodé, et cette personne ne pensait pas à vous. - Les calculs du MIME sont exacts : 4/3 fois 78/76, c'est pourquoi une pièce jointe de courriel arrive à environ 137 pour cent de sa taille d'origine (plus quelques centaines d'octets d'en-têtes en plus). Votre décodeur C++ le rétrécit à nouveau à 100 pour cent, ce qui est la joie discrète de tout l'exercice.
- Sur un libstdc++ ou MSVC typique,
std::stringporte les petits payloads dans un buffer de pile grâce à l'optimisation des petites chaînes au lieu d'allouer. Une entrée de 9 octets se décode en 6 octets et ne touche jamais le tas. La forme base64 de votre minuscule blob de configuration peut littéralement vivre dans un cadre de pile, ce qui est le genre de repas gratuit que la bibliothèque standard ne met pas en avant. - La commande
openssl base64vers laquelle vous pouvez tendre la main dans un shell n'est pas une commande du tout. C'est le programmeencqui vérifie son propre nom dansargv[0]et qui change de personnalité. Un alias par comparaison de chaînes, ce qui est la façon C++ de faire les choses, en C. - Les identifiants de vidéo YouTube sont du base64url : onze caractères, pas de padding, pas de
+ni de/aux alentours d'une URL. Le format d'encodage le plus regardé de la planète roule sur la variante « sûre pour les URL et les noms de fichiers » que la RFC 4648 a ajoutée dans une section qui tient sur une page.
Quand vous devez plutôt emballer
Tout ce que vous venez de décoder a été emballé par la même boîte à outils de l'autre côté : EVP_EncodeBlock pour les one-shots, EVP_EncodeUpdate plus EVP_EncodeFinal pour les streams (et c'est d'où viennent ces lignes de 64 caractères), la même arithmétique de buffers à l'envers, et le même impôt de 33 pour cent que le décodage rembourse discrètement. L'histoire complète de l'emballage - les calculs de taille au détail, les encodeurs qui terminent leur sortie par un nul, l'itérateur Boost qui n'a jamais croisé un caractère de padding, le base64url, l'enveloppement MIME, les fichiers, et l'API Windows avec son habitude CRLF - vit dans le guide d'encodage C++ sur le site soeur. Allez le lire, puis revenez ouvrir quelque chose de gros. C'est tout le jeu : pas de bibliothèque standard, trois fournisseurs fiables aux trois tempéraments différents, un décodeur qui pointe l'octet exact qui a fait mal, une correction de bug de 2025 qui a changé la queue en streaming, et un triplet rempli de zéros à retenir pour toujours. Bonne ouverture de paquets.
Dernière mise à jour : 2026-09-08
Article associé : Encodage Base64 en C++ (Cpp) : un guide complet