Upload a file
  1. base64hub
  2. Base64 in Code
  3. OpenSSL
Code

Base64 with OpenSSL

openssl base64 is available on nearly every Linux, macOS and BSD machine, and behaves the same on all of them. It also fails silently. Outputs below come from OpenSSL 3.5.

Encode and decode a string

printf '%s' 'héllo wörld' | openssl base64        # aMOpbGxvIHfDtnJsZA==
echo 'aMOpbGxvIHfDtnJsZA==' | openssl base64 -d   # héllo wörld

openssl base64 is shorthand for openssl enc -base64; both spellings take the same flags. Use printf '%s' when encoding so no newline sneaks into the data, and echo when decoding, for the reason below.

The trailing newline trap and -A

By default OpenSSL processes Base64 as newline-terminated lines of text. A final line without \n is ignored when decoding, so this prints nothing at all:

printf 'aGVsbG8K' | openssl base64 -d             # (no output)
printf 'aGVsbG8K\n' | openssl base64 -d           # hello
printf 'aGVsbG8K' | openssl base64 -d -A          # hello

-A tells OpenSSL to treat the whole input as a single line. Use it in both directions:

  • Encoding: without -A, output is wrapped at 64 characters per line. With -A you get one line, which is what HTTP headers, JSON and environment variables need.
  • Decoding: with -A, the input must be a single line without line breaks. Without -A, multi-line input such as PEM bodies decodes fine.
head -c 60 /dev/zero | tr '\0' x | openssl base64      # 64 chars, newline, 16 chars
head -c 60 /dev/zero | tr '\0' x | openssl base64 -A   # 80 chars on one line

The 64-character wrapping matches the PEM format (RFC 7468), not MIME, which uses up to 76. For MIME output, the MIME Base64 encoder or GNU base64 (default 76) fit better.

URL-safe Base64

OpenSSL has no Base64URL mode. Translate with tr:

printf '%s' '<<???>>' | openssl base64 -A | tr '+/' '-_' | tr -d '='
# PDw_Pz8-Pg

For decoding, reverse the mapping and restore padding; the Base64 in Bash page has a loop that does it. OpenSSL itself silently ignores - and _, as shown below, so never feed it URL-safe input directly. Base64 vs Base64URL explains the difference.

Encode and decode files

openssl base64 -in photo.jpg -out photo.b64
openssl base64 -d -in photo.b64 -out photo-copy.jpg

OpenSSL streams, so large files are fine. A 100 KB random file survived the round trip byte for byte. The encoded file is wrapped at 64 characters and ends with a newline, so decoding it without -A works. If you need a one-line file, add -A when encoding and again when decoding.

For a data URI:

printf 'data:image/jpeg;base64,%s' "$(openssl base64 -A -in photo.jpg)"

The BUGS section of the OpenSSL 3.5 enc manual still lists two related problems: -A “doesn’t work properly” with large files, and when decoding without -A, if the first 1024 bytes of input contain no newline, the first two lines are ignored. For a big one-line file, wrap it first with fold -w 64 and decode without -A.

Silent failures: validating the result

OpenSSL does not report invalid Base64. It stops at the bad data, writes whatever it decoded, and exits with status 0. Results from OpenSSL 3.5:

InputOutputExit code
aGVsbG8K + newlinehello\n0
aGVsbG8K (no newline)nothing0
aGk + newline (no padding)nothing0
aGk=!! + newlinenothing0
PDw_Pz8-Pg== + newline (URL-safe)nothing0
not base64 at all + newlinenothing0

Scripts therefore have to check the output themselves:

n=$(echo "$b64" | openssl base64 -d | wc -c)
[ "$n" -gt 0 ] || echo "decode failed or input was empty" >&2

That catches complete failures but not partial ones, where OpenSSL decoded a prefix before hitting bad data.

Or switch to GNU base64 -d, which prints base64: invalid input and exits with 1. To see what is wrong with a specific string, the Base64 validator marks the bad character, and common Base64 errors explains the causes.

Hashes, keys and encrypted output

Most OpenSSL Base64 use is in combination with other commands:

printf '%s' hi | openssl dgst -sha256 -binary | openssl base64 -A
# j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=

openssl rand -base64 32          # 32 random bytes -> 44 characters

The first form produces Base64 SHA-256 digests, which is what SRI hashes and many API signatures use; the browser version is the SHA-256 to Base64 tool. -binary matters: without it you encode the hex text of the hash, not the hash.

openssl rand -base64 32 is a common way to make secrets and keys; the Base64 key generator does the same in the browser.

openssl enc with -a (same as -base64) Base64-encodes ciphertext:

printf '%s' 'secret text' | openssl enc -aes-256-cbc -pbkdf2 -a -pass pass:demo
# U2FsdGVkX1/eMk5T8FT9dX8OyAxLRn/ukvgmovOQOeA=   (salt is random, changes every run)

The U2FsdGVkX1 prefix is Base64 for Salted__. Decoding it without the password reveals nothing readable. That difference between encoding and encrypting is covered in encoding vs encryption.

PEM files

Certificates and keys in PEM format are Base64 DER between -----BEGIN ...----- lines, wrapped at 64 characters. OpenSSL converts them directly, without manual Base64 work:

openssl x509 -in cert.pem -outform der -out cert.der
openssl x509 -in cert.der -inform der -outform pem

To inspect a PEM block in the browser, use the certificate decoder or PEM to DER.

FAQ

Why does openssl base64 -d print nothing?

Either the input has no trailing newline, or it is invalid. Without -A, OpenSSL expects newline-terminated lines and quietly drops a final line without one. Add -A, or echo the string instead of printf '%s'.

Is openssl base64 the same as openssl enc -base64?

Yes. openssl base64 is a shortcut for openssl enc -base64, and both accept -d, -A, -in and -out.

How do I check whether openssl base64 -d succeeded?

You cannot rely on the exit code: invalid input returns status 0 with empty or partial output. Check that the output is non-empty, or use GNU base64 -d, which exits with 1 on bad input.

What is U2FsdGVkX1 at the start of a Base64 string?

It is the Base64 of Salted__, the header openssl enc writes before encrypted data when a password is used. The string is ciphertext, so decoding it gives binary noise, not the plaintext.

Keep reading