Base64-Kodierung in R: Ein vollständiger Leitfaden
Sie haben Bytes, die reisen müssen, und die Straße erlaubt nur Text. Ein JPEG, das in einem JSON-Feld leben muss. Ein Zertifikat, das in einer Umgebungsvariablen sitzen muss. Ein Plot, der in einem eigenständigen HTML-Bericht reisen muss. Base64 ist das Paketband für all das: Jede Folge von Bytes wird zu einem String aus 64 harmlosen Zeichen, der jeden Textkanal übersteht, den Sie ihm zumuten. Die Startseite dieser Site behandelt das Format in voller Breite, also hier die Kurzversion: Drei Bytes gehen rein, vier Zeichen kommen raus, gezogen aus A bis Z, a bis z, 0 bis 9, dazu + und /, und am Ende ein bisschen =-Padding, wenn die Ladung sich nicht aufteilt.
Der R-spezifische Twist: base R liefert gar keinen Base64-Kodierer mit. Es gibt kein base64_encode(), das in einem Base-Paket wartet, und keinen Einzeiler-Builtin, nach dem Sie greifen könnten. Sie wählen ein Paket, und das Ökosystem gibt Ihnen wirklich die Wahl, mit unterschiedlichen Geschwindigkeiten, unterschiedlichen Umbrechen-Gewohnheiten und unterschiedlichen Meinungen zum Padding. Am Ende dieses Artikels wissen Sie, nach welchem Kodierer Sie in jeder Situation greifen - und welcher von ihnen still und heimlich etwas anderes tut als kodieren.
Die Kodierer-Landschaft
Fünf Pakete erledigen die Kodierung, und sie teilen sich in die Alltags-Arbeitstiere, die krypto-nahen und die kleinen Spezialisten. Hier ist die Besetzung, Stand 2026:
| Paket | Version (2026) | Kodier-Einstiegspunkte | Umbrechen-Gewohnheiten | Nach ihm greifen, wenn |
|---|---|---|---|---|
base64enc |
0.1-6 | base64encode() |
linewidth und newline, ganz nach Ihrem Geschmack |
Alltags-Strings, MIME-Umbrechen |
openssl |
2.4.2 | base64_encode() |
Zeilen mit 64 Zeichen, LF-Brüche, Zeilenumbruch am Ende | PEM-Dateien, bestehende Krypto-Stacks |
b64 |
0.1.7 | encode(), encode_file() |
bricht nie um; b64_chunk() und b64_wrap() auf Abruf |
Geschwindigkeit, Vektoren, URL-sichere Engines |
base64 |
2.0.2 | encode() |
standardmäßig Zeilen mit 64 Zeichen plus Zeilenumbruch am Ende | Datei-zu-Datei-Kleinkram, Berichtsbilder |
base64url |
1.4 | base64_urlencode() |
bricht nie um, kein Padding, Zeichen rein | URL-sichere Strings |
Drei weitere Kodierer verstecken sich in Paketen, die Sie womöglich bereits laden. jsonlite exportiert base64_enc() und base64url_enc(), also haben Sie womöglich schon einen Kodierer zur Hand, wenn Sie ohnehin JSON parsen. jose exportiert base64url_encode() für JWT-Arbeiten. Und das Veteranen-Paket RCurl trägt immer noch einen base64()-Wrapper um libcurl, der gut funktioniert und in eine frühere Ära gehört. Das Paket base64 schließlich beschreibt sich auf der Dose inzwischen selbst als Kompatibilitäts-Wrapper und verweist neue Anwendungen auf base64enc, openssl oder jsonlite.
Die Einrichtung
Falls R noch nicht auf dem Rechner ist, liefert Ihr Betriebssystem es mit: r-base auf Debian und Ubuntu, R auf Fedora, Homebrew oder der offizielle Installer auf macOS, ein Installer auf Windows. Dann die Kodierer, direkt von CRAN:
install.packages("base64enc")
install.packages("openssl")
install.packages("b64")
install.packages("base64url")
Die beiden Stellen, an denen Installationen schiefgehen, liegen beide in der Build-Phase. openssl kompiliert gegen Ihr System-OpenSSL, also will ein karg bestückter Linux-Rechner zuerst die Entwicklung-Header (sudo apt install libssl-dev), oder Sie überspringen die Kompilierung auf Debian und Ubuntu ganz mit sudo apt install r-cran-openssl. Und b64 ist ein Rust-Motor, der mit extendr verpackt ist, also verlangt ein Quell-Code-Build die Rust-Toolchain (sudo apt install cargo zieht auch rustc mit). Windows und macOS bekommen vorgebaute Binaries von CRAN, und nichts davon betrifft Sie.
Die erste Kodierung
Neunzig Prozent des Kodier-Alltags passen in drei Zeilen, mit demselben berühmten String, den die Dekodier-Seite als Smoke-Test benutzt:
library(base64enc)
packed <- base64encode(charToRaw("Man"))
packed
#> [1] "TWFu"
identical(packed, "TWFu")
#> [1] TRUE
Drei Dinge fallen in dieser Zeremonie auf. Erstens ist die Eingabe ein raw-Vektor: charToRaw() ist die Brücke von Ihrem R-String zu den Bytes, die verpackt werden, und die Alltags-Kodierer aus dieser Tabelle - base64enc, openssl, b64 - nehmen alle raw. Zweitens ist die Ausgabe die Gegenrichtung zu den Dekodierern: ein einzelner Zeichen-String, denn Kodieren endet auf der Text-Seite der Grenze. Drittens: beobachten Sie die Längen-Rechnung in Aktion: drei Bytes rein, vier Zeichen raus, kein Padding nötig, weil die Ladung sich aufteilt. Wenn sich die Ladung nicht aufteilt, landen ein oder zwei =-Zeichen am Ende.
Und weil ein Kodierer, dem Sie nicht trauen, schlechter ist als keiner, hier der Round-Trip, der beweist, dass sich beide Richtungen einig sind:
text <- "Hello, world!"
packed <- base64encode(charToRaw(text))
packed
#> [1] "SGVsbG8sIHdvcmxkIQ=="
identical(text, rawToChar(base64decode(packed)))
#> [1] TRUE
Strings, Bytes und die Dateinamen-Falle
Und nun die Falle, denn jeder R-Entwickler fällt einmal hinein. base64encode() behandelt ein character-Argument als Dateinamen, nicht als Text, der kodiert werden soll. Geben Sie ihm einen String, und er geht auf die Suche nach genau dieser Datei:
base64encode("Man")
#> Warning in file(what, "rb") :
#> cannot open file 'Man': No such file or directory
#> Error: cannot open the connection
base64encode(charToRaw("Man"))
#> [1] "TWFu"
Die Warnung ist das Erkennungszeichen: Sie hat file("Man", "rb") probiert, was "eine Datei namens Man zum rohen Lesen öffnen" heißt. Also gilt bei base64encode() eine Disziplin, ein Reflex: zuerst charToRaw(), immer. Wenn Sie tatsächlich eine Datei meinen, ist das genau das, was die Funktion tut, und die Ausgabe sind die Bytes der Datei, verpackt - was manchmal genau das ist, was Sie wollen.
b64 vertritt zu derselben Frage eine andere Position: Ihr encode() nimmt einen Zeichen-Vektor direkt an, behandelt jedes Element als UTF-8-Text und ist obendrein vektorisiert:
b64::encode("Man")
#> [1] "TWFu"
b64::encode(c("Man", "M"))
#> [1] "TWFu" "TQ=="
Zwei weitere Kanten dieser Grenze sollte man kennen. Die leere Eingabe wird auf drei verschiedene Arten kodiert, je nachdem, wen Sie fragen:
base64encode(raw(0))
#> character(0)
b64::encode("")
#> [1] ""
base64url::base64_urlencode("")
#> [1] ""
base64enc antwortet mit einem Zeichen-Vektor der Länge null, nicht mit einem leeren String, sodass nachgelagerter Code, der einen String erwartet und character(0) bekommt, an überraschenden Stellen scheitert. Und die Zeichensatz-Entscheidung lebt auch auf der Kodier-Seite: charToRaw() packt den String in dem Zeichensatz, den er gerade trägt, also reist UTF-8-Text als UTF-8-Bytes, was die andere Seite der Leitung erwartet. Emoji inklusive, denn modernes R speichert Codepunkte über U+FFFF als echtes UTF-8:
emoji <- "\U0001F600"
nchar(emoji, type = "bytes")
#> [1] 4
round_trip <- base64decode(base64encode(charToRaw(emoji)))
identical(emoji, rawToChar(round_trip))
#> [1] TRUE
Zeilenumbrüche: MIME, PEM und Ihre eigene Breite
Lange Base64-Strings werden in Zeilen zerbrochen, denn die ältesten Textkanäle der Welt hatten Spaltenlimits, und MIME hat sich nie darum gekümmert, sie zu vergessen. Die beiden historischen Umbrüche, die Sie treffen werden, sind MIME, der bei 76 Zeichen bricht und CRLF zwischen den Zeilen setzt, und PEM, der bei 64 bricht. Jeder Kodierer hat seine eigene Vorstellung davon, welchen er verwenden soll, falls er einen verwendet, also ist dies der Abschnitt, in dem Sie den Vertrag wählen, bevor Sie kodieren.
Zuerst die Größen-Rechnung, denn Umbrechen heißt zu wissen, wie lang die Ausgabe wird. Auf jeweils drei Bytes kommen vier Zeichen, was die Länge der kodierten Form zu einer einfachen Obergrenze macht:
nchar(base64encode(charToRaw(strrep("a", 100))))
#> [1] 136
4 * ceiling(100 / 3)
#> [1] 136
Mit diesem Wissen in der Tasche: die Kodierer. base64enc ist der flexibelste: Standardmäßig gibt es eine einzige ununterbrochene Zeile aus, und das Argument linewidth reicht Ihnen stattdessen einen Vektor aus Zeilen:
long <- charToRaw(strrep("R", 100))
base64encode(long, linewidth = 76)
#> [1] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJS"
#> [2] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUg=="
base64encode(long, linewidth = 76, newline = "\r\n")
#> [1] "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJS\r\nUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUg=="
Zwei Zeilen für 100 Bytes bei Breite 76, standardmäßig ein Vektor, ein einzelner mit CRLF zusammengefügter String, wenn Sie newline dazugeben. Es gibt kein leeres Element am Ende: 114 Bytes, die zu exakt zwei Zeilen zu je 76 kodieren, kommen als zwei Zeilen zurück, nicht als drei.
openssl nimmt den anderen Pol. linebreaks = TRUE bricht bei 64 Zeichen mit normalen LF-Brüchen und hängt noch einen Zeilenumbruch ans ganz Ende:
wrapped <- openssl::base64_encode(charToRaw(strrep("a", 100)), linebreaks = TRUE)
nchar(wrapped)
#> [1] 139 # 136 Datenzeichen plus 3 Zeilenumbrüche
nchar(strsplit(wrapped, "\n", fixed = TRUE)[[1]])
#> [1] 64 64 8
Zählen Sie die Rechnung nach: 136 Datenzeichen, zwei innere Brüche, ein Bruch am Ende, insgesamt 139. Und dieser Bruch am Ende ist für die natürlichste Zählweise der Zeilen unsichtbar, die Sie schreiben können, denn strsplit() wirft ein leeres Stück am Ende weg, sodass der Vektor drei Zeilen sagt, während der String vier trägt. Wenn Sie einmal eine mit OpenSSL umbrochene Ausgabe gegen eine mit MIME umbrochene vergleichen und die Zeichenzahlen nicht aufgehen, dann ist das die gespenstische vierte Zeile.
b64 bricht überhaupt nicht um; es gibt Ihnen die beiden Operationen getrennt, und die Chunk-Breite hat eine Regel: ein Vielfaches von vier, denn die Engine weigert sich, eine Base64-Gruppe zu halbieren:
enc <- b64::encode(strrep("a", 100))
ch <- b64::b64_chunk(enc, 76)
b64::b64_wrap(ch, "\r\n")
#> [1] "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\r\nYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQ=="
b64::b64_chunk(enc, 75)
#> Error: Chunk size must be a multiple of 4.
Und das dateiorientierte Paket base64 folgt dem Beispiel von OpenSSL: Zeilen mit 64 Zeichen, Zeilenumbruch am Ende, standardmäßig an:
writeBin(charToRaw(strrep("b", 200)), "big.bin")
base64::encode("big.bin", "big.b64")
con <- file("big.b64")
lines <- readLines(con)
close(con)
nchar(lines)
#> [1] 64 64 64 64 12 0
Die letzte Null ist der Zeilenumbruch am Ende, den readLines() als leere letzte Zeile gefangen hat. Hier ist das ganze Feld auf einen Blick:
| Kodierer | Breite | Zeilenumbruch | Zeilenumbruch am Ende |
|---|---|---|---|
base64encode(x) |
eine Zeile | kein | kein |
base64encode(x, linewidth = 76, newline = "\r\n") |
76 | CRLF | kein |
openssl::base64_encode(x, linebreaks = TRUE) |
64 | LF | ja |
b64::encode(x) |
eine Zeile | kein (mit b64_chunk() und b64_wrap() umbrechen) |
kein |
base64::encode(in, out) |
64 | LF | ja |
base64url::base64_urlencode(x) |
eine Zeile | kein | kein |
URL-sicheres Base64
Standard-Base64 gibt die letzten beiden Plätze seines Alphabets an + und / aus, und genau das sind die Zeichen, die URLs nicht mögen: Plus wird zu %2B, Schrägstrich zu %2F, und jedes = des Paddings wird zu %3D. Die URL-sichere Variante, definiert in Abschnitt 5 von RFC 4648, tauscht diese beiden Zeichen gegen - und _ aus und lässt das Padding in der Regel fallen, sodass ein Token, das überall einfügbar sein soll, überall einfügbar bleibt. R hat drei Türen hinein.
Die b64-Engines sind die vollständigsten: Eine Engine ist ein konfiguriertes Alphabet und eine Padding-Strategie, und das Paket liefert die vier, die Sie brauchen. Füttern Sie dieselben drei Bytes der Standard-Engine und der URL-sicheren, und beobachten Sie, wie das Alphabet seine Arbeit macht:
bytes <- as.raw(c(0xfb, 0xef, 0xbe))
b64::encode(bytes)
#> [1] "++++"
b64::encode(bytes, engine("url_safe"))
#> [1] "----"
b64::encode(as.raw(0x4d), engine("url_safe"))
#> [1] "TQ=="
b64::encode(as.raw(0x4d), engine("url_safe_no_pad"))
#> [1] "TQ"
Vier Engines, "standard", "standard_no_pad", "url_safe" und "url_safe_no_pad", und dasselbe Engine-Objekt funktioniert in beide Richtungen, was Ihren Code symmetrisch hält. Die no-padding-Varianten unterscheiden sich nur dann, wenn Padding tatsächlich erscheinen würde, wie im Ein-Byte-Beispiel oben.
Das dedizierte Paket base64url ist die zweckgebundene Tür: Zeichen rein, URL-sicherer String raus, bricht nie um, padet nie:
base64url::base64_urlencode("hello world")
#> [1] "aGVsbG8gd29ybGQ"
Und jose exportiert sein eigenes base64url_encode() für JWT-Arbeiten und gibt auf der Dekodier-Seite raw-Vektoren zurück, wie Sie es sich wünschen. Eine Regel verbindet alle drei Türen: Das Alphabet, mit dem Sie kodieren, ist das Alphabet, mit dem Sie dekodieren müssen. Reichen Sie einem Standard-Dekodierer den String "----", und er scheitert am ersten Gedankenstrich; der Schwestern-Artikel über das Dekodieren behandelt, wie jeder Dekodierer auf schmutzige oder nicht passende Eingabe trifft.
JWTs: Das Token bauen
Das JSON Web Token ist der Ort, an dem URL-sicheres Base64 zum Alltagsbegleiter wurde. Ein JWT besteht aus drei base64url-Teilen, verbunden durch Punkte: ein Header, der beschreibt, wie es signiert wurde, ein Payload aus JSON-Claims und eine Signatur, die beide verbindet. Auf der Kodier-Seite bauen Sie alle drei, und das Paket jose erledigt die ganze Zeremonie. Seine API ist um die jwt_*-Funktionen aufgebaut (jwt_claim(), jwt_encode_hmac(), jwt_decode_hmac(), jwt_split()); der 2.0-Release (April 2026) fügt ED25519-Unterstützung hinzu und macht das typ-Header-Feld optional:
library(jose)
claim <- jwt_claim(iss = "app", sub = "1234567890", exp = Sys.time() + 3600)
token <- jwt_encode_hmac(claim, "0123456789abcdef")
sp <- jwt_split(token)
sp$header
#> $typ
#> [1] "JWT"
#>
#> $alg
#> [1] "HS256"
names(sp$payload)
#> [1] "iss" "sub" "exp" "iat"
Beachten Sie, was jwt_claim() im Hintergrund getan hat: iat (issued at, Ausstellungszeitpunkt) nimmt als Voreinstellung die aktuelle Zeit, also tauchte es im Payload auf, ohne danach gefragt zu werden, während exp gar nichts als Voreinstellung hat, was ein Token ohne Ablaufdatum bedeutet, was normalerweise nicht das ist, was Sie wollen. Setzen Sie exp bewusst, und die Signatur kümmert sich um den Rest. jwt_split() ist das Inspektions-Tool: der Header, das Payload als benannte Liste und die rohe Signatur, ohne jede Verifizierung, genau der Hineinschauen-Schritt, den die Dekodier-Seite dieses Artikel-Paars beschreibt.
Die Verifizierungs-Seite ist es, wo jose seinen Lohn verdient. jwt_decode_hmac() prüft die Signatur und durchsetzt die Zeit-Claims, und sein Verweigerungs-Stil ist spezifisch:
round <- jwt_decode_hmac(token, "0123456789abcdef")
round$sub
#> [1] "1234567890"
jwt_decode_hmac(token, "ffffffffffffffff")
#> Error: HMAC signature verification failed!
old <- jwt_claim(sub = "1234567890", exp = Sys.time() - 3600)
expired <- jwt_encode_hmac(old, "0123456789abcdef")
jwt_decode_hmac(expired, "0123456789abcdef")
#> Error: Token has expired on 2026-08-30 05:09:36
Bei Erfolg bekommen Sie die Claims zurück als gewöhnliche Liste, also funktionieren round$sub und Kollegen einfach. Ein zukünftiger nbf (not before, nicht gültig vor) -Claim verdient seine eigene Verweigerung, Token is not valid before ..., und ein HMAC-Token wird nicht vom asymmetrischen jwt_decode_sig() dekodiert, das mit Unsupported algorithm: HMAC antwortet und stattdessen einen öffentlichen Schlüssel will. Zwei letzte Hinweise. Erstens: Das Payload ist kodiert, nicht verschlüsselt: Jeder kann jeden Claim lesen, also gehört nichts Geheimnisvolles hinein. Zweitens: Wenn Sie httr2 nach jose laden, achten Sie auf die Maskier-Meldung: httr2 exportiert seine eigenen jwt_claim(), jwt_encode_hmac() und jwt_encode_sig(), gebaut für OAuth-Client-Credentials, bei denen exp standardmäßig auf fünf Minuten in der Zukunft liegt, und sie verdecken die jose-Versionen für den Rest der Session.
Dateien: Binär hinein, Text heraus
Eine Datei zu kodieren ist der Spiegel der Datei-Arbeit auf der Dekodier-Seite, und das Paket b64 hat den klarsten Einstieg, der auch der schnellste ist, denn es baut nie einen riesigen Zwischen-String:
writeBin(charToRaw("file payload bytes"), "payload.bin")
enc <- b64::encode_file("payload.bin")
enc
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz"
cat(enc, file = "payload.b64")
readLines("payload.b64")
#> Warning in readLines("payload.b64") :
#> incomplete final line found on 'payload.b64'
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz"
Die Warnung ist ein Feature im Verborgenen: cat() fügt keinen Zeilenumbruch am Ende hinzu, also endet die Datei mitten in der Zeile, und readLines() sagt Ihnen das. Merken Sie sich, auf welcher Seite dieses Sachverhalts Sie stehen, wenn die Datei von b64::decode_file() konsumiert wird, denn der panikert bei einem Zeilenumbruch am Ende; der Dekodier-Artikel hat die ganze Geschichte. Schreiben Sie mit cat() oder writeBin(charToRaw(enc), path), und die Kante bleibt stumpf.
Das Paket base64 ist die reine Datei-zu-Datei-Option, mit dem passenden Funktionspaar und OpenSSL-artigem Umbrechen als Voreinstellung:
base64::encode("payload.bin", "payload2.b64")
readLines("payload2.b64")
#> [1] "ZmlsZSBwYXlsb2FkIGJ5dGVz" ""
Es gibt den Ausgabe-Pfad zurück, was sich gut für Protokolle eignet. Und wenn Sie die Mechanik sehen wollen, funktioniert die manuelle Pipeline mit jedem Kodierer aus der Tabelle: die Datei als raw lesen, kodieren, den Text schreiben, fertig:
bytes <- readBin("payload.bin", what = "raw", n = file.size("payload.bin"))
enc <- base64encode(bytes)
writeLines(enc, "payload3.b64")
identical(enc, readLines("payload3.b64"))
#> [1] TRUE
Data-URIs und eigenständige Dokumente
Das data:-URI-Schema (RFC 2397) ist der sichtbarste Verbraucher auf der Kodier-Seite: ein Dokument, das seinen eigenen Inhalt mitbringt, ein MIME-Typ, das Wort "base64" und der Payload, alles in einem Attribut. Die PNG-Magic-Bytes machen das Muster wiedererkennbar: Jedes Base64-PNG in freier Wildbahn beginnt mit denselben Zeichen, denn der Dateikopf 89 50 4E 47 kodiert immer gleich:
png_head <- as.raw(c(0x89, 0x50, 0x4e, 0x47))
uri <- paste0("data:image/png;base64,", base64encode(png_head))
uri
#> [1] "data:image/png;base64,iVBORw=="
tag <- paste0("<img src=\"", uri, "\" />")
Wenn Sie schon einmal eine HTML-Datei nach iVBOR durchsucht haben, um eingebettete Bilder zu finden, dann ist das der Grund, warum der Fingerabdruck funktioniert. R-Markdown-Berichte, Single-File-Dashboards und gescrapte Seiten benutzen alle dasselbe Muster, und eines zu bauen folgt demselben Rezept: die Datei als raw lesen, kodieren und hinter dem MIME-Präfix kleben. Der Preis ist die Größen-Rechnung von früher, ein Drittel größer als das Original, für immer in Ihrem HTML, also halten Sie die eingebetteten Bilder schlank.
APIs und Web-Anfragen
Die Dekodier-Seite dieses Artikel-Paars trifft auf APIs, die Ihnen Base64 reichen; diese Seite trifft auf APIs, die es verlangen. Das Muster ist jedes Mal dasselbe: die Bytes kodieren, den String in den JSON-Körper legen, senden. Der moderne HTTP-Client ist httr2:
library(httr2)
req <- request("https://httpbin.org/post")
req <- req_body_json(req, list(file = packed, name = "man.txt"))
req
#> <httr2_request>
#> POST https://httpbin.org/post
#> Body: JSON data
res <- req_perform(req)
res$status_code
#> [1] 200
Drei Hinweise für unterwegs. Der Konstruktor für Anfragen ist request(), und er trägt diesen Namen seit dem ersten Release von httr2. Der Antwortkörper kommt, wenn Sie Antworten lesen, als raw-Vektor an, also rawToChar() vor dem Parsen. Und die API kann einen Dialekt sprechen: Manche wollen das URL-sichere Alphabet, manche wollen das Padding gestutzt, und eine JSON-API hat mit = in einem String-Wert kein Problem, also wird die Padding-Frage nur zur URL-Frage, wenn das Base64 im Pfad oder in der Query reist. Im Zweifel lesen Sie die Beispiele der API, nicht die Spezifikation in Ihrem Kopf.
Datenbanken, Konfiguration und Umgebung
Base64 in einer Datenbank ist Binäres, das durch eine Textspalte schmuggelt, und die Kodier-Seite ist das Verpacken eines Blobs, bevor er hineinkommt. Hier ist der Round-Trip gegen SQLite über DBI und RSQLite:
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"
Die Alternative ist, die Bytes nativ als BLOB zu speichern, dann ist überhaupt kein Base64 nötig, und die Spalte kommt nach R zurück als raw-Vektor. Die Base64-in-TEXT-Variante existiert wegen der Portabilität: Sie können sie in einem Texteditor prüfen, vergleichen, und jede andere Sprache kann sie ohne Binärtreiber lesen. Dasselbe Argument trägt es in die Konfiguration, wo ein Zertifikat oder ein Geheimnis als String in YAML, JSON oder einer Umgebungsvariablen gespeichert ist:
Sys.setenv("API_CERT" = base64encode(charToRaw("LTSSECRET")))
Sys.getenv("API_CERT")
#> [1] "TFRTU0VDUkVU"
Eine Warnung gehört hierher, denn in Konfigurationsdateien leben Geheimnisse: Base64 ist Kodierung, keine Verschlüsselung. Ein Base64-Wert in einer Konfigurationsdatei ist für jeden lesbar, der die Datei lesen kann. Er übersteht Transport und Texteditoren, aber er schützt nichts.
E-Mail ist der Ort, an dem der 76-Zeichen-Umbruch geboren wurde, und MIME-Anhänge tragen ihn noch immer: Base64-Inhalt, bei 76 Zeichen gebrochen, mit CRLF zwischen den Zeilen, in einem Teil, der Content-Transfer-Encoding: base64 erklärt. Der Kodierer, der genau diese Form produziert, ist base64encode() mit seinen Umbrechen-Argumenten auf den MIME-Vertrag gesetzt:
body <- "The quick brown fox jumps over the lazy dog, and then it came back down again."
mime_part <- base64encode(charToRaw(body), linewidth = 76, newline = "\r\n")
strsplit(mime_part, "\r\n", fixed = TRUE)[[1]]
#> [1] "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgYW5kIHRoZW4gaXQg"
#> [2] "Y2FtZSBiYWNrIGRvd24gYWdhaW4u"
R hat keinen erstklassigen Mail-Client, aber der Punkt steht, wann immer Sie MIME-Teile von Hand bauen oder inspizieren, .eml-Fixtures erzeugen oder Anhänge aus einem herausschälen: Das ist die Form, die das Base64 haben muss, und die Dekodier-Seite dieses Artikel-Paars zeigt, wie die nachsichtigen Dekodierer es auf dem Rückweg entrollen.
Große Payloads und das String-Limit
R-Strings haben eine harte Obergrenze von 2^31 - 1 Bytes, und weil die kodierte Form etwa ein Drittel größer ist als das Original, würde eine Datei mit ungefähr 1,5 GB rohen Daten ihre Einzeilen-Kodierung gegen die Wand drücken. Der praktische Zug ist derselbe, den base64enc seit seinem 2022er-Long-Vector-Release anbietet: die Ausgabe als Zeilen halten, nicht als einen String:
big <- raw(10 * 1024 * 1024)
packed <- base64encode(big, linewidth = 76)
length(packed)
#> [1] 183961
sum(nchar(packed))
#> [1] 13981016
Zehn Megabytes aus Nullen werden zu 183961 Zeilen mit höchstens 76 Zeichen, ein völlig gewöhnlicher Vektor, den Sie zeilenweise schreiben oder durch eine Pipe streamen können, ohne je einen riesigen String zu halten. Für den Data-Frame-Fall, eine Spalte mit vielen Binärwerten, ist b64 der Geschwindigkeits-Champion: Seine Rust-Engine kodiert die ganze Spalte in einem vektorisierten Aufruf, was ein dramatischer Unterschied zum Zeile-für-Zeile-Loopen ist. Wenn Sie eine Spalte mit kodierten Werten erzeugen müssen, führen Sie selbst einen schnellen system.time()-Vergleich durch; die Lücke zwischen einer Zeile-für-Zeile-Schleife und einem vektorisierten Aufruf ist meist groß genug, um ins Gewicht zu fallen.
Die Kommandozeile
Nicht alles braucht eine volle R-Session. Das klassische Unix-Tool spricht Base64 nativ, kodiert auf jeder Plattform ohne Flags und dekodiert auf Linux mit -d und auf macOS und den BSDs mit -D:
echo -n "Hello, world!" | base64
#> SGVsbG8sIHdvcmxkIQ==
echo -n "SGVsbG8sIHdvcmxkIQ==" | base64 -d
#> Hello, world!
Achten Sie auf das -n in der ersten Zeile: Ohne es steuert echo einen Zeilenumbruch bei, und die Ausgabe kodiert 14 Bytes statt 13 und endet in o= statt IQ==. Ein GNU-base64 bricht auch für Sie um (-w 76), was praktisch ist, wenn Sie in etwas leiten, das MIME-geformte Eingabe erwartet. Und ein einzeiliges Rscript erledigt denselben Job mit denselben Paketen, die Sie in Ihren Skripten nutzen:
Rscript -e 'library(base64enc); writeLines(base64encode(charToRaw("Man")))'
#> TWFu
Nutzen Sie die Shell für schnelle Checks und Pipes, und R, wenn das Ergebnis in einem Data Frame, einer Datei oder einem Bericht leben muss. Und fügen Sie keine Strings mit mehreren Megabytes in das Terminal ein: Kommandozeilen-Argumente stoßen lange vor Base64 an ARG_MAX, also leiten Sie über eine Datei um.
Fallen, die man kennen sollte
Hier ist die kurze Liste der Bisswunden, die R-Entwickler auf der Kodier-Seite davontragen, alle dem Ökosystem eigen und nicht Base64 insgesamt:
- Ein String ist ein Dateiname.
base64encode("Man")versucht, eine Datei namens Man zu öffnen. Die Warnung benennt die Datei; der Fehler sagt, die Verbindung sei fehlgeschlagen. Verwenden Sie zuerstcharToRaw()mitbase64encode(). - Leere Eingabe wird auf drei Arten kodiert.
base64encode(raw(0))liefertcharacter(0), währendb64::encode("")undbase64url::base64_urlencode("")""liefern. Nachgelagerter String-Code erwartet keinen Vektor der Länge null. - openssl bricht um und hängt dann eine Geister-Zeile an.
linebreaks = TRUEbricht bei 64 mit LF und hängt einen Zeilenumbruch am Ende an, denstrsplit()stillschweigend wegwirft, sodass naive Zeilenzahlen und Zeichenzahlen sich um eine Zeile nicht einig sind. - Umbrechen ist ein Vertrag. MIME ist 76 mit CRLF, PEM ist 64, JSON-APIs wollen meist gar nichts. Wählen Sie die Form, die die andere Seite erwartet, denn ein Dekodierer, der einen Umbruch-Stil toleriert, lehnt einen anderen ab.
- Das URL-sichere Alphabet muss auf beiden Enden passen. Kodieren Sie
"----"URL-sicher, scheitert es in einem Standard-Dekodierer am ersten Gedankenstrich, und Padding wird zu%3D, in dem Moment, in dem der String in einer URL lebt. - b64_chunk verlangt Vielfache von vier. Jede andere Breite verdient
Chunk size must be a multiple of 4., denn eine Base64-Gruppe kann nicht halbiert werden. - Ein Zeilenumbruch am Ende kann den Dekodierer panikieren lassen. Dateien, die Sie für
b64::decode_file()schreiben, müssen ohne Zeilenumbruch enden;cat(), nichtwriteLines(). - JWT-Zeit-Claims werden durchgesetzt.
jwt_decode_hmac()verweigert abgelaufene Tokens und zukünftigenbf-Claims, undhttr2verdeckt beijosediejwt_*-Funktionen, wenn Sie es als Zweites laden. - Das Payload ist sichtbar. Base64-Claims in einem JWT, einer Data-URI oder einer Konfigurationsdatei sind für jeden lesbar. Kodierung ist keine Verschlüsselung.
- Die String-Obergrenze ist eine Wand, keine Empfehlung. Ungefähr 1,5 GB rohe Daten pro R-String ist der Punkt, an dem eine Einzeilen-Kodierung nicht mehr passt; halten Sie große Ausgaben als Zeilen oder streamen Sie sie.
Best Practices fürs Kodieren
- Wandeln Sie mit
charToRaw()um, bevor Sie mitbase64enc::base64encode()kodieren. Wenn Sie direkte Zeichen-Eingabe und Vektorisierung brauchen, istb64::encode()das Paket, das Strings als Strings behandelt. - Gehen Sie im Alltag von
base64enc::base64encode()aus, greifen Sie nachb64, wenn Sie Geschwindigkeit, echte Vektorisierung oder die URL-sicheren Engines wollen, und nutzen Sieopenssl, wenn es ohnehin im Projekt ist und Sie PEM-geformte Ausgabe wollen. - Wählen Sie das Umbrechen pro Kanal, nicht pro Paket: nichts für JSON und URLs, 76 mit CRLF für MIME, 64 für PEM-artige Blöcke, und bleiben Sie konsistent, damit die Dekodier-Seite der Pipe weiß, was sie erwarten soll.
- Verwenden Sie das URL-sichere Alphabet ohne Padding für alles, was in einer URL, einem JWT oder einem Dateinamen leben wird, und das Standard-Alphabet für E-Mail.
- Bei JWTs setzen Sie
exp(undiat) ausdrücklich, verifizieren Sie mitjwt_decode_hmac(), bevor Sie einem Token trauen, und denken Sie daran, dass jeder Claim öffentlicher Text ist. - Testen Sie Ihre Kodier-Pfade mit einem Round-Trip,
identical(text, rawToChar(base64decode(base64encode(charToRaw(text))))); Es ist eine Zeile, und sie fängt Alphabet-, Padding- und Zeichensatz-Fehler auf einen Schlag. - Für Dateien bevorzugen Sie
b64::encode_file()oderbase64::encode()gegenüber dem Lesen der ganzen Datei in einen String, und schreiben Sie Ihre Ausgabe mitcat(), wenn ein strenger Dekodierer sie lesen wird. - Halten Sie das Paketband ehrlich: Base64 lässt Bytes reisen, aber es macht sie nicht geheim und nicht kleiner. Verschlüsseln Sie zuerst, wenn Geheimnis das Ziel ist, komprimieren Sie zuerst, wenn Größe es ist.
Wie Base64 in R kam
Das Format kam lange bevor R etwas damit anfangen konnte. Es wurde 1987 für das Privacy-Enhanced-Mail-Protokoll standardisiert (RFC 989), 1993 von MIME übernommen (RFC 1521, dann das finale RFC 2045 im Jahr 1996, das noch immer den 76-Zeichen-Umbruch definiert), 2003 in RFC 3548 aufgeräumt und 2006 in RFC 4648 seine moderne Form erhalten, die das URL-sichere Alphabet, die no-padding-Option und den kleineren Bruder Base32 hinzugefügt hat. Die R-Geschichte begann im September 2012, als Simon Urbaneks base64enc auf CRAN landete und leise zur Standardantwort auf "wie kodierte ich das hier in Base64" für über ein Jahrzehnt wurde, mit checkUTF8() 2015 und der Unterstützung langer Vektoren 2022. Die Krypto-Welt kam über openssl dazu, den langjährigen Wrapper von Jeroen Ooms um das System-OpenSSL, dessen base64_encode() seitdem die PEM-geformte Option ist. Das alte Paket base64, ebenfalls von Ooms, wurde im Oktober 2024 ausdrücklich als Kompatibilitäts-Wrapper neu herausgegeben, seine eigene Beschreibung verweist neue Anwendungen inzwischen woanders hin. Dann kam b64 im Januar 2024, eine Rust-Engine, die mit extendr gebaut wurde und Vektorisierung sowie einen Stall voller Alphabete mitbrachte, und im April 2026 veröffentlichte jose Version 2.0, die ED25519-Unterstützung zur jwt_*-API hinzugefügt und signierte Tokens zu einem Bürger ersten Rangs gemacht hat. Das Ergebnis ist eine Werkzeugkiste mit einem Kodierer pro Job: Alltags-Strings, PEM-Blöcke, Geschwindigkeit, URLs, Dateien und Tokens.
Fun Facts, R-Edition
Denn ein vollständiger Leitfaden sollte mit einem Lächeln enden:
- Jedes Base64-PNG im Internet beginnt mit denselben Zeichen: Der magische Kopf 89 50 4E 47 kodiert zu
iVBOR, also findet eine Suche nach diesem Fingerabdruck in der HTML-Datei jedes eingebettete Bild. Formate haben Fingerabdrücke, und dieser hier ist ein Präfix. base64encode("Man")kodiert nicht das Wort Man. Es geht auf die Suche nach einer Datei namens Man, warnt, dass es sie nicht öffnen kann, und gibt auf. Die R-spezifischste Falle im Base64-Ökosystem, im Klartext im Argument-Listing versteckt.- Ein mit OpenSSL umbrochener String endet immer mit einem Zeilenumbruch, also ist die letzte Zeile eines PEM-Blocks nie die letzte Zeile der Datei. Der Umbruch hat einen Punkt am Satzende, ob Sie ihn wollen oder nicht.
- Der leere String wird auf drei verschiedene Arten kodiert:
base64encliefert einen Vektor aus null Strings,b64undbase64urlliefern den leeren String. R trifft auf nichts, und R gibt drei Antworten. - jose schreibt den JWT-Header mit
typvoralg, während die meisten handgeschriebenen Beispielealgzuerst setzen. JSON schert sich nicht um die Reihenfolge der Schlüssel, und JWT-Verifizierung weiß das, aber Ihr String-Vergleich weiß es nicht. - Das 76-Zeichen-Limit von MIME ist eine 1993-Entscheidung über die Zeilenlänge von Nachrichten, die durch vier RFCs in jeden E-Mail-Anhang getragen wurde, den Sie je geschickt haben. Der Umbruch, den Sie heute einstellen, wurde verhandelt, bevor R einen Farb-Plot hatte.
b64dekodiert Alphabete, die Sie nie gesehen haben, BinHex, IMAP-modifiziertes UTF-7, bcrypt, crypt und das URL-sichere Paar, mit je einer Engine. Derselbe Rust-Code, der Ihr JSON-Feld packt, kann einen Macintosh-Anhang aus den 1980ern auspacken.
Die Kehrseite
Wählen Sie Ihren Kodierer nach dem Job: base64enc für Alltags-Strings, mit linewidth und newline, wenn der Kanal eine Form hat; openssl, wenn Sie PEM-artige 64-Zeichen-Ausgabe wollen oder es ohnehin im Projekt ist; b64, wenn Sie Geschwindigkeit, Vektoren oder die URL-sicheren Engines wollen; und die kleinen Spezialisten base64 und base64url für Datei-Flickarbeiten und URL-sichere Strings. Wandeln Sie mit charToRaw() um, bevor Sie mit base64enc::base64encode() kodieren, wählen Sie das Umbrechen pro Kanal statt pro Paket, behalten Sie das URL-sichere Alphabet für alles, was in einer URL oder einem JWT leben wird, signieren und verifizieren Sie Tokens mit jose, und testen Sie jeden Pfad mit einem Round-Trip. Das Format selbst ist an genau zwei Stellen unerbittlich, dem Alphabet und den Zeilenumbrüchen, und die Kodierer unterscheiden sich vor allem darin, wie ehrlich sie Ihnen sagen, wenn Sie eines davon verfehlt haben. Und wenn die andere Richtung ruft - wenn ein String ankommt und Sie ihn auseinandernehmen, prüfen, was die Bytes bedeuten, und die Dekodierer überstehen müssen, die still scheitern - dann deckt der Schwestern-Artikel die Base64-Dekodierung in R im Detail ab.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in R: Ein vollständiger Leitfaden