Charger un fichier
  1. base64hub
  2. Base64 dans le code
  3. Ruby
Code

Base64 en Ruby

Le module Base64 de Ruby est une fine couche au-dessus de Array#pack et String#unpack1. Le principal piège : les méthodes non strictes ajoutent des sauts de ligne. Les sorties ci-dessous proviennent de Ruby 3.4.

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éeProblème
PDw_Pz8-Pg==caractères URL-safe
aGkremplissage manquant
aGk=!!déchets après le remplissage
alongueur 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.

Continuer la lecture