Vous avez à traiter le format Base64 ? Alors ce site est parfait pour vous ! Utilisez notre outil en ligne super pratique pour encoder ou décoder vos données.

Décodage Base64 en R : un guide complet

Une chaîne atterrit dans votre session R. Elle ressemble à une soupe d'alphabet mélangée : des lettres, des chiffres, un plus ou une barre oblique de-ci de-là, et une paire de signes égal qui traîne à la fin. La personne qui vous l'a envoyée jure qu'elle fut un jour une tout à fait ordinaire phrase, un JPEG ou un document JSON. Cette chaîne est du Base64, et cette page est la recette pour la ramener à ce qu'elle était.

Un petit rappel, parce que la page d'accueil de ce site explique le format en profondeur : le Base64 écrit trois octets d'entrée sous forme de quatre caractères tirés d'un alphabet de 64 symboles, et un ou deux caractères = à la queue marquent là où les vraies données se sont terminées. C'est cet échange de quatre contre trois qui fait que le texte encodé est environ un tiers plus gros que l'original, et le décodage suit simplement l'échange en sens inverse. Gardez le contour du problème en tête, et ouvrons quelques enveloppes.

Voici le détail qui rend R un peu différent de beaucoup d'autres langages : le R de base n'a aucun Base64. Il n'y a pas de base64_decode() caché dans un paquet de base, et pas de builtin en une ligne à portée de main. Il faut apporter son propre paquet. La bonne nouvelle, c'est que l'écosystème en offre plusieurs, chacun avec sa personnalité, et à la fin de cet article vous saurez exactement lequel choisir et auquel se méfier.

Les décodeurs en un coup d'œil

Cinq paquets font le gros du travail, et ils se répartissent en deux grands camps : les tolérants, qui haussent les épaules face à une entrée sale, et les stricts, qui traitent la RFC comme un contrat. Voici la distribution, à jour en 2026 :

Paquet Version (2026) Points d'entrée du décodage Personnalité
base64enc 0.1-6 base64decode() Tolérant par défaut, a gagné un mode strict en février 2026
openssl 2.4.2 base64_decode() Saute les espaces, mais échoue en silence de manières qui méritent un regard sévère
b64 0.1.7 decode(), decode_as_string() Strict, vectorisé, écrit en Rust, rapide
base64 2.0.2 decode() Enveloppe de confort fichier à fichier autour de openssl
base64url 1.4 base64_urldecode() Alphabet URL-safe, sans padding, indulgent sans faire de bruit

Trois dauphins méritent une mention. Le paquet jsonlite exporte ses propres aides, base64_enc, base64_dec et le duo URL-safe, donc si vous parsez déjà du JSON, vous avez peut-être un décodeur sous la main. Le paquet jose embarque base64url_decode() pour le travail avec les JWT. Et le très ancien paquet RCurl porte toujours une fonction base64() qui enveloppe libcurl : ça marche, c'est orienté caractères, et on dirait un grand-père fidèle plus qu'un outil pour du nouveau code.

Installer la distribution

Si R n'est pas encore sur la machine, votre système d'exploitation l'offre : r-base sur Debian et Ubuntu, R sur Fedora, un paquet ou un installateur sur macOS et Windows. Puis les paquets, directement depuis CRAN :

install.packages("base64enc")
install.packages("openssl")
install.packages("b64")
install.packages("base64url")

Deux notes de compilation, parce que c'est là que les installations prennent un mauvais virage. Le paquet openssl se compile contre votre OpenSSL système, donc une machine Linux dépouillée voudra d'abord les en-têtes de développement :

sudo apt install libssl-dev

Le paquet b64 est un moteur Rust enveloppé avec extendr, donc le construire depuis le source veut la chaîne d'outils Rust (sudo apt install cargo installe aussi rustc). Sur Windows et macOS vous obtenez des binaires précompilés depuis CRAN et rien de tout cela ne s'applique. Si vous préférez un gestionnaire de paquets, pak::pkg("base64enc") ou remotes::install_cran("b64") font le même travail avec moins d'avis sur les dépôts.

Votre premier décodage : le rite des trois lignes

Quatre-vingt-dix pour cent de la vie du décodage tiennent en trois lignes. Voici le test de fumée canonique, avec la célèbre chaîne TWFu :

library(base64enc)
packed <- "TWFu"
bytes <- base64decode(packed)
bytes
#> [1] 4d 61 6e
rawToChar(bytes)
#> [1] "Man"

Trois choses méritent attention dans cette petite cérémonie. Premièrement, base64decode() vous remet toujours un vecteur brut, jamais une chaîne. C'est une fonctionnalité, pas un accident : le Base64 peut porter une phrase, un JPEG ou un certificat, et aucun de ces trois ne doit être traité différemment avant que vous sachiez ce que vous tenez. Deuxièmement, le saut des octets vers le texte est une étape séparée et délibérée via rawToChar(), et c'est là que vit la décision de charset (nous y reviendrons). Troisièmement, TWFu se décode en le mot « Man » : trois lettres, zéro drame. Gardez cette chaîne dans votre poche. Si un bout de code de décodage transforme TWFu en Man, la machine est honnête.

