OpenSSL Passphrase Sources: Safe Files, Pipes and Prompts
You will finish with a small set of OpenSSL commands that take passphrases from a prompt, environment variable, file, file descriptor or standard input. You will also know which choices expose the secret and how to prove that the same passphrase decrypts the result.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need OpenSSL and a POSIX shell. The examples use openssl enc with AES-256-CBC and PBKDF2 only as a reproducible demonstration of the passphrase interface. They are not a recommendation to design a new file format around enc. No command needs elevated privileges, and none changes system configuration.
1. Check the installed OpenSSL
Start by checking the binary that your shell will actually run. Package metadata and the executable selected by PATH can describe different installations:
$ command -v openssl
/home/linuxbrew/.linuxbrew/bin/openssl
$ openssl version
OpenSSL 3.6.1 27 Jan 2026
Your version and path may differ. The installed manpage on this machine is from the Ubuntu openssl package, version 3.0.13-0ubuntu3.15, while the selected executable is OpenSSL 3.6.1. The passphrase-source syntax described here is the same in both references, but always use the help and manpages belonging to the binary you will run.
Checkpoint: confirm that the command accepts a passphrase source through -pass:
$ openssl enc -help 2>&1 | grep -- ' -pass '
-pass val Passphrase source
2. Know what the source names mean
OpenSSL passphrase arguments have a prefix followed by a value. The common forms are:
| Source | Meaning | Practical warning |
|---|---|---|
pass:VALUE | Use the literal value. | Visible to process-listing tools. Use only for disposable tests. |
env:NAME | Read an environment variable. | Other processes may be able to inspect environments on some systems. |
file:PATH | Read the first line of a file or stream. | Protect the path and remember that only one line is consumed. |
fd:NUMBER | Read from an open file descriptor. | Useful for a pipe, but not supported on Windows. |
stdin | Read from standard input. | Do not confuse the password stream with the command's data input. |
If an OpenSSL command needs a password and you omit its passphrase argument, it normally prompts on the current terminal with echo disabled. That is often the least surprising interactive choice. Character encoding can matter for non-ASCII passphrases, so use a deliberately agreed encoding when different systems will exchange the data.
3. Use a prompt for an interactive operation
Make a short plaintext file, then let OpenSSL prompt for the encryption passphrase. The redirection writes only to /tmp and can be removed after the test:
$ printf '%s\n' 'passphrase source test' > /tmp/openssl-passphrase-plain
$ openssl enc -aes-256-cbc -pbkdf2 -salt \
-in /tmp/openssl-passphrase-plain \
-out /tmp/openssl-passphrase-cipher
enter AES-256-CBC encryption password:
Verifying - enter AES-256-CBC encryption password:
Type the same test passphrase twice. Nothing should be echoed. This is an ordinary command. Do not use sudo to make a private file readable: fix its ownership or permissions instead.
Checkpoint: decrypt it into a different file and compare the content:
$ openssl enc -d -aes-256-cbc -pbkdf2 \
-in /tmp/openssl-passphrase-cipher \
-out /tmp/openssl-passphrase-plain.dec
enter AES-256-CBC decryption password:
$ cmp /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-plain.dec
$ cat /tmp/openssl-passphrase-plain.dec
passphrase source test
A zero exit status from cmp means the recovered bytes match. A wrong password normally produces a non-zero OpenSSL status and an error such as a bad decrypt message; do not treat a partial output file as valid.
4. Use a protected file or environment variable in automation
For a job that cannot answer a prompt, file: keeps the secret out of the command line. Create a temporary password file with a restrictive mode, use it, and remove it when the job finishes:
$ umask 077
$ printf '%s\n' 'TEST-ONLY-passphrase' > /tmp/openssl-passphrase.secret
$ chmod 600 /tmp/openssl-passphrase.secret
$ openssl enc -d -aes-256-cbc -pbkdf2 \
-pass file:/tmp/openssl-passphrase.secret \
-in /tmp/openssl-passphrase-cipher \
-out /tmp/openssl-passphrase-file.dec
$ cmp /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-file.dec
$ rm /tmp/openssl-passphrase.secret
The file source reads its first line. A newline terminates that password; extra lines are not a way to supply a longer passphrase to this invocation. A file, device or named pipe can be used as the pathname, but every reader of a pipe must be considered part of the security boundary.
An environment variable avoids a password file but is not automatically private:
$ export OPENSSL_TEST_PASS='TEST-ONLY-passphrase'
$ openssl enc -d -aes-256-cbc -pbkdf2 \
-pass env:OPENSSL_TEST_PASS \
-in /tmp/openssl-passphrase-cipher \
-out /tmp/openssl-passphrase-env.dec
$ cmp /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-env.dec
$ unset OPENSSL_TEST_PASS
Unset the variable after use, but do not claim that this erases every copy from process memory or shell history. For a high-value secret, prefer an interactive prompt or a dedicated secret-delivery mechanism supplied by your service platform.
5. Pass a password through a descriptor or standard input
fd: is useful when a parent process already has a pipe. In Bash, descriptor 3 can be attached for one command without putting the secret in the argument list:
$ openssl enc -d -aes-256-cbc -pbkdf2 \
-pass fd:3 \
-in /tmp/openssl-passphrase-cipher \
-out /tmp/openssl-passphrase-fd.dec \
3<<'PASSWORD'
TEST-ONLY-passphrase
PASSWORD
$ cmp /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-fd.dec
The here-document is convenient for a disposable test, not for a real secret. A production wrapper can connect descriptor 3 to a protected pipe instead. On Unix-like systems, descriptor numbers are per-process handles. On Windows, the OpenSSL manpage says fd: is not supported.
stdin reads the password from standard input. Keep the encrypted data on -in or another descriptor so the two streams do not collide:
$ printf '%s\n' 'TEST-ONLY-passphrase' | \
openssl enc -d -aes-256-cbc -pbkdf2 -pass stdin \
-in /tmp/openssl-passphrase-cipher \
-out /tmp/openssl-passphrase-stdin.dec
$ cmp /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-stdin.dec
6. Avoid the common traps
pass:... is easy to type and easy to leak. Unix process listings can expose command arguments, so never use a real secret there. Environment variables can also be visible to other processes, depending on the operating system and permissions. A protected file can remain on disk after the command exits, so remove it or use your platform's secret store. A named pipe avoids a persistent file but can block or deliver the password to the wrong reader.
Do not confuse -passin and -passout when using commands that read and write separate passphrases, such as key-management commands. If the same file: pathname is supplied to both, OpenSSL consumes the first line for input and the next line for output. That is an intentional two-line protocol, not a default password-file format you should guess at.
When an operation fails, check the source first: the variable may be unset, the file may be unreadable, the descriptor may not be open, or the password may contain an unexpected newline. Inspect paths and permissions without printing the secret. If a failed decryption wrote output, treat that output as untrusted and remove it after preserving any evidence you actually need.
Done means
- You checked the OpenSSL binary and version used by your shell.
- You can explain the difference between
pass:,env:,file:,fd:andstdin. - You kept real passphrases out of command arguments and disposable examples out of permanent storage.
- You verified a decryption with
cmp, rather than trusting a successful-looking command alone. - You understand that file and stream sources consume the first line, and that the same file can provide successive lines for input and output passwords.
- You removed the temporary test files when finished:
rm -f /tmp/openssl-passphrase-plain /tmp/openssl-passphrase-cipher /tmp/openssl-passphrase-plain.dec /tmp/openssl-passphrase-file.dec /tmp/openssl-passphrase-env.dec /tmp/openssl-passphrase-fd.dec /tmp/openssl-passphrase-stdin.dec.