Encoder et décoder une chaîne
Utilisez les méthodes strict_, sauf si vous voulez précisément une sortie MIME :
require "base64"
enc = Base64.strict_encode64("héllo wörld")
puts enc # aMOpbGxvIHfDtnJsZA==
dec = Base64.strict_decode64(enc).force_encoding("UTF-8")
puts dec # héllo wörld
Les chaînes Ruby portent un encodage, et les chaînes décodées reviennent toujours en BINARY (ASCII-8BIT), car Base64 ignore ce que signifient les octets. force_encoding("UTF-8") les réétiquette sans copie. Si vous l’oubliez, les comparaisons avec des littéraux UTF-8 échouent et les méthodes de chaîne traitent chaque octet comme un caractère.
Si la chaîne source est dans un autre encodage (par exemple Windows-1252 issu d’un ancien fichier), appelez .encode("UTF-8") avant d’encoder en Base64, sinon le destinataire verra d’autres octets que ceux que vous attendez.
encode64 ou strict_encode64
encode64 est la variante MIME. Elle insère un saut de ligne tous les 60 caractères et se termine toujours par un saut de ligne :
Base64.encode64("hi") # => "aGk=\n"
Base64.encode64("x" * 60)
# => "eHh4...eHh4\neHh4eHh4eHh4eHh4eHh4\n" (60 chars per line)
Base64.strict_encode64("hi") # => "aGk="
Ce \n final est le bug Base64 le plus courant en Ruby : il casse les en-têtes HTTP, les signatures et les comparaisons de JSON. Notez que Ruby coupe à 60 caractères, alors que la RFC 2045 autorise jusqu’à 76 et que la plupart des autres langages utilisent 76. Les deux sont valides pour l’e-mail ; s’il vous faut exactement 76, l’encodeur Base64 MIME montre ce format.
Base64 URL-safe avec et sans remplissage
Base64.urlsafe_encode64("<<???>>") # => "PDw_Pz8-Pg=="
Base64.urlsafe_encode64("<<???>>", padding: false) # => "PDw_Pz8-Pg"
Base64.urlsafe_decode64("PDw_Pz8-Pg") # => "<<???>>"
urlsafe_decode64 accepte une entrée avec ou sans remplissage : il ajoute les = manquants puis décode en mode strict. Le mot-clé padding: existe depuis Ruby 2.3. Pour les JWT et les autres jetons, voir Base64 ou Base64URL ou essayez le décodeur Base64URL.
Lire les claims d’un JWT, sans le vérifier :
require "json"
jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
header, payload, _sig = jwt.split(".")
JSON.parse(Base64.urlsafe_decode64(header)) # => {"alg" => "HS256"}
JSON.parse(Base64.urlsafe_decode64(payload)) # => {"sub" => "1234"}
Le format de sortie {"alg" => "HS256"} est le nouveau Hash#inspect de Ruby 3.4 ; les versions plus anciennes affichent {"alg"=>"HS256"}.
Encoder et décoder un fichier
Lisez et écrivez en mode binaire, sinon la conversion des fins de ligne sous Windows et la conversion d’encodage peuvent modifier les octets :
puts Base64.strict_encode64(File.binread("hello.txt")) # aGVsbG8K
File.binwrite("copy.txt", Base64.strict_decode64("aGVsbG8K"))
Gros fichiers par blocs
Le module Base64 n’a pas d’API de flux, mais pack("m0") sur des blocs fonctionne tant que chaque bloc est un multiple de 3 octets :
File.open("big.bin", "rb") do |src|
File.open("big.b64", "wb") do |dst|
while (chunk = src.read(3 * 1024 * 64)) # 192 KiB, divisible by 3
dst.write([chunk].pack("m0"))
end
end
end
Sur un fichier de test de 300 Ko, le résultat était identique à strict_encode64(File.binread(...)). Avec toute autre taille de bloc, des = de remplissage apparaissent au milieu de la sortie.
pack et unpack1 directement
Le module n’est qu’une couche de confort. Ces appels sont équivalents et ne demandent aucun require :
["hi"].pack("m0") # => "aGk=" (same as strict_encode64)
["hi"].pack("m") # => "aGk=\n" (same as encode64)
"aGk=".unpack1("m0") # => "hi" (same as strict_decode64)
Validation et erreurs
strict_decode64 lève une ArgumentError avec le message invalid base64 pour tout ce qui n’est pas du Base64 canonique avec remplissage. Nous avons essayé six entrées, et toutes l’ont déclenchée :
| Entrée | Problème |
|---|---|
PDw_Pz8-Pg== | caractères URL-safe |
aGk | remplissage manquant |
aGk=!! | déchets après le remplissage |
a | longueur impossible |
aGl= | bits de remplissage non nuls |
"aGVs\nbG8K" | saut de ligne |
def base64?(s)
Base64.strict_decode64(s)
true
rescue ArgumentError
false
end
Le decode64 non strict ne lève jamais d’erreur. Il saute tout ce qu’il ne comprend pas, ce qui produit des octets sans queue ni tête au lieu d’une erreur :
Base64.decode64("aGk=!!") # => "hi"
Base64.decode64("PDw_Pz8-Pg==") # => "<<\x0F\xCF\xC3\xE0" (should be "<<???>>")
N’utilisez decode64 que pour des corps MIME avec sauts de ligne, et seulement si vous faites confiance à la source. Quand une chaîne refuse de se décoder, le validateur Base64 désigne le problème et les erreurs Base64 courantes en expliquent les causes habituelles.
Ruby 3.4 et Bundler
Ruby 3.4 a déplacé base64 des default gems vers les bundled gems. Hors de Bundler, rien ne change. Sous bundle exec, un Gemfile qui ne la mentionne pas échoue :
warning: base64 was loaded from the standard library, but is not part of the
default gems starting from Ruby 3.4.0.
...
cannot load such file -- base64 (LoadError)
Ajoutez gem "base64" au Gemfile ou au gemspec, ou remplacez les appels par pack("m0") / unpack1("m0"), qui font partie du cœur de Ruby.
FAQ
Pourquoi Base64.encode64 ajoute-t-il \n à ma chaîne ?
encode64 produit une sortie RFC 2045 avec un saut de ligne tous les 60 caractères et à la fin. Utilisez strict_encode64 pour les en-têtes, le JSON et les URL : cette méthode n’ajoute jamais de saut de ligne.
Pourquoi « cannot load such file -- base64 » sous Ruby 3.4 ?
Depuis Ruby 3.4, base64 est une bundled gem et non plus une default gem. La commande ruby seule la charge toujours, mais sous Bundler il faut ajouter gem "base64" au Gemfile. Vous pouvez aussi vous passer du require et utiliser [data].pack("m0").
Comment décoder du Base64 sans remplissage en Ruby ?
Base64.urlsafe_decode64 ajoute lui-même le remplissage manquant. Pour l’alphabet standard, ajoutez "=" * (-s.length % 4) avant d’appeler strict_decode64.