Parce que vous finirez toujours par décoder quelque chose que vous avez encodé vous-même, voici l'aller-retour complet qui prouve que les deux directions se mettent d'accord :

text <- "Hello, world!"
packed <- base64encode(charToRaw(text))
packed
#> [1] "SGVsbG8sIHdvcmxkIQ=="
identical(text, rawToChar(base64decode(packed)))
#> [1] TRUE

La frontière du vecteur brut

R franchit la frontière octets/texte avec deux minuscules fonctions, et une fois qu'on les connaît, le reste de cet article semble évident. charToRaw() transforme une chaîne en ses octets, rawToChar() fait l'inverse, et entre les deux vous avez tout l'outillage brut : length() pour compter les octets, head() pour jeter un coup d'œil, writeBin() et readBin() pour les déplacer vers et depuis des fichiers. Chaque décodeur de cet article s'arrête volontairement à cette frontière.

bytes <- base64decode("SGVsbG8s")
length(bytes)
#> [1] 6
head(bytes)
#> [1] 48 65 6c 6c 6f 2c
rawToChar(bytes)
#> [1] "Hello,"

Ainsi, quand vous voyez un résultat comme [1] 48, lisez-le comme « un octet, valeur 72, la lettre H », et arrêtez-vous là tant que vous ne savez pas quel charset les octets portent. Cette pause est toute la discipline du décodage.

Entrées sales : comment les décodeurs divergent

C'est la section qui vous sauve à 2 heures du matin, parce que les décodeurs ne sont pas d'accord sur ce qui arrive quand l'entrée est un peu sale. Le Base64 du monde réel arrive avec des espaces au milieu, un padding arraché par un copier-coller nerveux, un symbole orphelin d'un presse-papiers qui l'a avalé, ou des ordures qui traînent à la fin. Voici comment chaque décodeur répond, face à la même famille de délinquants :

Entrée base64enc (défaut) base64enc (strict = TRUE) openssl b64
"SGVs bG8s IHdvcmxkIQ==" (un espace au milieu) « Hello, world! » (espace sauté) erreur : caractère invalide, position donnée « Hello, world! » (espace sauté) erreur
"SGVsbG8sIHdvcmxkIQ" (padding arraché) « Hello, world! » (padding manquant toléré) erreur : padding manquant erreur : échec du décodage erreur : padding invalide
"SGVsbG8s!IHdvcmxkIQ==" (un « ! » au milieu) « Hello, world! » (mauvais caractère sauté) erreur : caractère invalide vecteur brut vide, sans erreur erreur
"SGVsbG8sIHdvcmxkIQ==xx" (ordures en queue) « Hello, world! » (ordures ignorées) erreur : contenu en queue vecteur brut vide, sans erreur erreur
"TQ==" (propre) M M M M

Lisez ce tableau deux fois, parce qu'il contient toute l'histoire. Le base64decode() par défaut est l'agent de douane amical : il saute les caractères hors alphabet, tolère le padding manquant et ignore le contenu en queue, de sorte que les chaînes en forme d'email ou de presse-papiers passent tout simplement. Le mode strict = TRUE, qui est arrivé avec la version 0.1-5 en février 2026 après un long silence, est l'expert en forensique : il valide toute la chaîne, désigne la position exacte du délinquant et refuse tout ce qui n'est pas dans les manuels. b64 est strict par défaut et ne réussit jamais à moitié. Et openssl se tient dans le milieu inconfortable : il saute volontiers les espaces et signale correctement le padding manquant, mais quand il rencontre un caractère véritablement illégal ou des ordures en queue, il renvoie un vecteur brut vide et ne dit absolument rien. Ce résultat vide silencieux est le comportement le plus dangereux de tout cet article, alors regardons-le arriver :

dirty <- "SGVsbG8s!IHdvcmxkIQ=="
openssl::base64_decode(dirty)
#> raw(0)   # vide. pas d'erreur. pas d'avertissement. pas d'indice.

Si vous écrivez du code contre openssl, vérifiez la longueur de ce que vous avez reçu avant de lui faire confiance. C'est un petit réflexe qui empêche toute une classe de mystères du genre « où est passée ma donnée ».

Pour le camp strict, voici base64enc dans toute sa précision, avec les messages d'erreur exacts que vous verrez dans votre propre console :

base64enc::base64decode("TWF u", strict = TRUE)
#> v=30000, pad=0, org='u'
#> Error: Invalid character (' ') at position 4 in base64 string (not allowed in strict mode)
base64enc::base64decode("TWF", strict = TRUE)
#> v=10000, pad=3, org=''
#> Error: Missing padding (1 characters) at the end of the base64 string (not allowed in strict mode)
base64enc::base64decode("TWF=xx", strict = TRUE)
#> v=20000, pad=0, org='xx'
#> Error: Trailing content 'xx' after padding at position 4 in base64 string (not allowed in strict mode)

