Do you have to deal with Base64 format? Then this site is perfect for you! Use our super handy online tool to encode or decode your data.

Base64 Encoding in Perl: A Complete Guide

You have data that must survive a channel that does not like it. A binary blob that needs to sit in a JSON field. An image that must live inside an HTML tag. A certificate that belongs in a config file. A token that will travel through URLs, headers, and query strings. This is the daily life of Base64 encoding: it rewrites every three bytes of raw data as four characters from a 64 letter alphabet, with one or two = signs finishing the tail, so the result is plain text that anything can carry, typically running about 33 percent longer than what you started with. The main page of this site explains the format in full, so this article focuses on what Perl gives you, what it quietly decides for you, and where the traps are.

The good news first: encode_base64 has lived in the core of Perl since 2002, it is implemented in C, and it is comfortably fast. The interesting part is that the function has opinions. It wraps its output at 76 characters, it appends a trailing newline, and it flatly refuses to encode Unicode characters you have not converted to bytes first. This guide walks through every flavor of Base64 a Perl developer actually produces: the one liner, the MIME wrapped email body, the PEM wrapped key, the URL safe token, and the streaming version for files that are too big to hold in memory.

One Function, a Hidden Newline

The entire API, exactly as the modern documentation presents it:

encode_base64( $bytes )
encode_base64( $bytes, $eol )

Read that again. Two arguments, one of them optional, one return value, no flags. The optional second argument is the line ending sequence, and it defaults to a plain newline, which means the most innocent looking call in Perl produces wrapped, newline terminated output:

use MIME::Base64 qw(encode_base64);
my $wrapped = encode_base64("Aladdin:open sesame");
print length($wrapped), "\n";  # 29: the 28 characters plus a newline
my $single = encode_base64("Aladdin:open sesame", "");
print length($single), "\n";   # 28: pass an empty string for no wrapping

The output size follows a fixed pattern you can predict before calling:

Input bytes Output characters Padding
0 0 none
1 4 two =
2 4 one =
3 4 none
100,000 133,336 none, on a single line
3,000,000 4,000,000 none

The pattern is four characters for every complete group of three bytes, plus a final partial group padded with one or two = signs. One consequence worth knowing: one byte and three bytes both produce four characters, so the encoded length hides the exact input size. And if you need the size without doing the work, the module has had a length function since 3.10 in 2010. It is just not exported by default, so you call it through the package name:

use MIME::Base64 ();
my $with_wrap   = MIME::Base64::encoded_base64_length($bytes);        # 76 char lines, default eol
my $single_line = MIME::Base64::encoded_base64_length($bytes, "");    # no wrapping
my $mime_body   = MIME::Base64::encoded_base64_length($bytes, "\r\n");

There is one more rule to memorize, because it is the only way the encoder ever shouts: if the string you hand it contains characters with a code above 255, encode_base64 dies with Wide character in subroutine entry. The Base64 encoding is only defined for single byte characters, and Perl 5.8 and better allow extended characters in strings, so the module forces you to make the conversion on purpose. The next section is all about that step.

Text or Bytes? The Step the Function Cannot Do

Perl strings carry a quiet flag that says whether they hold characters or bytes, and Base64 lives on the bytes side of that line. If your text is a character string, and it is the moment it comes from a JSON parser, a template, or a literal with accented letters in a UTF 8 source file, the encoder refuses to guess what bytes you meant, and it tells you so. The fix is one core function from the Encode module:

use MIME::Base64 qw(encode_base64);
use Encode qw(encode);
my $chars = "H\x{eb}llo W\x{f6}rld";  # a character string
my $utf8  = encode("UTF-8", $chars);  # now: bytes
my $b64   = encode_base64($utf8, "");
print $b64, "\n";

That encode call is the whole dance: pick the byte representation, and UTF 8 for anything modern, convert the characters to those bytes, and only then hand the bytes to the encoder. For legacy Western text that arrived as Windows 1252, the conversion is the same function with a different name, encode("Windows-1252", $legacy), which hands you the original single byte form. The Encode module is core, so all of this costs nothing.

Now the trap. If the bytes you have are already UTF 8 and you run them through encode("UTF-8", ...) again, thinking you are making them UTF 8, you do not get a copy: you get a double encoding, where every accented character balloons into two characters of its own. The classic symptom is text that used to read Hëllo and now reads Hëllo, and every decoder on the internet will decode that faithfully for you:

