How to Create a Bash File Decryption Tool

How to Create a Bash File Decryption Tool

How to Create a Bash File Decryption Tool

After a scare where a sensitive backup archive of mine sat unencrypted on a cloud drive for longer than I was comfortable with, I decided to build a proper encryption and decryption workflow using nothing but Bash and OpenSSL. What started as a single command grew into a small toolset I now use for every sensitive file I need to store or transfer. This article covers the decryption side in depth, along with the encryption context needed to understand it fully.

Why Build This in Bash

Encryption and decryption logic itself should never be hand-rolled — that’s a job for well-audited, standard cryptographic libraries. Bash’s role here isn’t to implement cryptography, but to orchestrate trusted tools like openssl or gpg reliably:

The Tool Behind It: OpenSSL

OpenSSL ships with virtually every Linux distribution and macOS by default, and its command-line interface supports strong symmetric encryption suitable for file-level protection.

Check that it’s available:

openssl version

Step 1: Encrypting a File (Context for Decryption)

Since decryption only makes sense with matching encryption, here’s the encryption side first:

openssl enc -aes-256-cbc -salt -pbkdf2 -in secret.txt -out secret.txt.enc

You’ll be prompted to enter a passphrase, which is used to derive the actual encryption key.

Breaking Down the Flags

Step 2: Decrypting a File

openssl enc -d -aes-256-cbc -pbkdf2 -in secret.txt.enc -out secret.txt

A Basic Bash Decryption Script

#!/bin/bash

set -euo pipefail

usage() {
    echo "Usage: $0 <encrypted_file> <output_file>"
    exit 1
}

if [ "$#" -ne 2 ]; then
    usage
fi

encrypted_file="$1"
output_file="$2"

if [ ! -f "$encrypted_file" ]; then
    echo "Error: encrypted file '$encrypted_file' not found." >&2
    exit 1
fi

openssl enc -d -aes-256-cbc -pbkdf2 -in "$encrypted_file" -out "$output_file"

echo "Decrypted successfully: $output_file"

How This Works Internally

Decrypting Without an Interactive Prompt (Using an Environment Variable)

For automated pipelines, prompting for a passphrase interactively isn’t practical. You can pass it via an environment variable instead:

#!/bin/bash

set -euo pipefail

encrypted_file="$1"
output_file="$2"

if [ -z "${DECRYPT_PASSPHRASE:-}" ]; then
    echo "Error: DECRYPT_PASSPHRASE environment variable is not set." >&2
    exit 1
fi

openssl enc -d -aes-256-cbc -pbkdf2 -pass env:DECRYPT_PASSPHRASE -in "$encrypted_file" -out "$output_file"

echo "Decrypted successfully: $output_file"

Usage:

export DECRYPT_PASSPHRASE="my-secure-passphrase"
./decrypt.sh secret.txt.enc secret.txt
unset DECRYPT_PASSPHRASE

Why -pass env:DECRYPT_PASSPHRASE

The -pass flag tells OpenSSL where to source the passphrase from instead of prompting interactively. env:VARNAME reads it from the named environment variable. This is far safer than passing a passphrase directly as a command-line argument, since command-line arguments are often visible to other users on the system via ps aux.

Batch-Decrypting Multiple Files

#!/bin/bash

set -euo pipefail

if [ -z "${DECRYPT_PASSPHRASE:-}" ]; then
    echo "Error: DECRYPT_PASSPHRASE environment variable is not set." >&2
    exit 1
fi

output_dir="decrypted"
mkdir -p "$output_dir"

for enc_file in *.enc; do
    [ -e "$enc_file" ] || continue
    base_name="${enc_file%.enc}"
    openssl enc -d -aes-256-cbc -pbkdf2 -pass env:DECRYPT_PASSPHRASE -in "$enc_file" -out "$output_dir/$base_name"
    echo "Decrypted: $enc_file -> $output_dir/$base_name"
done

echo "Batch decryption complete."

This decrypts every .enc file in the current directory using a single shared passphrase, writing outputs into a dedicated decrypted/ folder.

Verifying Integrity After Decryption

Encryption alone doesn’t guarantee the decrypted file is intact — pairing it with a checksum, generated before encryption, gives you confidence the round trip succeeded:

#!/bin/bash

set -euo pipefail

encrypted_file="$1"
expected_checksum_file="$2"
output_file="${encrypted_file%.enc}"

openssl enc -d -aes-256-cbc -pbkdf2 -pass env:DECRYPT_PASSPHRASE -in "$encrypted_file" -out "$output_file"