Une bizarrerie à prévoir : chaque appel strict, réussi ou raté, glisse une ligne de statut C dans la console, de la forme v=30000, pad=0, org='u'. C'est une écriture directe du code compilé, pas un avertissement R, donc suppressWarnings() n'y touche pas. C'est inoffensif, mais si vos tests comparent la sortie de la console, vous savez maintenant pourquoi il y a une ligne en plus.

Spécialités R : NA, vecteurs et concaténation silencieuse

Le Base64 a ses bizarreries ; R en ajoute. La première mord à travers NA. Quand vous passez NA_character_ à un décodeur, R le force en silence à la chaîne "NA" avant que le décodeur ne le voie, et "NA" est un groupe Base64 parfaitement valide. Le résultat n'est ni une erreur ni un vecteur vide. C'est l'octet 0x34, la lettre "4" :

base64decode(NA_character_)
#> [1] 34
rawToChar(base64decode(NA_character_))
#> [1] "4"

Donc une colonne de valeurs manquantes ne se décode pas en valeurs manquantes. Elle se décode en une colonne de la lettre 4, et votre code en aval la traite joyeusement. Si votre entrée peut contenir NA, filtrez d'abord :

values <- c("TWFu", NA_character_, "TQ==")
clean <- values[!is.na(values)]
out <- vapply(clean, function(x) rawToChar(base64decode(x)), character(1))
unname(out)
#> [1] "Man" "M"

La deuxième bizarrerie est l'entrée vectorielle, et les décodeurs prennent des virages complètement différents. base64decode() traite un vecteur de caractères comme plusieurs morceaux d'une même chaîne et les concatène, donc deux éléments reviennent en un seul vecteur brut. openssl::base64_decode() n'arrive même pas jusque-là : il aplati son argument puis tombe sur un test logique à deux éléments, qui produit l'un des messages d'erreur les plus honnêtes de R. Et b64::decode() est le seul véritablement vectorisé, qui renvoie une masse décodée par entrée :

base64decode(c("TWFu", "TQ=="))
#> [1] 4d 61 6e 4d
openssl::base64_decode(c("TWFu", "TQ=="))
#> Error in if (is.na(text)) : the condition has length > 1
out <- b64::decode(c("TWFu", "TQ=="))
rawToChar(out[[1]])
#> [1] "Man"
rawToChar(out[[2]])
#> [1] "M"

À retenir : bouclez ou vectorisez délibérément, et ne supposez jamais qu'un vecteur d'entrées vous donne un vecteur de sorties.

L'alphabet URL-safe

Le Base64 standard passe les deux dernières places de son alphabet à + et /, et ce sont précisément les caractères que les URL n'aiment pas. Le plus devient %2B, la barre oblique devient %2F, et le padding devient %3D, donc un jeton qui devrait se coller partout se met à porter des signes pourcentage. La variante URL-safe, définie dans la section 5 du RFC 4648, échange ces deux caractères contre - et _ et supprime d'habitude le padding aussi. R a trois portes vers ce monde.

La porte un, ce sont les moteurs b64. Un moteur est juste un alphabet configuré et une politique de padding, et le paquet embarque les quatre qui vous servent :

library(b64)
std <- encode(as.raw(c(0xfb, 0xef, 0xbe)))
std
#> [1] "++++"
url <- encode(as.raw(c(0xfb, 0xef, 0xbe)), engine("url_safe"))
url
#> [1] "----"
decoded <- decode(url, engine("url_safe"))[[1]]
toString(decoded)
#> [1] "fb, ef, be"

Les moteurs sont "standard" (le défaut), "standard_no_pad", "url_safe" et "url_safe_no_pad", et le même objet moteur marche dans les deux directions, ce qui garde votre code symétrique. Ils sont aussi stricts sur leur alphabet : offrez "----" au moteur standard et il répond "Invalid byte 45, offset 0.", ce qui est un soulagement.

La porte deux, c'est le paquet dédié base64url, petit et à une seule mission : l'alphabet URL-safe, sans padding, toujours. Une réserve : son décodeur est tolérant, donc il décodera joyeusement le préfixe valide d'une chaîne corrompue et s'arrêtera là sans se plaindre. Si une valeur URL-safe vient d'une source non fiable, préférez b64.

base64url::base64_urlencode("hello world")
#> [1] "aGVsbG8gd29ybGQ"
base64url::base64_urldecode("aGVsbG8g!!!")
#> [1] "hello "   # le reste a été jeté en silence

La porte trois, c'est jose, qui exporte base64url_encode() et base64url_decode() pour le travail avec les JWT, et qui renvoie des vecteurs bruts comme vous pourriez l'espérer.

Et quand il ne vous faut qu'un coup ponctuel et que vous voulez rester dans base64enc, une chirurgie de chaîne plus une correction de padding suffisent :

url <- "----"
fixed <- gsub("_", "/", gsub("-", "+", url), fixed = TRUE)
padded <- switch(as.character(nchar(fixed) %% 4L),
  "0" = fixed,
  "2" = paste0(fixed, "=="),
  "3" = paste0(fixed, "="))
rawToChar(base64decode(padded))
#> [1] "\xfb\xef\xbe"