use Encode qw(encode decode);
my $right = encode("UTF-8", "H\x{eb}llo");
my $wrong = encode("UTF-8", $right);  # re encoding the bytes as characters
print decode("UTF-8", $right), "\n";  # Hëllo
print decode("UTF-8", $wrong), "\n";  # Hëllo

The rule of thumb that prevents it: bytes get encoded exactly once, and utf8::is_utf8() shows you which side of the line a string is on. If the flag is set, you are holding characters and the encode() call is the right move; if it is not set, you are holding bytes and you are ready for Base64.

Line Wrapping: Three Dialects, One Rule

Because the default output is wrapped and newline terminated, the first decision for every encoding job is a destination problem: where will this string live? The unifying fact is that decoders ignore line breaks entirely, RFC 2045 tells decoding software to ignore all line breaks and characters outside the alphabet, so wrapping is a courtesy to line based tools and humans, not a semantic difference. The three answers in practice:

No line breaks. The function output with the wrapping disabled, exactly one line. This is what you want for URLs, JSON payloads, headers, database values, and anything else where a line break would be a bug. This is also what most people mean when they ask for just the Base64:

my $single = encode_base64($bytes, "");

MIME: 76 characters plus CRLF. The email convention from RFC 2045: encoded lines must not exceed 76 characters, and the MIME world speaks CRLF. This one is the module's own job, done with the second argument:

my $mime_body = encode_base64($bytes, "\r\n");

PEM: 64 characters plus LF. Keys and certificates use the older convention with shorter 64 character lines, and the module cannot produce that width on its own, so a four line helper fills the gap:

sub wrap_lines {
  my ($text, $width) = @_;
  return join "\n", $text =~ /(.{1,$width})/g;
}
my $pem = "-----BEGIN CERTIFICATE-----\n"
        . wrap_lines(encode_base64($der, ""), 64) . "\n"
        . "-----END CERTIFICATE-----\n";

The same helper serves the other 64 character dialect, the PKIX textual encoding from RFC 7468, and a wider width produces the OpenPGP style 76 character armor, where the trailing CRC24 checksum line is OpenSSL's job rather than yours. One gotcha applies to all of them: encode_base64 appends the line ending at the very end of the result, even when the last line fills its width exactly. If a downstream consumer trips over that trailing blank line, an rtrim of the separator fixes it.

URL-Safe Base64: The - and _ Alphabet

The standard alphabet includes + and /, and both are trouble outside a text file: a + in a form encoded query string becomes a space before your application ever sees it, and / is a path separator in URLs. Filenames and tokens have their own complaints. RFC 4648, section 5, solves this with the URL and filename safe alphabet, where + becomes -, / becomes _, and the trailing = padding is usually dropped. The RFC insists this should not be regarded as the same as the base64 encoding, so treat it as a distinct format, commonly called base64url. Perl has produced it in one call since version 3.11 in 2010:

use MIME::Base64 qw(encode_base64url);
my $seg = encode_base64url("sunset-42");
print $seg, "\n";  # c3Vuc2V0LTQy: no padding, no newline

That single call does all three changes: the alphabet swap, no padding, no line breaks. If you are already holding standard Base64 and the destination wants the URL safe dialect, two string operations convert it in place:

sub to_urlsafe {
  my ($b64) = @_;
  $b64 =~ tr{+/}{-_};
  return $b64 =~ s/=+\z//r;
}
my $seg = to_urlsafe(encode_base64($bytes, ""));

When to use it: JSON Web Token parts, OAuth state and nonce parameters, API IDs you put in URL paths, and opaque keys that need to survive an address bar or a file name, where CPAN's Data::UUID::Base64URLSafe exists for exactly this. When not to use it: email bodies, PEM armor, and any place a standard alphabet consumer is on the other end, because - and _ are not in their vocabulary. And do not mix the two alphabets silently: a value encoded URL safe must be decoded URL safe, everywhere, forever. On Perls older than 3.11, the standalone MIME::Base64::URLSafe module from 2006, which is a port of Python's urlsafe codec, provides urlsafe_b64encode; on anything modern, the built in is the right tool.

Building a JWT: Every Part by Hand

JSON Web Tokens are the flagship consumer of Base64 in modern APIs, and they use the URL safe, unpadded dialect from the section above. Per RFC 7515, a compact JWT is three dot separated base64url parts: the protected header, the payload, and the signature. Building one by hand is a pleasant way to see every moving part:

use MIME::Base64 qw(encode_base64url);
use JSON::PP qw(encode_json);
use Digest::SHA qw(hmac_sha256);
my $secret  = "correct-horse-battery-staple";
my $head    = encode_base64url(encode_json({ alg => "HS256", typ => "JWT" }));
my $claims  = encode_base64url(encode_json({ sub => "homer", role => "admin" }));
my $sig     = encode_base64url(hmac_sha256("$head.$claims", $secret));
my $jwt     = "$head.$claims.$sig";

Three details worth noticing. First, encode_json from the core JSON::PP module emits compact UTF 8 bytes with no whitespace, which is exactly what the JOSE specs want inside a token. Second, the payload is readable by anyone, and that is by design: a JWT is a signed ticket, not a secret, so never put confidential values in the claims. Third, the signature is the Base64url encoding of raw HMAC bytes, which is why hmac_sha256 goes straight into the encoder without any hex formatting.

For production, you do not hand roll signing. The CPAN module Crypt::JWT, which builds on CryptX, implements JWS and JWE with the full set of algorithms:

use Crypt::JWT qw(encode_jwt);
my $jwt = encode_jwt(
  payload => { sub => "homer", role => "admin" },
  alg     => "HS256",
  key     => $secret,
);

And on the receiving side, pin the algorithm with accepted_alg so an attacker cannot flip the token to a weaker variant: decode_jwt(token => $jwt, key => $secret, accepted_alg => "HS256") verifies the signature and croaks on failure. Hand rolling is fine for understanding; a library is fine for money.

HTTP: Auth Headers, Data URIs and the WebSocket Handshake

The Authorization: Basic header is the oldest live use case: the username and password joined by a colon, encoded as one line, prefixed with the scheme word. The empty string second argument is load bearing here, because a trailing newline inside a header field is a bug:

use MIME::Base64 qw(encode_base64);
my $user = "alice";
my $pass = "s3cr3t";
my $header = "Basic " . encode_base64("$user:$pass", "");
print $header, "\n";  # Basic YWxpY2U6czNjcjN0

Data URIs from RFC 2397 are the same idea applied to images: the payload sits directly in the URL, so no second request is needed to fetch it. Binary media use the ;base64 flag, so the payload is exactly what encode_base64 produced with the wrapping disabled:

my $uri = "data:" . $mime_type . ";base64," . encode_base64($bytes, "");

The trade offs are real, though. The encoded payload is about 33 percent bigger than the file, which makes the HTML document itself bigger. Browsers will not cache a data URI the way they cache a file URL, so every page view re downloads the bytes, and the RFC itself says data URIs are only useful for short values. Use them for avatars, icons, and small inline graphics; use real files for everything else. There is a third HTTP corner that uses Base64 quietly: the WebSocket handshake from RFC 6455, where the client sends a Sec-WebSocket-Key header that is the Base64 of sixteen random bytes. Frameworks like Mojolicious do it for you, but if you ever see it on the wire, now you know what it is:

use MIME::Base64 qw(encode_base64);
my $key = encode_base64(pack("C16", map { int(rand 256) } 1 .. 16), "");

Files: Slurps, 57-Byte Chunks and the CLI

The most straightforward encoding job: a file becomes text. Perl strings are bytes, so there is no binary mode to worry about. Open raw, read, encode, write:

use MIME::Base64 qw(encode_base64);
open my $in, "<:raw", $ARGV[0] or die $!;
local $/;
my $bytes = <$in>;
close $in;
open my $out, ">:raw", $ARGV[1] or die $!;
print {$out} encode_base64($bytes);
close $out;

The :raw layers matter. Without them, Perl would try to interpret the bytes as platform text on the way in and out, and on a system with a different default encoding that is exactly the corruption you cannot see until the file is opened somewhere else. And remember the size bill when you plan storage: a 500 KB image becomes a 670 KB text file, and a 1 GB video becomes 1.33 GB.

For files too big to hold in memory, the module's own documentation gives you the rule: encode in chunks that are a multiple of 57 bytes, because 57 bytes of data fills exactly one 76 character line, 76 being 57 times 4 divided by 3. Chunk at that boundary and you never get padding in the middle of the stream:

use MIME::Base64 qw(encode_base64);
open my $in, "<:raw", $ARGV[0] or die $!;
while (read($in, my $buf, 57 * 10)) {
  print encode_base64($buf);
}
close $in;

