La formule
Pour une entrée de n octets, le Base64 avec remplissage (le défaut dans la RFC 4648) fait exactement :
length = 4 × ceil(n / 3)
Sans remplissage, comme le Base64URL des JWT, il fait :
length = ceil(4n / 3)
La différence entre les deux correspond au nombre de signes =, soit 0, 1 ou 2 selon n mod 3. Le guide du remplissage explique d’où ils viennent.
Dans l’autre sens, une chaîne Base64 avec remplissage de longueur L se décode en :
bytes = 3 × L / 4 − (number of trailing "=")
Ainsi, TWFu fait 3 octets, TWE= 2 et TQ== 1. Supprimez les espaces et les sauts de ligne avant de compter. Le calculateur de taille Base64 fonctionne dans les deux sens, sauts de ligne et préfixes de data URI compris.
En code :
import base64, math, os
for n in [1, 2, 3, 100, 1000]:
data = os.urandom(n)
assert len(base64.b64encode(data)) == 4 * math.ceil(n / 3)
const b64Len = (n) => 4 * Math.ceil(n / 3);
const rawLen = (s) => (s.length * 3) / 4 - (s.endsWith("==") ? 2 : s.endsWith("=") ? 1 : 0);
b64Len(1000); // 1336
rawLen("TWE="); // 2
Pourquoi le surcoût est de 33 %
Un octet compte 8 bits. Un caractère Base64 porte 6 bits de données, puisqu’il fait partie de 64 = 2⁶ symboles, mais il est stocké comme un octet ASCII de 8 bits. Chaque octet de sortie contient donc 6 bits utiles, et la sortie doit faire 8/6 = 4/3 de l’entrée. Cela fait 33,3 % de plus, et c’est un plancher absolu : aucune implémentation de Base64 ne peut faire mieux sans abandonner l’alphabet. (Le regroupement des bits est détaillé étape par étape dans le fonctionnement de Base64.)
Pour les petites entrées, le remplissage dégrade le ratio, puisque la sortie arrive toujours par blocs de 4. Un octet devient 4 caractères, soit une augmentation de 300 %. À partir de quelques centaines d’octets, le remplissage devient négligeable et le ratio se stabilise à 4/3.
Il existe des encodages texte plus denses. Base85 stocke 4 octets en 5 caractères (25 % de surcoût), et Base91 fait un peu mieux. Ils sont moins largement pris en charge et leurs alphabets contiennent des caractères qui posent problème dans certains contextes. La comparaison se trouve dans Base64, hex, Base32, Base58 et Base85 comparés.
Sauts de ligne : MIME, PEM et outils en ligne de commande
Certains formats coupent le Base64 en lignes, et les fins de ligne comptent dans la taille.
| Format | Longueur de ligne | Fin de ligne | Surcoût total |
|---|---|---|---|
| Base64 simple (RFC 4648) | pas de découpage | aucune | 33,3 % |
| E-mail MIME (RFC 2045) | 76 | CRLF (2 octets) | 36,8 % |
GNU base64, encodebytes de Python | 76 | LF (1 octet) | 35,1 % |
| Certificats et clés PEM | 64 | LF | 35,4 % |
Le chiffre de MIME vient de 4/3 × 78/76 : chaque tranche de 76 caractères de Base64 apporte 2 octets de CRLF en plus. Avec L = 4 × ceil(n / 3), la taille avec un CRLF après chaque ligne, y compris la dernière, est :
mime_length = L + 2 × ceil(L / 76)
Chaque ligne complète de 76 caractères contient exactement 57 octets d’entrée : c’est pourquoi les encodeurs MIME traitent les données par blocs de 57 octets. Plus d’informations sur l’e-mail dans Base64 dans les e-mails, et l’encodeur Base64 MIME produit une sortie découpée.
Table des tailles
Calculée avec les formules ci-dessus et vérifiée avec le module base64 de Python.
| Entrée | Base64 avec remplissage | Sans remplissage | MIME (76 + CRLF) | Surcoût (avec remplissage) |
|---|---|---|---|---|
| 1 octet | 4 | 2 | 6 | 300 % |
| 2 octets | 4 | 3 | 6 | 100 % |
| 3 octets | 4 | 4 | 6 | 33,3 % |
| 10 octets | 16 | 14 | 18 | 60 % |
| 57 octets | 76 | 76 | 78 | 33,3 % |
| 100 octets | 136 | 134 | 140 | 36 % |
| 1 000 octets | 1 336 | 1 334 | 1 372 | 33,6 % |
| 1 Kio (1 024) | 1 368 | 1 366 | 1 404 | 33,6 % |
| 1 Mo (10⁶) | 1 333 336 | 1 333 334 | 1 368 424 | 33,3 % |
| 1 Mio (1 048 576) | 1 398 104 | 1 398 102 | 1 434 898 | 33,3 % |
| 10 Mio | 13 981 016 | 13 981 014 | 14 348 938 | 33,3 % |
| 25 Mo (25 × 10⁶) | 33 333 336 | 33 333 334 | 34 210 530 | 33,3 % |
La dernière ligne correspond au cas de l’e-mail. Les limites de taille SMTP (l’extension SIZE, RFC 1870) portent sur le message tel que transmis, encodage compris : un fichier de 25 Mo devient environ 34,2 Mo sur le réseau et ne passera pas la limite d’un serveur fixée à 25 Mo. Certains fournisseurs expriment plutôt leurs limites en taille de pièce jointe : vérifiez comment le vôtre compte.
L’effet de la compression
La compression interagit de deux façons avec Base64, et l’ordre compte.
Compressez avant d’encoder chaque fois que vous maîtrisez les deux extrémités. Le Base64 de données compressées représente 4/3 d’un nombre plus petit. Pour le texte de la licence GPL-3 (35 149 octets), nous avons mesuré avec zlib de Node.js, au niveau 9 de gzip :
| Chaîne de traitement | Taille |
|---|---|
| Texte brut | 35 149 |
| Base64 seul | 46 868 |
| Base64, puis gzip | 17 465 |
| gzip, puis Base64 | 16 124 |
| gzip seul | 12 091 |
L’outil Gzip + Base64 applique la chaîne « gzip, puis Base64 » dans le navigateur.
La compression au transport récupère l’essentiel du surcoût pour les données binaires. Si une chaîne Base64 circule dans une réponse HTTP compressée avec gzip ou Brotli, le compresseur ramène les caractères de 6 bits vers 6 bits chacun. Pour 1 Mio d’octets aléatoires, le Base64 est monté à 1 398 104 caractères ; compressé avec gzip, il faisait 1 053 023 octets (0,4 % au-dessus des données brutes) et Brotli l’a ramené à 1 048 805. Les mêmes données avec des sauts de ligne MIME donnaient 1 088 689 octets avec gzip : les CRLF coûtent donc environ 3,4 % même après compression.
Pour le texte, c’est différent. Base64 casse les répétitions dont dépendent les compresseurs : du texte en Base64 se compresse donc bien moins bien que le texte lui-même, 17 465 contre 12 091 octets dans le tableau ci-dessus. L’article data URI et performances donne des mesures pour PNG, JPEG, WOFF2 et SVG.
Limites des URL, des en-têtes et des cookies
Base64 fait grossir tout ce que vous mettez dans une URL, un en-tête ou un cookie, et ces trois emplacements ont des limites.
- Les URL. La RFC 9110 recommande que les émetteurs et les destinataires prennent en charge des URI d’au moins 8 000 octets. De nombreux serveurs s’arrêtent à peu près là par défaut : nginx alloue des tampons d’en-tête de 8 Ko pour la ligne de requête, et
LimitRequestLined’Apache vaut 8 190 octets par défaut. Les URL plus longues échouent en général avec414 URI Too Long. En Base64URL, cela laisse de la place pour environ 6 000 octets de données, moins le reste de l’URL. - Les en-têtes. Les mêmes tampons de serveur s’appliquent à chaque ligne d’en-tête, ce qui compte pour les longs JWT dans les en-têtes
Authorization: Bearer ...et pour les identifiants Basic Auth volumineux. - Les cookies. La RFC 6265 demande aux navigateurs de prendre en charge au moins 4 096 octets par cookie, nom et attributs compris. Cela représente environ 3 Ko de données une fois encodées en Base64.
Si un contenu Base64 approche de l’une de ces limites, envoyez-le plutôt dans le corps d’une requête, ou stockez-le côté serveur et transmettez un identifiant.
Saisissez un nombre d'octets ou collez une chaîne pour voir les tailles encodée et décodée, avec ou sans sauts de ligne.
FAQ
De combien Base64 est-il plus gros que le fichier d'origine ?
D’environ 33,3 % pour tout ce qui dépasse quelques octets. Avec les sauts de ligne MIME (76 caractères plus CRLF), c’est environ 36,8 %.
Comment calculer la taille d'origine à partir d'une chaîne Base64 ?
Prenez la longueur sans les espaces, multipliez-la par 3/4 et soustrayez le nombre de signes = à la fin. Par exemple, TWE= donne 4 × 3/4 − 1 = 2 octets.
Pourquoi une entrée de 1 octet produit-elle 4 caractères ?
Avec remplissage, Base64 produit toujours des blocs complets de 4 caractères. Un octet remplit 2 caractères et les 2 autres sont des = : les toutes petites entrées ont donc un surcoût relatif bien plus élevé.
Peut-on rendre Base64 plus compact ?
Compressez les données avant de les encoder, par exemple avec gzip, et supprimez le remplissage si le destinataire l’accepte. Base64 a lui-même un ratio fixe ; des encodages texte plus denses comme Base85 existent, mais sont moins largement pris en charge.