JWT : jeter un œil et faire confiance

La raison pour laquelle la plupart des gens croisent le Base64 URL-safe, c'est le JSON Web Token. Un JWT, ce sont trois parties base64url jointes par des points : un en-tête qui décrit la façon dont il a été signé, un payload de claims, et une signature qui rend le tout digne de confiance. Décoder les deux premières parties en R est une affaire de cinq lignes. Remarquez le fixed = TRUE dans strsplit() : le point est un joker en regex, et sans cet attribut R découpe joyeusement votre jeton en caractères un par un, ce qui est le bug Base64 le plus courant dans le code R du monde réel.

jwt <- jose::jwt_encode_hmac(jose::jwt_claim(sub = "1234567890", iat = 1516239022, name = "John Doe"), "0123456789abcdef")
parts <- strsplit(jwt, ".", fixed = TRUE)[[1]]
header <- base64url::base64_urldecode(parts[1])
header
#> [1] "{\"typ\":\"JWT\",\"alg\":\"HS256\"}"
payload <- jsonlite::fromJSON(base64url::base64_urldecode(parts[2]))
payload$name
#> [1] "John Doe"

Deux avertissements honnêtes. Premièrement, décoder un JWT, c'est jeter un œil, pas faire confiance : la troisième partie est la signature, et elle ne veut dire quelque chose que lorsqu'elle est vérifiée contre la clé de l'émetteur. Pour ça, le paquet jose gère toute la famille. Sa version 2.0 (avril 2026) ajoute le support ED25519 et rend l'en-tête typ optionnel ; les vieux tutoriels qui montrent les aides 1.x jwk_read()/jwk_write() (ou leurs alias read_jwk()/write_jwk()) restent valides, parce que la 2.0 embarque toujours ces mêmes aides de lecture/écriture JWK :

library(jose)
claims <- jwt_decode_hmac(jwt, "0123456789abcdef")
claims$name
#> [1] "John Doe"
claims$sub
#> [1] "1234567890"
sp <- jwt_split(jwt)
sp$header
#> $alg
#> [1] "HS256"
#>
#> $typ
#> [1] "JWT"
sp$payload$name
#> [1] "John Doe"

jwt_decode_hmac() vérifie la signature et impose aussi les claims exp et nbf, en levant une erreur si le jeton est expiré. Et deuxièmement : base64url::base64_urldecode() et ses amis décodent l'en-tête et le payload sans problème, mais la partie signature est binaire, alors décodez-la avec une fonction qui renvoie du brut (comme b64::decode() avec le moteur "url_safe_no_pad") plutôt que vers une chaîne.

Charsets : quel texte portent ces octets ?

Chaque décodeur de cet article s'arrête volontairement à la frontière du vecteur brut, parce que la réponse à « quel texte était-ce ? » dépend du charset dans lequel les octets étaient emballés. La bonne nouvelle tout de suite : si l'expéditeur a utilisé l'UTF-8, ce qui est la plus grande partie du web moderne, votre vie est courte et heureuse. base64enc embarque même une porte de garde pour ça :

packed <- base64encode(charToRaw("caf\u00e9"))
bytes <- base64decode(packed)
checkUTF8(bytes, quiet = TRUE)
#> [1] TRUE
rawToChar(bytes)
#> [1] "café"

checkUTF8() demande « ces octets peuvent-ils se lire en UTF-8 ? » sans s'engager. Avec quiet = TRUE il renvoie TRUE ou FALSE ; par défaut il est encore plus direct et lève une erreur sur les octets invalides en désignant le fautif, comme dans INVALID byte 0xff at 0x0 (0, line 1): invalid start byte (FE/FF). Il signale aussi FALSE pour les chaînes qui contiennent des octets NUL, qui ne peuvent jamais faire partie d'une chaîne R UTF-8 valide. Utilisez-le comme porte de garde, et le reste suit.

Voici pourquoi la porte de garde compte. Dans une locale UTF-8, rawToChar() ne lève pas d'erreur quand les octets ne sont pas un UTF-8 valide. Il vous remet une chaîne que R marque avec Encoding() == "unknown", et selon la locale et la build de R, le même appel peut au contraire mourir d'une erreur « invalid multibyte sequence », ce qui est au moins honnête. Dans les deux cas, un rawToChar() sans garde sur des octets étranges est la façon dont le mojibake voit le jour :

broken <- as.raw(c(0xc3, 0x28))   # une paire UTF-8 tronquée
checkUTF8(broken, quiet = TRUE)
#> [1] FALSE
rawToChar(broken)                  # pas d'erreur. c'est ça le problème.
#> [1] "\xc3("

Et quand l'expéditeur a utilisé tout autre chose, du Latin-1 ou du Windows-1252 ou du Shift-JIS, la recette est de décoder vers le brut puis de traduire avec iconv(), qui comprend les charsets nommés :

latin1_bytes <- as.raw(c(0x63, 0x61, 0x66, 0xe9))   # "café" en Latin-1
iconv(rawToChar(latin1_bytes), from = "latin1", to = "UTF-8")
#> [1] "café"