if sha256sum -c "$expected_checksum_file"; then
    echo "Integrity check passed."
else
    echo "Warning: integrity check failed!" >&2
    exit 1
fi

This assumes a checksum file was generated at encryption time (sha256sum original_file > checksum.sha256) and shipped alongside the encrypted file for later verification.

Using GPG as an Alternative

For asymmetric encryption (public/private key pairs) rather than a shared passphrase, GPG is often a better fit:

# Decrypting a GPG-encrypted file
gpg --output secret.txt --decrypt secret.txt.gpg

GPG will prompt for your private key’s passphrase (if it’s protected by one) and use your locally stored private key to decrypt the file — no shared secret needs to be communicated at all, which is a meaningful advantage over symmetric approaches like the OpenSSL example above.

Real-World Use Cases

Automation Example

Here’s a deployment script that decrypts a secrets file just before starting an application, then cleans it up immediately after:

#!/bin/bash

set -euo pipefail

encrypted_secrets="/opt/app/secrets.env.enc"
decrypted_secrets="/opt/app/secrets.env"

if [ -z "${DEPLOY_PASSPHRASE:-}" ]; then
    echo "Error: DEPLOY_PASSPHRASE not set." >&2
    exit 1
fi

openssl enc -d -aes-256-cbc -pbkdf2 -pass env:DEPLOY_PASSPHRASE -in "$encrypted_secrets" -out "$decrypted_secrets"
chmod 600 "$decrypted_secrets"

# shellcheck disable=SC1090
source "$decrypted_secrets"

shred -u "$decrypted_secrets"

echo "Application starting with decrypted secrets loaded into environment..."
# exec ./start_app.sh

This decrypts secrets just-in-time, restricts file permissions immediately, sources the values into the current shell environment, and then securely deletes the plaintext file using shred -u rather than leaving it sitting on disk.

Best Practices

Security Considerations

Optimization Tips

Troubleshooting Common Issues

Problem: bad decrypt error during decryption. This almost always means either the wrong passphrase was supplied, or the cipher/KDF flags don’t match what was used during encryption (e.g., forgetting -pbkdf2 on one side but not the other).

Problem: Decrypted file is empty or corrupted. Verify the encrypted file wasn’t truncated during transfer (check its size or checksum against the original encrypted output), and confirm the exact same OpenSSL flags were used on both ends.

Problem: Script hangs waiting for input. This happens when no passphrase source is provided and OpenSSL is waiting on an interactive prompt in a non-interactive (e.g., cron or CI) context — use -pass env:VARNAME or -pass file:path instead.

Problem: “unknown option” errors from OpenSSL. This typically indicates a version mismatch — very old OpenSSL versions may not support -pbkdf2; check your installed version with openssl version and consider upgrading.

Common Mistakes to Avoid

Frequently Asked Questions

Is AES-256-CBC still considered secure in 2026? Yes, AES-256 remains a strong, widely trusted cipher when combined with proper key derivation (-pbkdf2) and a strong passphrase; the security relies more on passphrase strength and correct usage than the cipher choice itself.

What’s the difference between using OpenSSL and GPG for this? OpenSSL’s enc command uses symmetric encryption with a shared passphrase, while GPG typically uses asymmetric public/private key pairs (though it also supports symmetric mode) — GPG is generally preferable when multiple parties need independent decryption access without sharing a single secret.

Can I decrypt a file without knowing which cipher was originally used? Not directly — you need to know (or record) which cipher and KDF flags were used at encryption time; without that information, decryption will fail even with the correct passphrase.

How do I securely share the passphrase with someone else who needs to decrypt the file? Never send it over the same channel as the encrypted file itself; use a separate, trusted channel (in person, a password manager’s sharing feature, or an encrypted messaging app) to communicate it.

What happens if I forget the passphrase? The file is effectively unrecoverable — there is no “reset” mechanism for properly implemented symmetric encryption, which is precisely the point of strong encryption, but also why passphrase management matters so much.

Summary

A Bash-based file decryption tool built around OpenSSL wraps proven, standard cryptographic primitives rather than reinventing them, while Bash itself handles the orchestration: argument parsing, batch processing, integrity verification, and secure cleanup of plaintext afterward. The details that matter most: never expose passphrases as plain command-line arguments, always match your cipher and KDF flags exactly between encryption and decryption, and treat any decrypted plaintext file as a temporary artifact that should be securely removed the moment it’s no longer needed.

References

Exit mobile version