Each chunk lands exactly on line boundaries, the last, possibly short, chunk carries the final padding, and the result is byte for byte the same as slurping the whole file and encoding it at once, just with a constant memory footprint. And when you do not need a script at all, the one liner covers it, with -0777 slurping the input and the empty string argument keeping the output on one line:

perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file > file.b64

Email: MIME Bodies and Attachments

Email is where Base64 earned its name. The MIME standard says data that cannot safely ride as raw text should be sent with Content-Transfer-Encoding: base64, in lines no longer than 76 characters. If you build mail with MIME::Lite, the whole thing is one argument, and the module does the encoding, the wrapping, and the header for you:

use MIME::Lite;
my $mime = MIME::Lite->new(
  From    => 'me@example.com',
  To      => 'you@example.com',
  Subject => 'A file',
  Type    => 'text/plain',
  Data    => 'The body text.',
);
$mime->attach(
  Type     => 'application/octet-stream',
  Data     => $bytes,
  Encoding => 'base64',
  Filename => 'hello.txt',
);

The Encoding argument is the trigger: MIME::Lite Base64 encodes the attachment in 76 character CRLF lines and stamps the part with the matching Content-Transfer-Encoding header. Email::MIME takes the same stance and encodes attachments automatically when the body is not 8 bit clean. If you are assembling a raw MIME message by hand, the equivalent is the two lines from the wrapping section, encode_base64($bytes, "\r\n") plus the header line, and that is the entire protocol side story.

Databases, Config and Environment Variables

Databases: binary data often rides in a TEXT column as Base64, because the column cannot promise to pass arbitrary bytes through untouched. Store the single line form, never the wrapped one, or your next SELECT will return a string with newlines in the middle of the value:

use MIME::Base64 qw(encode_base64);
# $dbh is an already connected DBI handle
my $stmt = $dbh->prepare(q{UPDATE photos SET data = ? WHERE id = ?});
$stmt->execute(encode_base64($bytes, ""), 42);

Config files are the same shape: a JSON document where the binary or secret field is a single line Base64 string, which is exactly why the second argument exists:

use JSON::PP qw(encode_json);
my $config = {
  api_key  => encode_base64($key_bytes, ""),
  logo_png => encode_base64($png_bytes, ""),
};
open my $fh, ">:raw", "app.json" or die $!;
print {$fh} encode_json($config);
close $fh;

Environment variables deserve a word of warning. Base64 is fine for small tokens in the environment, but the encoded form is 33 percent bigger than the original, and the operating system caps each argument. On Linux the limit is 128 KB per string, enforced by execve, and a big blob in an environment variable does not fail politely: the child process dies with a cryptic error the moment it is spawned. Small values in the environment, big values in a file or a database.

Performance: C, Not Perl, Is Doing the Work

The core module is implemented in C, and that C descends from code written for metamail in 1991, which is a fun fact until you notice the implication: the encoder has had three decades of tuning. On a modern machine it processes data at a gigabytes per second pace, which is faster than the disk or network it is usually feeding, so Base64 itself is almost never the bottleneck. The I/O is.

For the odd system without a C compiler, the pure Perl twin MIME::Base64::Perl on CPAN provides the same basic interface, a few times slower but still comfortable for ordinary workloads. And two habits keep the big jobs predictable: stream with 57 byte chunks instead of slurping, and size your buffers with encoded_base64_length before you allocate, which spares you both the guesswork and the reallocation.

Pitfalls, Ranked by Afternoon Cost

The traps, in roughly the order they bite:

Pitfall What happens Fix
Forgetting the second argument the output arrives wrapped at 76 characters with a trailing newline, and your URL, JSON field, or header breaks in the middle of the value pass "" for one line output, and keep the wrapping for destinations that expect it
Wrapping at the wrong width a PEM consumer expects 64 character lines and gets 76, or a MIME body exceeds the 76 character limit match the width to the dialect: "" for none, "\r\n" for MIME, a helper for PEM
The wide character croak a character string with codes above 255 dies with Wide character in subroutine entry mid request run the bytes through Encode first, named on purpose, before the encode_base64 call
Double encoding re encoding already UTF 8 bytes through encode("UTF-8", ...) turns Hëllo into Hëllo bytes get encoded exactly once; check utf8::is_utf8() when in doubt
Mixing alphabets silently a value encoded with - and _ hits a standard alphabet decoder and comes back as garbage one dialect per value, end to end: pick base64url or standard at the boundary
The trailing line ending encode_base64 appends the eol even when the last line is exactly full, and a strict consumer sees a blank line chomp or rtrim the separator when the consumer is picky
Wrapped values in a database newlines land inside a TEXT column and the next SELECT returns a broken token store the single line form; wrap only at the destination
Environment variables with big blobs the 33 percent growth plus the OS per argument limit kills the child process at spawn with a cryptic error small values in the environment, big values in a file or a database
Assuming Base64 is protection the format hides nothing, and RFC 4648 documents real incidents where a user pasted an exchange and accidentally revealed a password treat the output as confidential from the moment it is produced, and keep it out of logs
Planning without the size bill a 500 KB image becomes 670 KB of text, and the storage or payload limit you did not check bites budget 4/3 of the original size before you commit

A History Told by the Encoder

Perl's Base64 encoder has a career worth a minute, and it starts in the first web toolkit:

  • Born in libwww perl. The encoder began life as LWP::Base64 inside the libwww perl distribution, written by Martijn Koster and Joerg Reichelt, and it graduated to its own CPAN distribution, MIME::Base64, in April 1997, version 2.00, with a changelog entry that simply says it is based on libwww perl 5.08.
  • The speed era. Version 2.07 in 1998 shipped a faster and smarter C implementation, about 25 percent quicker on the then modern Linux boxes, and the tuning continued for a decade.
  • The Unicode era. Perl 5.8 in 2002 brought characters with codes above 255 into ordinary strings, and the module answered in steps: 2.12 in 2001 downgraded UTF 8 strings before encoding, and the modern Wide character in subroutine entry croak is the encoder's way of keeping that promise. The 2.13 sync with the core that same year brought EBCDIC support along, a reminder that Base64 in Perl still runs on mainframes.
  • The command line era. Releases from 2.14 in 2003 through 3.05 in 2005 bundled an actual encode-base64 command, along with its decode and quoted printable twins; the scripts moved to a separate MIME Base64 Scripts distribution in 2005.
  • The URL safe arrival. RFC 4648 standardized the URL safe alphabet in 2006, a standalone MIME::Base64::URLSafe module appeared the same year, and the core module caught up in 3.11 in 2010 with encode_base64url in a single call.
  • The modern line. Version 3.16 in 2020 rebuilt the packaging and raised the floor to Perl 5.6.2; the current core Perls ship the 3.16 series, and the maintainers are the perl5 porters themselves, which is about as safe a home as a core module can have.

Fun Facts, Specifically Perl

The trivia that makes this story a good one:

  • The POD's example is a magic phrase. Since 1997, the module's own documentation has encoded Aladdin:open sesame, so the string QWxhZGRpbjpvcGVuIHNlc2FtZQ== has been the module's business card for nearly thirty years.
  • The default line ending is the one you probably did not expect. It is a plain \n, not the CRLF that MIME speaks. The RFC's own convention needs the second argument, and the module ships with the programmer's default, not the protocol's.
  • The empty string has a special rule. Encode nothing and you get nothing back, no newline appended: the one documented exception to the trailing eol rule, and the reason an empty file round trips cleanly.
  • The IMAP cousin has a comma. The mailbox name variant from RFC 3501 swaps the / for a comma in the alphabet, so a Base64 string from an IMAP server can contain a letter the standard decoder treats as noise.
  • The 1991 lineage is real. The C implementation descends from metamail, Bellcore's 1991 mail program, three years before Perl 5 was born, so every encode_base64 call is partly nineties code.
  • The pure Perl twin has a story of its own. When version 3.00 in 2004 dropped the pure Perl implementations from the core module, the changelog called them bloat that hides real problems in the XS code and re released them as MIME::Base64::Perl, where they still live.

So the next time raw bytes need to travel through a text only world, you know the whole story. One function call does the work, the hidden newline is a decision you make with the second argument, the wide character croak is the encoder's way of keeping your Unicode honest, the URL safe dialect is one call since 2010, files stream in 57 byte chunks, and the 33 percent bill is the price of admission. And if one day you need to do the trip in the other direction, taking a string of letters and getting the original bytes back out, the related article on Base64 decoding in Perl, linked below, covers that ritual in the same depth.

Last updated: 2026-08-29

Related article: Base64 Decoding in Perl: A Complete Guide