Les emoji méritent un mot, parce que R a une histoire ici. Le R moderne stocke les points de code au-dessus de U+FFFF, où vivent les emoji, en vrai UTF-8, donc l'aller-retour fonctionne et le nombre d'octets correspond à ce que chaque autre langage attend. Les versions de R plus anciennes stockaient ces mêmes caractères en paires de supplétifs, un schéma souvent appelé CESU-8, donc un seul emoji franchissait la frontière en six octets d'UTF-8 invalide. Si vous héritez d'ancien code R dont les emoji arrivent cassés sur le fil, c'est le premier suspect : vérifiez nchar(x, type = "bytes") et comparez avec ce que le langage de l'expéditeur produirait.

emoji <- "\U0001F600"
nchar(emoji, type = "bytes")
#> [1] 4
round_trip <- base64decode(base64encode(charToRaw(emoji)))
identical(emoji, rawToChar(round_trip))
#> [1] TRUE

La règle simple : supposez de l'UTF-8, vérifiez avec checkUTF8(), et n'allez vers iconv() que si vous avez le savoir positif d'un autre charset. Ne devinez jamais.

Fichiers : du texte enveloppé aux vrais octets

Les chaînes, c'est facile ; les fichiers, c'est là que le Base64 gagne sa vie. Commencez par le pipeline manuel qui marche avec n'importe quel paquet : lisez le texte encodé, décodez, écrivez les octets.

cat("SGVsbG8sIHdvcmxkIQ==", file = "hello.b64")
bytes <- base64decode(paste(readLines("hello.b64"), collapse = ""))
writeBin(bytes, "hello.txt")
readLines("hello.txt")
#> [1] "Hello, world!"

Puis la couche de confort. base64enc peut lire et écrire pour vous : l'argument file pointe vers un fichier qui contient du texte Base64, et output pointe vers l'endroit où les octets décodés doivent atterrir. La valeur renvoyée est le nombre d'octets écrits, une vérification de santé gratuite et délicieuse :

n <- base64decode(file = "hello.b64", output = "hello.txt")
n
#> [1] 13
readLines("hello.txt")
#> [1] "Hello, world!"

L'entrée enveloppée, de celle qui a survécu à l'email ou à l'armure PEM, ne pose aucun problème aux décodeurs tolérants : ils regardent droit à travers les retours à la ligne où qu'ils tombent, tant que ce qui reste est du Base64 valide. Le MIME enveloppe à 76 caractères avec CRLF entre les lignes ; les blocs PEM enveloppent à 64. Le moteur strict, bien sûr, rejette l'enveloppement d'office.

wrapped <- paste0("VGhlIHF1aWNrIGJ", "\r\n",
  "yb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==")
rawToChar(base64decode(wrapped))
#> [1] "The quick brown fox jumps over the lazy dog"
rawToChar(openssl::base64_decode(wrapped))
#> [1] "The quick brown fox jumps over the lazy dog"
b64::decode(wrapped)
#> Error: Invalid byte 13, offset 15.

Le paquet b64 a le duo fichier aux noms les plus clairs, et comme il est rapide comme du Rust c'est lui qu'il faut choisir quand le fichier est gros. Il y a tout de même un tranchant : decode_file() lit le fichier octet par octet et ne pardonne pas. Un retour à la ligne final - du genre que writeLines() ajoute mais que cat() n'ajoute jamais - fait paniquer le moteur Rust, qui remonte en une erreur attrapable de la forme User function panicked: decode_file_. Écrivez le texte encodé avec cat() ou writeBin(charToRaw(enc), path) et le tranchant reste émoussé.

writeBin(charToRaw("file payload bytes"), "payload.bin")
enc <- b64::encode_file("payload.bin")
cat(enc, file = "payload.b64")
bytes <- b64::decode_file("payload.b64")
rawToChar(bytes)
#> [1] "file payload bytes"

Le paquet base64 est le troisième chemin d'entrée : purement orienté fichiers, avec un duo de fonctions qui vont ensemble :

base64::encode("payload.bin", "payload.b64")
base64::decode("payload.b64", "payload.out")
readLines("payload.out")
#> [1] "file payload bytes"

Une forme de fichier de plus que vous croiserez en pratique : une colonne de data frame pleine de blobs encodés, très courant dans les dumps d'API. Pour quelques centaines de lignes, un simple appel vectorisé suffit amplement :

df <- data.frame(content = c(b64::encode("alpha"), b64::encode("beta"),
  b64::encode("gamma")))
decoded <- b64::decode(df$content)
df$decoded <- vapply(decoded, function(x) rawToChar(x), character(1))
df$decoded
#> [1] "alpha" "beta"  "gamma"

API et réponses web

Les API JSON adorent glisser du binaire dans du texte, et le Base64 est leur malle préférée. Le schéma en R est le même à chaque fois : récupérer, parser, décoder, et décider ce que sont les octets. Le client HTTP moderne est httr2, et le côté JSON est jsonlite :

library(httr2)
library(jsonlite)
res <- req_perform(request("https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt"))
data <- fromJSON(rawToChar(res$body))
bytes <- base64decode(data$args$attachment)
writeBin(bytes, data$args$name)
length(bytes)
#> [1] 11

