A question every DBA hears sooner or later: "This script has the database password in it. Can we encrypt it so people who run it can't read it?" On Linux the usual answer is shc, the shell script compiler. It turns a script into a binary, so cat and vi show nothing useful.
In this post we install shc, compile a script, add an expiry date, and then test the part most guides skip: can another user still see the password? Every output below comes from a real test on shc 4.0.3, Ubuntu 24.04 and bash 5.2.
/proc while the binary was running. Section 08 shows what to use instead.01What shc is (and is not)
shc was written by Francisco Rosales and is now maintained as an open-source project on GitHub (neurobin/shc, GPL-3.0). Despite the name, it is not a real compiler. It works in two steps:
- It encrypts your script with the RC4 cipher and embeds it, together with the key, in a generated C program (
script.x.c). - It compiles that C program with
ccinto a binary (script.x).
When you run the binary, it decrypts the script in memory and hands it to the shell named on the first line (#!/bin/bash). So the binary still needs bash on the server, and the decryption key travels inside the binary itself.
02Install shc
The old download site used in many tutorials no longer works. Install it from your distribution instead:
| Platform | Command |
|---|---|
| Ubuntu / Debian | sudo apt install shc |
| Oracle Linux / RHEL / Rocky (EPEL) | sudo dnf install epel-release (on Oracle Linux: oracle-epel-release-el8 or -el9) then sudo dnf install shc |
| Fedora | sudo dnf install shc |
| From source | git clone https://github.com/neurobin/shc.git → ./configure && make && sudo make install |
shc also needs a C compiler (gcc) on the machine where you compile. Check the version:
$ shc -h | head -1 shc Version 4.0.3, Generic Shell Script Compiler
03Compile a script
Our test script does exactly what the question describes: it contains a password. (Never do this for real; it is here only to test shc.)
#!/bin/bash
# db_check.sh - demo script that (badly) contains a password
DB_USER="report_user"
DB_PASS="S3cr3t#Demo2026"
echo "Connecting as ${DB_USER} ..."
sleep "${1:-0}"
echo "Check complete on $(hostname) at $(date '+%F %T')"
$ shc -f db_check.sh $ ls -l db_check.sh* -rwx------ 1 appuser appuser 230 Oct 28 05:56 db_check.sh -rwxrwxr-x 1 appuser appuser 15384 Oct 28 05:56 db_check.sh.x -rw-rw-r-- 1 appuser appuser 18909 Oct 28 05:56 db_check.sh.x.c
| File | What it is |
|---|---|
db_check.sh | Your original script, unchanged |
db_check.sh.x | The compiled binary that you deploy |
db_check.sh.x.c | Generated C source with the RC4-encrypted script and its key. Delete it; it is all someone needs to rebuild the binary. |
rwxrwxr-x (775). Every user on the server can run it and copy it. Fix that before anything else (section 07).$ file db_check.sh.x db_check.sh.x: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, stripped $ ./db_check.sh.x Connecting as report_user ... Check complete on dbhost01 at 2026-10-28 05:56:55 $ strings db_check.sh.x | grep -c S3cr3t 0
So far so good: the binary runs exactly like the script, and strings finds no password. This is where most tutorials stop.
04Set an expiry date
shc can stop a binary from running after a date (-e, format dd/mm/yyyy) and show your own message (-m). It is handy for scripts you hand out for a fixed period, such as a migration window. We used a past date to see the result:
$ shc -e 31/12/2025 -m "Expired: contact the DBA team for a new version" -f db_check.sh -o db_check_exp $ ./db_check_exp ./db_check_exp: has expired! Expired: contact the DBA team for a new version $ echo $? 1
The check uses the server clock, so it is a convenience, not a licence lock: anyone who can change the system date can get around it.
05shc 4.x options
Several older tutorials use -T to make the binary traceable. That option no longer exists: in shc 4.x binaries are traceable by default, and -U makes them untraceable.
| Option | What it does |
|---|---|
-f file | Script to compile (required) |
-o file | Output file name (default: script.x) |
-e dd/mm/yyyy | Expiry date |
-m "text" | Message shown after expiry |
-r | Relax security: make a redistributable binary for other servers with the same OS (by default it is tied to the shell binary of the build host) |
-U | Untraceable: refuses to run under strace / gdb |
-H | Hardening: extra protection against dumping and ptrace. Works only with /bin/sh scripts and does not support arguments |
-v | Verbose: shows the cc and strip commands it runs |
-S | setuid for root-callable programs. Avoid this unless you fully understand the risk |
-B | Compile for busybox |
06The password test: what other users can see
Now the real question. We copied the binary to the home of appuser, ran it there (with a 5-second sleep so it stays alive), and from a different, unprivileged account looked at the process:
$ ./db_check.sh.x 5 &
$ tr '\0' '\n' < /proc/309/cmdline
./db_check.sh.x
-c
#!/bin/bash
# db_check.sh - demo script that (badly) contains a password
DB_USER="report_user"
DB_PASS="S3cr3t#Demo2026"
echo "Connecting as ${DB_USER} ..."
The whole decrypted script, password included. shc passes the script to bash as a command-line argument (bash -c "<script>"). ps shows only padding, but /proc/<pid>/cmdline is readable by every local user by default. We repeated the test for each build mode:
| Build | Script visible in /proc/<pid>/cmdline? |
|---|---|
default (shc -f) | Yes, full script with password |
-U untraceable | Yes, full script with password |
-H hardening (/bin/sh, no arguments) | No, cmdline was empty |
Even with -H, remember that the RC4 key is inside the binary. Anyone who can copy the binary can study it at leisure, and decoders for older shc versions have been published. shc raises the effort; it does not make a secret safe.
07If you still use shc
shc is fine for its real purpose: keeping the logic of a script away from casual eyes, or shipping a script with an expiry date. If you use it, do it like this:
shc -H -f job.sh -o job # use -H when the script is plain sh with no arguments rm -f job.sh.x.c # never leave the generated C source behind chmod 700 job # owner only (711 if others must run it, they still can't copy it) # keep job.sh in your source repository, not on the server
# hide other users' processes from each other (test monitoring tools first) mount -o remount,hidepid=2 /proc # make it permanent in /etc/fstab: proc /proc proc defaults,hidepid=2 0 0
gid= mount option).08Better ways to handle passwords in scripts
The real fix is to keep the password out of the script. Every major database already has a standard place for it, protected by file permissions:
| Database / tool | Store the password in | Script then runs |
|---|---|---|
| Oracle | Secure External Password Store (wallet) | sqlplus /@PRODDB |
| PostgreSQL | ~/.pgpass (chmod 600) | psql -h dbhost -U report_user -d appdb |
| MySQL / MariaDB | mysql_config_editor login path | mysql --login-path=report |
| Any script | Config file owned by a service account, chmod 600 | sudo -u svc_report /opt/scripts/job.sh |
| Cloud / enterprise | HashiCorp Vault, Azure Key Vault, AWS Secrets Manager | fetch the secret at run time with a machine identity |
Example: Oracle wallet
mkdir -p /u01/app/oracle/wallet mkstore -wrl /u01/app/oracle/wallet -create mkstore -wrl /u01/app/oracle/wallet -createCredential PRODDB report_user # mkstore prompts for the passwords, so they never appear in shell history or ps
WALLET_LOCATION = (SOURCE = (METHOD = FILE) (METHOD_DATA = (DIRECTORY = /u01/app/oracle/wallet))) SQLNET.WALLET_OVERRIDE = TRUE
sqlplus -s /@PRODDB <<'EOF' SELECT name, open_mode FROM v$database; EOF
Example: PostgreSQL .pgpass
echo 'dbhost:5432:appdb:report_user:YourStrongPassword' >> ~/.pgpass chmod 600 ~/.pgpass psql -h dbhost -U report_user -d appdb -c 'SELECT now();' # no password prompt
/proc exactly as shown above.09FAQ
Is shc real encryption?
It uses RC4, but the key is stored in the same binary, and the script is decrypted every time it runs. Treat it as obfuscation, not encryption.
Can I move the binary to another server?
Only if it was built with -r, and the target needs the same OS family and the shell from the first line.
Does shc make my script faster?
No. The binary decrypts the script and runs it with the normal shell, so speed is the same (plus a tiny start-up cost).
Then why use it at all?
To keep business logic away from casual readers, or to give out a script that stops working after a date. For secrets, use a wallet, .pgpass or a vault.
Originally published at techstaradmin.blogspot.com.

