Trois opérations différentes
Base64 apparaît régulièrement là où l’on s’attend à une protection : il vaut donc la peine d’être précis sur ce qu’il fait. C’est un encodage : une correspondance fixe et publique entre des octets et du texte (voir le fonctionnement de Base64). Cette correspondance n’a ni clé ni secret, et la même entrée produit toujours la même sortie.
| Encodage (Base64, hex) | Chiffrement (AES-GCM) | Hachage (SHA-256, Argon2) | |
|---|---|---|---|
| Objectif | Représenter des octets sous forme de texte | Garder les données confidentielles | Prendre l’empreinte de données ou les vérifier |
| Réversible | Oui, par n’importe qui | Oui, avec la clé | Non |
| Nécessite une clé | Non | Oui | Non (HMAC en utilise une) |
| Même entrée, même sortie | Toujours | Non, avec un nonce aléatoire | Oui, sauf avec un sel |
| Taille de sortie | Environ 4/3 de l’entrée | Entrée plus nonce et tag | Longueur fixe |
Décoder ne demande aucun effort :
echo ZGVwbG95Omh1bnRlcjI= | base64 -d
# deploy:hunter2
Pour une personne qui y jette un œil, cette chaîne paraît opaque. Pour un script, un scanner de logs ou un attaquant, c’est du texte en clair avec une étape de plus.
Comment les données en Base64 fuient en pratique
Le schéma est toujours le même : une valeur est encodée en Base64 pour le transport, quelqu’un la prend pour une valeur protégée, puis elle finit quelque part où on peut la lire. Voici les formes courantes de cette erreur.
En-têtes HTTP Basic Auth
L’authentification Basic (RFC 7617) envoie le nom d’utilisateur et le mot de passe sous la forme Base64(user:password) dans chaque requête :
Authorization: Basic ZGVwbG95Omh1bnRlcjI=
La seule protection est le TLS de la connexion. Dès que l’en-tête sort de ce tunnel, le mot de passe est de fait en clair. Les cas typiques : des reverse proxies ou des frameworks applicatifs qui journalisent les en-têtes de requête, des fichiers HAR exportés depuis les outils de développement du navigateur et joints à un ticket de support, une sortie curl -v collée dans une messagerie, des outils de suivi d’erreurs qui capturent les requêtes complètes. Le décodeur Basic Auth montre à quel point il en faut peu pour en lire un.
Secrets Kubernetes
Un Secret Kubernetes stocke ses valeurs encodées en Base64 :
apiVersion: v1
kind: Secret
metadata:
name: db
data:
password: czNjcjN0
czNjcjN0, c’est s3cr3t. L’encodage existe pour que les valeurs puissent contenir des données binaires dans du YAML. La documentation de Kubernetes indique que les Secrets sont stockés non chiffrés dans etcd par défaut, et quiconque a le droit de faire un get sur le Secret via l’API peut le lire. Les fuites courantes : des manifestes avec de vraies valeurs data: commités dans un dépôt Git, des sorties kubectl get secret -o yaml collées dans des tickets, et des sauvegardes d’etcd ou de l’état du cluster stockées sans chiffrement. Le décodeur de Secrets Kubernetes décode d’un coup toutes les valeurs d’un Secret, ce qui permet aussi de vérifier vite ce qu’un manifeste expose réellement.
Les identifiants de registres de conteneurs suivent le même schéma. Le champ auth d’un config.json Docker ou d’un Secret .dockerconfigjson vaut Base64(user:password) ; le décodeur dockerconfigjson le lit.
Payloads JWT
Un JWT signé ressemble à un jeton scellé, mais ses deux premières parties ne sont que du JSON en Base64URL :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImVtYWlsIjoiYW5hQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIn0.<signature>
Le payload se décode en {"sub":"42","email":"ana@example.com","role":"admin"}. La signature protège le jeton contre la modification ; elle ne fait rien pour la confidentialité. Chaque extension de navigateur, proxy, fichier de log et script d’analytics qui voit le jeton peut lire son contenu. Les données personnelles, les identifiants internes que vous préférez ne pas exposer et tout ce qui est secret n’ont rien à faire dans le payload d’un JWT. S’ils doivent voyager dans un jeton, utilisez un JWT chiffré (JWE). Plus de détails dans Base64 dans les JWT.
Secrets « obfusqués » dans le code et la configuration
Les clés d’API stockées en Base64 dans du JavaScript front-end, des bundles d’applications mobiles ou des fichiers de configuration sont un classique. Le raisonnement habituel : cela évite que la clé soit « en clair ». Cela évite en effet qu’elle corresponde à une recherche naïve de la clé littérale, et c’est tout. Tout ce qui est livré à un client peut être lu par ce client.
La même idée sert dans l’autre sens lors d’attaques. Le -EncodedCommand de PowerShell prend du Base64 de texte UTF-16LE, et il est souvent utilisé pour rendre des commandes malveillantes plus difficiles à lire d’un coup d’œil. Les défenseurs le décodent en quelques secondes, par exemple avec le décodeur de commandes PowerShell encodées.
Que faire à la place
Choisissez l’outil selon la tâche. Base64 garde sa place dans chacun de ces cas, comme enveloppe texte autour d’octets protégés par autre chose.
Pour garder des données confidentielles : le chiffrement authentifié
Utilisez AES-256-GCM ou ChaCha20-Poly1305, via une bibliothèque bien maintenue. Les deux sont authentifiés : toute altération est détectée au déchiffrement. Deux règles comptent plus que l’algorithme : ne jamais réutiliser un nonce avec la même clé, et garder la clé ailleurs qu’à côté des données (un KMS, un gestionnaire de secrets ou un environnement que l’attaquant ne contrôle pas aussi).
import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
const key = randomBytes(32); // in real code, load this from a KMS or secret store
const iv = randomBytes(12); // 96-bit nonce, unique per message
const cipher = createCipheriv("aes-256-gcm", key, iv);
const ct = Buffer.concat([cipher.update("deploy:hunter2", "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
// Base64 carries the encrypted bytes as text: nonce + tag + ciphertext
const token = Buffer.concat([iv, tag, ct]).toString("base64");
const raw = Buffer.from(token, "base64");
const d = createDecipheriv("aes-256-gcm", key, raw.subarray(0, 12));
d.setAuthTag(raw.subarray(12, 28));
console.log(Buffer.concat([d.update(raw.subarray(28)), d.final()]).toString("utf8"));
// deploy:hunter2
La sortie est une chaîne Base64 de 56 caractères qui change à chaque exécution, car le nonce est aléatoire. Sans la clé, elle ne sert à rien. Pour expérimenter sans écrire de code, les outils chiffrement AES et déchiffrement AES fonctionnent dans votre navigateur.
Pour stocker des mots de passe : un hachage lent et salé
Les mots de passe ne doivent être ni chiffrés ni encodés. Stockez un hachage produit par une fonction conçue pour les mots de passe : Argon2id, scrypt ou bcrypt, avec un sel aléatoire unique par mot de passe. Un simple SHA-256 d’un mot de passe se calcule trop vite et cède à la force brute. La bibliothèque standard de Python inclut scrypt :
import hashlib, hmac, os
def hash_password(pw: str) -> bytes:
salt = os.urandom(16)
return salt + hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)
def check_password(pw: str, stored: bytes) -> bool:
salt, digest = stored[:16], stored[16:]
candidate = hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1)
return hmac.compare_digest(candidate, digest)
Si vous devez placer le résultat dans une colonne texte ou du JSON, encodez-le en Base64.
Pour prouver l’intégrité : signatures et HMAC
Quand l’objectif est « personne n’a modifié ceci » plutôt que « personne ne peut lire ceci », utilisez un HMAC avec un secret partagé ou une signature numérique. Le résultat est une suite d’octets, généralement transportée en Base64 ou en hex. Le générateur HMAC affiche la sortie sous les deux formes.
Pour les identifiants en transit et au repos
Utilisez TLS pour chaque connexion qui transporte du Basic Auth ou des jetons, préférez des jetons à courte durée de vie aux mots de passe statiques, retirez les en-têtes Authorization des logs, activez le chiffrement au repos des Secrets Kubernetes et gardez les vraies valeurs secrètes hors de Git.
FAQ
Base64 est-il sécurisé ?
Base64 n’a aucune propriété de sécurité. Il n’y a pas de clé : pour décoder, il suffit de la chaîne elle-même. On peut l’utiliser sans risque comme format de transport pour des données déjà protégées, comme un texte chiffré ou une signature.
Base64 suffit-il pour cacher un mot de passe dans un fichier de configuration ?
Non. Quiconque peut lire le fichier peut décoder le mot de passe avec base64 -d. Restreignez les droits sur le fichier, utilisez un gestionnaire de secrets, ou chiffrez la valeur avec une clé stockée ailleurs.
Pourquoi les Secrets Kubernetes sont-ils encodés en Base64 si ce n'est pas pour la sécurité ?
Pour que les valeurs des Secrets puissent contenir des données binaires quelconques, comme des clés TLS ou des keystores, dans du YAML et du JSON. La protection vient du RBAC, du chiffrement au repos d’etcd et d’une manipulation prudente des manifestes.
Peut-on lire le payload d'un JWT sans le secret ?
Oui. L’en-tête et le payload sont du JSON en Base64URL que tout le monde peut lire. La clé n’est nécessaire que pour vérifier ou créer la signature. Essayez avec le décodeur JWT.
Quelle différence entre encodage, chiffrement et hachage ?
L’encodage est réversible par n’importe qui. Le chiffrement n’est réversible qu’avec une clé. Le hachage est à sens unique : on peut vérifier une valeur par rapport à un hachage, mais pas retrouver la valeur à partir du hachage.