Trois notes pour la route. Le constructeur de requête est request(), et il porte ce nom depuis la toute première version de httr2. Le corps de la réponse arrive sous forme de vecteur brut, donc rawToChar() avant de parser en JSON. Et l'API peut parler un dialecte : vérifiez si son Base64 est URL-safe ou a été dépouillé de son padding, avant de l'offrir à un décodeur qui attend la version des manuels. Certaines API font même un double encodage, du Base64 de Base64, ce que vous reconnaîtrez quand un seul décodage vous redonne du Base64 au lieu des octets attendus.

URI data et documents autonomes

Le schéma d'URI data: (RFC 2397) permet à un document de porter son propre contenu : un type MIME, le mot « base64 » quand le payload est encodé, et le payload lui-même. Vous croiserez ça sans cesse dans le HTML que vous scrappez, et en décoder un, c'est une opération sur chaîne en deux temps suivie d'un décodage ordinaire :

img_tag <- "<img src=\"data:image/png;base64,iVBORw0KGgoAAA\" />"
uri <- regmatches(img_tag, regexec("src=\"([^\"]*)\"", img_tag))[[1]][2]
uri
#> [1] "data:image/png;base64,iVBORw0KGgoAAA"
payload <- sub("^data:[^,]*,", "", uri)
bytes <- base64decode(payload)
length(bytes)
#> [1] 10

La même forme apparaît dans le CSS, dans les annotations PDF, et dans les rapports R Markdown autonomes, où un graphique a été aplati en balise <img> pour que le HTML voyage sans fichiers à côté. Si vous rendez ce genre de documents et voulez extraire les images embarquées, voilà toute la recette : trouvez la chaîne data:, coupez à la première virgule, décodez.

Bases de données, e-mails et configuration

Le Base64 apparaît dans les bases de données chaque fois que quelqu'un a voulu du binaire dans une colonne texte, et le côté décodage est une colonne de chaînes ramenée vers des octets. Voici l'aller-retour contre SQLite via DBI et RSQLite : stockez la valeur encodée, interrogez-la, décodez-la, et vous avez vos octets :

library(DBI)
library(RSQLite)
db <- dbConnect(SQLite(), ":memory:")
dbExecute(db, "CREATE TABLE files (name TEXT, payload TEXT)")
stored <- base64encode(charToRaw("stored in a database"))
dbExecute(db, paste0("INSERT INTO files VALUES ('note.txt', '", stored, "')"))
row <- dbGetQuery(db, "SELECT * FROM files")
rawToChar(base64decode(row$payload))
#> [1] "stored in a database"

SQLite peut aussi stocker du binaire nativement en BLOB, auquel cas aucun Base64 n'est nécessaire du tout et la colonne revient en R comme vecteur brut, comme dans class(dbGetQuery(db, "SELECT payload FROM blobs")$payload[[1]]) == "raw". La variante Base64-dans-TEXT existe pour la portabilité : vous pouvez l'inspecter avec un éditeur de texte, et chaque autre langage peut la lire sans pilote binaire.

L'e-mail est l'endroit où le Base64 enveloppé est né. Les parties MIME sont enveloppées à 76 caractères avec CRLF entre les lignes, et tout système de messagerie que vous ayez jamais lu a transporté des pièces jointes exactement comme ça. R n'a pas de client de messagerie de premier ordre, mais quand vous recevez quand même un fichier .eml, la partie base64 d'une pièce jointe n'est que du texte enveloppé : les décodeurs tolérants le déplieront pour vous en le décodant. Le paquet mime vous aide à identifier ce que vous tenez :

mime::guess_type("photo.jpg")
#> [1] "image/jpeg"
part <- "SGVsbG8s\r\nCnRoaXMgaXMgYW4g\r\nZW1haWwgYXR0YWNobWVudA=="
rawToChar(base64decode(part))
#> [1] "Hello,\nthis is an email attachment"

Les fichiers de configuration terminent la visite. Quand un certificat ou un blob est stocké encodé en Base64 dans une config YAML ou JSON, ou dans une variable d'environnement, R le lit comme une chaîne ordinaire et vous décodez à la demande :

config_line <- "api_cert: TFRTU0VDUkVU"
cert_b64 <- sub("^api_cert: ", "", config_line)
raws <- base64decode(cert_b64)
rawToChar(raws)
#> [1] "LTSSECRET"

Les gros payloads

Les chaînes R ont un plafond dur de 2^31 - 1 octets, et une chaîne Base64 assez grosse peut y buter, parce que la forme encodée est environ un tiers plus grosse que l'original. Le paquet base64enc gère ça depuis sa sortie de 2022 : donnez une largeur de ligne à base64encode() et il vous rend un vecteur de lignes au lieu d'une chaîne énorme, et côté décodage l'argument file = lit le fichier ligne par ligne et décode sans vous forcer à tenir une chaîne géante dans une variable.

