23

I'm writing a bash script, and need to ask user for his password and pass it to openssl. Whilst openssl can read the password itself, I need for two runs of the program and don't want to ask the user twice. Here is the script:

cp file{,.old}
read -sp 'Enter password. ' PASS; echo
export PASS

# decode | edit | encode
openssl enc -d -aes-256-cbc -k "$PASS" -in file.old | \
  sed ... | openssl enc -e -aes-256-cbc -k "$PASS" -out file

unset PASS

This is not safe as the password is easily available by looking at the command line; somebody can read it using ps, for example.

openssl can read a password from an environment variable, so I can replace -k "$PASS" with -pass env:PASS, but it's still not safe; the environment variables of any process can be read freely (again, ps can do it).

So, how can I safely pass the password to the two openssl instances?

Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • combination of GnuPG and PinEntry can be used here http://www.gnupg.org/related_software/pinentry/index.en.html – Nikhil Mulley Jan 14 '12 at 12:37
  • 1
    "the environment variables of any process can be read freely" - this is not entirely correct. ps reads the environment of a process from /proc/<pid>/environ, but this file has 0600 permissions, so only root and the user running the process are able to read the environment of the process. I'd say that's pretty safe. – Martin von Wittich Dec 02 '16 at 17:01

4 Answers4

17

Pass the password on a separate file descriptor from the input (twice, once for encryption and once for decryption). Do not export PASS to the environment.

read -sp 'Enter password. ' PASS
printf '%s\n' "$PASS" |
openssl enc -d -aes-256-cbc -kfile /dev/stdin -in file.old |
sed ... | {
  printf '%s\n' "$PASS" |
  openssl enc -e -aes-256-cbc -kfile /dev/stdin -in /dev/fd/3 -out file;
} 3<&0

If your system doesn't have /dev/fd, you can use the -pass argument to tell openssl to read the passphrase from an open file descriptor.

printf '%s\n' "$PASS" | {
  printf '%s\n' "$PASS" |
  openssl enc -d -aes-256-cbc -pass fd:0 -in file.old |
  tr a-z A-Z | tee /dev/tty | {
  openssl enc -e -aes-256-cbc -pass fd:3 -out file; }
} 3<&0
  • As I understand from another your answer, in the bash version with env:PASS is safe too. –  Jan 15 '12 at 06:32
  • printf '%s\n' "$PASS" isn't safe. Somebody can read the command line with ps for example. –  Jan 15 '12 at 06:38
  • 8
    @user14284 No, and no. env:PASS is not safe because the password would appear in the environment of the openssl process (it wouldn't appear in the environment of the bash process, but that's not enough). Using printf is safe because it's a bash built-in. – Gilles 'SO- stop being evil' Jan 15 '12 at 17:01
  • echo is a bash built in, so would not a simple echo command be safe? echo $PASS | openssl .... It would not appear in ps listing. The only place you can get pass would be in bash process memory. I think ? – gaoithe Dec 09 '19 at 17:03
  • 1
    @gaoithe Yes, echo would be safe for the same reason printf is safe (and printf would not be safe in a shell where it is not built in). The reason I use printf and not echo is that echo may mangle backslashes (depending on the bash options). – Gilles 'SO- stop being evil' Dec 09 '19 at 18:08
9

Using Bash it can be done without using printf '%s\n' "$PASS" by associating a so-called here string with file descriptors using the Bash builtin exec command.

For more information see: Shell script password security of command-line parameters.

(

# sample code to edit password-protected file with openssl
# user should have to enter password only once
# password should not become visible using the ps command

echo hello > tmp.file

#env -i bash --norc   # clean up environment
set +o history
unset PASS || exit 1

read -sp 'Enter password. ' PASS; echo

# encrypt file and protect it by given password
exec 3<<<"$PASS"
openssl enc -e -aes-256-cbc -pass fd:3  -in tmp.file -out file

cp file{,.old}

# decode | edit | encode
exec 3<<<"$PASS" 4<<<"$PASS"
openssl enc -d -aes-256-cbc -pass fd:3 -in file.old | 
   sed 's/l/L/g' | 
   openssl enc -e -aes-256-cbc -pass fd:4 -out file

exec 3<<<"$PASS"
openssl enc -d -aes-256-cbc -pass fd:3 -in file

rm -P tmp.file file.old
unset PASS

)
jon
  • 91
1

Sorry, my previous answer was from openssl man, not the openssl enc docs.

This solution is not a pipeline, but I believe this solution prevents the password from being visible to ps.

Using a here document, only openssl sees the text of the password.
As long as you're certain to eliminate the intermediate file, no trace remains. Maybe someone can help do this in a pipeline and eliminate the intermediate file?

# cp file{,.old}  don't need this anymore since intermediate becomes same
read -sp 'Enter password. ' PASS; echo
#no need to export, env's are readable, as mentioned

# decode into intermediate file
openssl <<HERE 2>&1 >/dev/null
enc -d -aes-256-cbc -k "$PASS" -in file -out intermediate
HERE

# edit intermediate

# encode intermediate back into file
openssl <<HERE 2>&1 >/dev/null
enc -e -aes-256-cbc -k "$PASS" -in intermediate -out file 
HERE
unset PASS
rm -f intermediate
bsd
  • 11,036
  • This would be a better answer if it explained how to use the switch. It's not wrong (except that the enc command has no -kn switch, at least on current versions, it's -pass), but not very informative. (The downvote isn't mine.) – Gilles 'SO- stop being evil' Jan 15 '12 at 02:25
  • Thanks @Gilles, looked at the docs and saw my blunder, updated answer with a different approach. – bsd Jan 15 '12 at 12:54
0

I created this little bash script to do a complete reset preserving the config file. The forced push effectively recreates your GitHub repo.

#!/bin/bash
tfile=$(mktemp /tmp/config.XXXXXXXXX)
GITCONF=".git/config"
commitmsg=${1:-git repository initialised}
if [ -f $GITCONF ]; then
   mv .git/config tfile
   rm -rf .git
   git init .
   mv tfile .git/config
   git add .
   git commit -a -m "${commitmsg}"
   git push -f
else
   echo "Warning: No git config file found. Aborting.";exit;
fi
RichieHH
  • 99
  • 3