Hiding Files in PNG Images with OpenSSL
This article explains three approaches to securely encrypt files using Bash and OpenSSL, including a more "paranoid" technique that allows you to hide encrypted files inside a PNG image.
All solutions work on Linux and macOS.
1. Quick & Dirty — Plain OpenSSL, No Scripts
Just want to encrypt a file fast without any scripts? These are the raw OpenSSL commands.
Encrypt a file
openssl enc -aes-256-cbc -pbkdf2 -salt -in secret.txt -out secret.txt.enc
OpenSSL will ask for a password. The result is secret.txt.enc.
Decrypt a file
openssl enc -d -aes-256-cbc -pbkdf2 -in secret.txt.enc -out secret.txt
Workflow
graph LR
A[secret.txt] --> B[openssl enc]
B --> C[secret.txt.enc]
C --> D[openssl enc -d]
D --> A
2. Simple File Encryption With Scripts
Wrap the OpenSSL commands in reusable scripts.
Example
Encrypt a file:
./encrypt.sh secret.txt
Result:
secret.txt.enc
Decrypt the file:
./decrypt.sh secret.txt.enc
Result:
secret.txt
Workflow
graph LR
A[Original File] --> B[Encrypt AES-256-CBC + PBKDF2]
B --> C[Encrypted File]
C --> D[Decrypt]
D --> A
Scripts
encrypt.sh
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 file_to_encrypt"
exit 1
fi
FILE="$1"
OUT="$FILE.enc"
openssl enc -aes-256-cbc -pbkdf2 -salt -in "$FILE" -out "$OUT"
echo "Encrypted file: $OUT"
decrypt.sh
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 file_to_decrypt"
exit 1
fi
FILE="$1"
OUT="${FILE%.enc}"
openssl enc -d -aes-256-cbc -pbkdf2 -in "$FILE" -out "$OUT"
echo "Decrypted file: $OUT"
3. Paranoid File Encryption With PNG Embedding
This technique encrypts files and then appends them to a PNG image.
The PNG image still opens normally, but secretly contains encrypted data.
Example
Hide files inside PNG:
./encrypt_into_png.sh image.png secret1.txt secret2.pdf
Result:
hidden_image.png
Extract hidden files:
./extract_from_png.sh hidden_image.png
Result:
secret1.txt
secret2.pdf
Workflow
graph TD
A[Original PNG] --> B[Encrypt secret1.txt]
A --> C[Encrypt secret2.pdf]
B --> D[Append encrypted data]
C --> D
D --> E[Hidden PNG]
E --> F[Extract data]
F --> G[Decrypt secret1.txt]
F --> H[Decrypt secret2.pdf]
Scripts
encrypt_into_png.sh
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 2 ]; then
echo "Usage: $0 source.png file1 [file2 ...]"
exit 1
fi
PNG="$1"
shift
OUT="hidden_$PNG"
cp "$PNG" "$OUT"
get_file_size() {
local file="$1"
if [[ "$(uname)" == "Darwin" ]]; then
stat -f%z "$file"
else
stat -c%s "$file"
fi
}
for FILE in "$@"; do
ENC="$FILE.enc"
openssl enc -aes-256-cbc -pbkdf2 -salt -in "$FILE" -out "$ENC"
FNAME_LEN=$(echo -n "$FILE" | wc -c)
DATA_LEN=$(get_file_size "$ENC")
{
echo -n "MAGICHID"
printf "%04d" "$FNAME_LEN"
echo -n "$FILE"
printf "%010d" "$DATA_LEN"
cat "$ENC"
} >> "$OUT"
rm "$ENC"
echo "Added $FILE"
done
echo "Output file: $OUT"
extract_from_png.sh
#!/usr/bin/env bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 hidden_file.png"
exit 1
fi
PNG="$1"
MAGIC="MAGICHID"
while true; do
POS=$(grep -aob "$MAGIC" "$PNG" | head -n1 | cut -d: -f1 || true)
if [ -z "$POS" ]; then
echo "No more hidden files found"
break
fi
OFFSET=$((POS + ${#MAGIC}))
FNAME_LEN=$(dd if="$PNG" bs=1 skip="$OFFSET" count=4 2>/dev/null)
FNAME_LEN_NUM=$((10#$FNAME_LEN))
OFFSET=$((OFFSET+4))
FNAME=$(dd if="$PNG" bs=1 skip="$OFFSET" count="$FNAME_LEN_NUM" 2>/dev/null)
OFFSET=$((OFFSET+FNAME_LEN_NUM))
DATA_LEN=$(dd if="$PNG" bs=1 skip="$OFFSET" count=10 2>/dev/null)
DATA_LEN_NUM=$((10#$DATA_LEN))
OFFSET=$((OFFSET+10))
dd if="$PNG" bs=1 skip="$OFFSET" count="$DATA_LEN_NUM" of="$FNAME.enc" 2>/dev/null
openssl enc -d -aes-256-cbc -pbkdf2 -in "$FNAME.enc" -out "$FNAME"
rm "$FNAME.enc"
echo "Extracted $FNAME"
OFFSET=$((OFFSET+DATA_LEN_NUM))
tail -c +$OFFSET "$PNG" > tmpfile && mv tmpfile "$PNG"
done
Security Notes
- Always use strong passwords.
- Remove metadata from files before encryption.
- Test extraction before deleting originals.
- Store backups of encrypted data.