Pour des fichiers de plusieurs centaines de mégaoctets, le schéma pratique est de lire par morceaux alignés. Les groupes Base64 sont indépendants, donc n'importe quel morceau dont la longueur est un multiple de quatre caractères se décode tout seul ; il ne vous faut qu'un petit tampon pour porter la fin biscornue :

chunk <- 65536L
con <- file("huge.b64", "rb")
on.exit(close(con))
leftover <- ""
out <- raw(0)
while (TRUE) {
  piece <- readChar(con, chunk, useBytes = TRUE)
  if (length(piece) == 0) break
  piece <- gsub("[\r\n]", "", piece)
  ready <- paste0(leftover, piece)
  take <- floor(nchar(ready) / 4) * 4
  if (take > 0) {
    out <- c(out, base64decode(substr(ready, 1, take)))
    leftover <- substr(ready, take + 1, nchar(ready))
  } else {
    leftover <- ready
  }
}
if (nchar(leftover) > 0) out <- c(out, base64decode(leftover))
length(out)

Dans ce territoire, le paquet b64 est le champion de la vitesse : son moteur Rust décode un fichier de 50 mégaoctets en une fraction de seconde, et son decode() vectorisé traite une colonne pleine de valeurs encodées en un seul appel, ce qui est une différence dramatique par rapport à une boucle ligne par ligne. Si vous avez beaucoup de chaînes, faites vous-même une petite comparaison avec system.time() ; l'écart entre une boucle par ligne et un seul appel vectorisé est généralement assez grand pour compter.

La ligne de commande

Tout n'a pas besoin d'une session R complète. Les outils Unix classiques parlent Base64 nativement, et R peut leur confier du travail ou en recevoir d'eux. Sur Linux, base64 -d décode (sur macOS et les autres systèmes BSD c'est -D) :

echo "SGVsbG8sIHdvcmxkIQ==" | base64 -d
#> Hello, world!

Et un Rscript d'une ligne fait le même travail avec les mêmes paquets que ceux que vous utilisez dans vos scripts :

Rscript -e 'library(base64enc); writeLines(rawToChar(base64decode("TWFu")))'
#> Man

Utilisez le shell pour les vérifications rapides et les tuyaux ; utilisez R quand le résultat doit vivre dans un data frame, un fichier ou un rapport. Une précaution : les arguments de ligne de commande ont une limite de taille (ARG_MAX), alors ne collez pas des chaînes de plusieurs mégaoctets dans le terminal. Faites-les passer par un fichier à la place.

Les pièges qui valent le coup d'être connus

Voici la liste courte des façons dont ça mord les développeurs R, toutes natives de l'écosystème plutôt que du Base64 en général :

  • openssl échoue en silence. base64_decode() renvoie un vecteur brut vide, sans erreur ni avertissement, quand il rencontre un caractère hors alphabet ou des ordures en queue. Vérifiez toujours length() du résultat avant de le croire.
  • NA n'est pas une valeur manquante pour un décodeur. NA_character_ est forcé à la chaîne "NA", qui se décode en l'octet 0x34. Filtrez les NA avant de décoder.
  • Les vecteurs ne se comportent pas comme vous le pensez. base64decode() concatène son entrée en un seul vecteur brut ; openssl::base64_decode() meurt avec the condition has length > 1 sur une entrée à plusieurs éléments ; seul b64::decode() est vraiment vectorisé.
  • rawToChar est un corrompeur silencieux. Les octets UTF-8 invalides ne lèvent pas d'erreur dans une locale UTF-8 ; ils deviennent une chaîne marquée "unknown". Mettez une porte de garde avec checkUTF8() d'abord.
  • Le point dans un JWT est un joker regex. strsplit(jwt, ".") sans fixed = TRUE découpe sur chaque caractère. C'est le bug Base64 le plus courant dans le code R.
  • Les fichiers b64 ne pardonnent pas. Un retour à la ligne final dans le fichier encodé fait paniquer b64::decode_file(). Écrivez avec cat(), pas writeLines().
  • Tolérant ne veut pas dire assez tolérant pour la confiance. Le mode par défaut de base64decode() saute les mauvais caractères n'importe où, donc une chaîne corrompue peut se décoder en de l'ordure qui a l'air plausible, sans erreur. Utilisez strict = TRUE aux frontières de confiance.
  • Les messages d'erreur de b64 parlent Rust. Attendez-vous à des phrases comme Both cases of Either errored quand vous lui passez un type qu'il ne veut pas. Le message est inutile à dessein ; c'est un système de types Rust qui hausse les épaules.

