Encode and decode a string
use strict;
use warnings;
use utf8;
use MIME::Base64 qw(encode_base64 decode_base64);
use Encode qw(encode decode);
my $enc = encode_base64(encode('UTF-8', 'héllo wörld'), '');
print "$enc\n"; # aMOpbGxvIHfDtnJsZA==
print decode('UTF-8', decode_base64($enc)), "\n"; # héllo wörld
Perl distinguishes character strings from byte strings, and encode_base64 wants bytes, so text goes through Encode::encode('UTF-8', ...) first. A string with characters above U+00FF dies with Wide character in subroutine entry; one with only Latin-1 characters like é is silently encoded as Latin-1, not UTF-8.
The '' argument is explained in the next section.
The line-ending argument
encode_base64($bytes, $eol) wraps output at 76 characters and appends $eol to every line, including the last one. The default is "\n":
print encode_base64('hi'); # "aGk=\n"
print encode_base64('x' x 60);
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
# eHh4
That is correct for MIME email bodies, and wrong for almost everything else: HTTP headers, JSON, URLs and database fields. Pass '' for single-line output, or "\r\n" for strict RFC 2045 line endings.
For an HTTP header, that means:
print "Authorization: Basic ", encode_base64('user:pass', ''), "\n";
# Authorization: Basic dXNlcjpwYXNz
Decoding does not care about line endings: decode_base64("aGVs\r\nbG8K") returns "hello\n", so MIME bodies with either \n or \r\n decode directly.
URL-safe Base64
MIME::Base64 exports encode_base64url and decode_base64url on request. The encoder drops padding and the decoder accepts input without it:
use MIME::Base64 qw(encode_base64url decode_base64url);
print encode_base64url('<<???>>'), "\n"; # PDw_Pz8-Pg
print decode_base64url('PDw_Pz8-Pg'), "\n"; # <<???>>
For URL-safe output that keeps padding, translate the standard form with tr:
my $padded = encode_base64('<<???>>', '') =~ tr{+/}{-_}r; # PDw_Pz8-Pg==
The /r flag (Perl 5.14+) returns the modified copy. Base64 vs Base64URL explains which systems expect which variant.
Encode and decode a file
Open files in :raw mode so no layer translates newlines or encodings:
open my $in, '<:raw', 'hello.txt' or die $!;
my $data = do { local $/; <$in> };
close $in;
print encode_base64($data, ''), "\n"; # aGVsbG8K
open my $out, '>:raw', 'copy.txt' or die $!;
print {$out} decode_base64('aGVsbG8K');
close $out;
Large files in chunks
Read in multiples of 57 bytes. 57 bytes encode to exactly 76 characters, so each chunk produces complete MIME lines and the concatenated output is identical to encoding the whole file:
open my $in, '<:raw', 'big.bin' or die $!;
open my $out, '>:raw', 'big.b64' or die $!;
while (read($in, my $buf, 57 * 1024)) {
print {$out} encode_base64($buf);
}
close $in;
close $out;
The result decoded back to the original 300 KB file with base64 -d. For single-line output use encode_base64($buf, '') with any chunk size divisible by 3. The module can also tell you sizes without encoding: MIME::Base64::encoded_base64_length('hello') returns 9 (including the newline) and decoded_base64_length('aGVsbG8=') returns 5. The size overhead guide has the formula.
From the command line, the module ships with two one-liners worth knowing:
perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < file.b64 > file.bin
Validation: decode_base64 never complains
decode_base64 is lenient by design. It drops anything outside the alphabet, stops at padding and returns what it has:
decode_base64('aGk=!!'); # "hi"
decode_base64('aGk'); # "hi" (missing padding is fine)
decode_base64('aGl='); # "hi" (non-zero padding bits ignored)
decode_base64('a'); # "" (too short, no warning)
decode_base64('PDw_Pz8-Pg=='); # "<<\x0f\xcf\xc3\xe0", not "<<???>>"
The last one is the dangerous case: URL-safe input fed to the standard decoder silently produces wrong bytes. Use decode_base64url for tokens. For untrusted input, check the alphabet and length, then confirm the round trip:
sub is_base64 {
my ($s) = @_;
return 0 unless $s =~ m{\A[A-Za-z0-9+/]*={0,2}\z} && length($s) % 4 == 0;
return encode_base64(decode_base64($s), '') eq $s ? 1 : 0;
}
# is_base64('aGk=') = 1, 'aGk' = 0, 'aGl=' = 0, 'aGk=!!' = 0
The Base64 validator runs the same checks in the browser and tells you which character is wrong. For broken strings from logs, try Repair Base64; common Base64 errors explains what usually causes them.
Pitfalls
- Trailing newline in headers.
"Authorization: Basic " . encode_base64("$user:$pass")ends with\n, which breaks the request. Pass''. - Double encoding. Under
use utf8with an output layer of:encoding(UTF-8), printing decoded bytes withoutdecode('UTF-8', ...)shows mojibake likehéllo. - Treating Base64 as secret. It is reversible by anyone. See encoding vs encryption.
FAQ
Why does encode_base64 add a newline?
The second argument is the line ending, and it defaults to "\n". Output is wrapped at 76 characters and always ends with that string. Pass '' to get a single line.
What does 'Wide character in subroutine entry' mean?
You passed a character string with code points above 255 to encode_base64, which only accepts bytes. Encode the text first: encode_base64(encode('UTF-8', $text), '').
Does decode_base64 validate its input?
No. It ignores characters outside the alphabet and returns whatever it can decode, so decode_base64('aGk=!!') gives hi. Check the string with a regex before decoding if the input is untrusted.