Bonnes pratiques

  • Partez de base64enc::base64decode() par défaut pour le travail de tous les jours, et activez strict = TRUE partout où l'entrée franchit une frontière de confiance : API non fiables, téléversements d'utilisateurs, tout ce qui est signé.
  • Allez vers b64 quand il vous faut de la vitesse, une vraie vectorisation ou les moteurs URL-safe et sans padding, et acceptez qu'il est strict par conception.
  • Si openssl est déjà dans votre projet, utilisez-le, mais traitez chaque résultat comme suspect tant que vous n'avez pas vérifié sa longueur.
  • Décidez le charset délibérément : supposez de l'UTF-8, vérifiez avec checkUTF8(), et n'utilisez iconv() que si l'expéditeur vous a dit le contraire.
  • Pour les JWT, jetez un œil avec strsplit() plus fixed = TRUE, mais ne faites confiance qu'après que jose a vérifié la signature.
  • Gardez TWFu dans vos tests : c'est trois octets de test de fumée pour n'importe quel chemin de décodage que vous écrivez.
  • Souvenez-vous de ce que le Base64 n'est pas : ce n'est pas un chiffrement, et ce n'est pas une compression. C'est du ruban adhésif, et n'importe qui avec cet article peut renverser tout ce qu'il fait. Décodez à volonté, faites confiance avec sélection.

Une brève histoire du Base64 dans R

Le format est vieux. Il a été standardisé pour le protocole Privacy-Enhanced Mail en 1987 (RFC 989), adopté par le MIME en 1993 (RFC 1521, puis le RFC 2045 final en 1996), remis en forme dans le RFC 3548 en 2003, et a reçu sa forme moderne, alphabet URL-safe compris, dans le RFC 4648 en 2006. L'histoire R est bien plus courte et bien plus vive. Le paquet base64enc, par Simon Urbanek, est arrivé sur CRAN en septembre 2012 et a été le cheval de trait depuis, gagnant checkUTF8() en 2015 et le support des longs vecteurs en 2022. En octobre 2024, le vieux paquet base64 a été réédité explicitement comme enveloppe de compatibilité, sa propre description renvoyant désormais les nouvelles applications vers base64enc, openssl ou jsonlite. Puis est arrivé b64 début 2024 (sortie actuelle 0.1.7, juillet 2025), un moteur Rust construit avec extendr qui a apporté la vraie vectorisation et un régiment d'alphabets. En février 2026, base64enc a sorti son mode strict attendu depuis longtemps, et en avril 2026 le paquet jose s'est redessiné autour des fonctions jwt_*. Dix ans pour ce que d'autres langages livraient dans une seule bibliothèque, mais le résultat est une boîte à outils où chaque décodeur a un métier clair et une personnalité claire.

Faits amusants

Parce qu'un guide complet doit finir sur un sourire :

  • base64decode(NA_character_) renvoie la lettre "4", parce que NA_character_ voyage jusqu'au décodeur sous forme de la chaîne "NA", et "NA" est un groupe Base64 valide. La surprise d'un octet la plus spécifique à R de tout le langage.
  • Offrez à openssl::base64_decode() une chaîne avec un seul caractère illégal et il renvoie raw(0) dans un silence total. Le silence le plus dangereux de l'écosystème.
  • Chaque appel strict de base64decode() marmonne une ligne de statut C de la forme v=30000, pad=0, org='u'. Ce n'est pas un avertissement. C'est le code C qui se racle la gorge.
  • b64 peut décoder des alphabets que vous n'avez jamais croisés : BinHex, l'UTF-7 modifié d'IMAP, et les alphabets sur mesure de bcrypt et crypt. R peut lire une pièce jointe de Macintosh des années 1980 et un hachage de mot de passe moderne avec le même moteur.
  • Les chaînes R s'arrêtent à 2^31 - 1 octets, c'est pourquoi base64enc a appris à renvoyer un vecteur de lignes pour les entrées longues. Le format a buté contre le mur du langage, et le paquet a poussé une échelle.
  • Le mot « base64 » s'encode en YmFzZTY0. Un format qui sait se décrire est l'équivalent technique d'un miroir qui parle en morse.
  • Un seul octet NUL coûte toujours quatre caractères : "AA==". En Base64, le rien est toujours quelque chose.
  • Votre build de R a un surnom. R 4.5.0 s'appelle « How About a Twenty-Six », parce que les versions de R sont nommées d'après des bandes dessinées Peanuts et des films, et les bandes dont elles s'inspirent ont des décennies de plus que les sorties qu'elles nomment. Même la version avec laquelle vous décodez a de l'humour.

En résumé

Choisissez votre décodeur selon la compagnie que vous fréquentez : base64enc pour le travail de tous les jours, avec strict = TRUE aux bords ; openssl s'il est déjà dans votre projet, à condition de vérifier vos résultats ; b64 quand vous voulez de la vitesse, des vecteurs et les moteurs URL-safe ; et les petits spécialistes base64 et base64url pour les corvées de fichiers et les chaînes URL-safe. Gardez vos octets sous checkUTF8() avant de les appeler du texte, utilisez iconv() quand le charset est connu pour être autre chose, et souvenez-vous que le vecteur brut au milieu est une fonctionnalité : il vous force à décider ce que les octets veulent dire au lieu de laisser une bibliothèque deviner. Décodez tout, ne faites confiance qu'à ce qui se vérifie. Et quand il faut aller dans l'autre direction, emballer vos propres octets dans une chaîne pour la route au lieu de déballer une chaîne, l'article sœur couvre l'encodage Base64 en R en détail.

Dernière mise à jour : 2026-09-08

Article associé : Encodage Base64 en R : un guide complet