58

Suppose that I were using sha1pass to generate a hash of some sensitive password on the command line. I can use sha1pass mysecret to generate a hash of mysecret but this has the disadvantage that mysecret is now in the bash history. Is there a way to accomplish the end goal of this command while avoiding revealing mysecret in plain text, perhaps by using a passwd-style prompt?

I'm also interested in a generalized way to do this for passing sensitive data to any command. The method would change when the sensitive data is passed as an argument (such as in sha1pass) or on STDIN to some command.

Is there a way to accomplish this?


Edit: This question attracted a lot of attention and there have been several good answers offered below. A summary is:

  • As per @Kusalananda's answer, ideally one would never have to give a password or secret as a command-line argument to a utility. This is vulnerable in several ways as described by him, and one should use a better-designed utility that is capable of taking the secret input on STDIN
  • @vfbsilva's answer describes how to prevent things from being stored in bash history
  • @Jonathan's answer describes a perfectly good method for accomplishing this as long as the program can take its secret data on STDIN. As such, I've decided to accept this answer. sha1pass in my OP was just an example, but the discussion has established that better tools exist that do take data on STDIN.
  • as @R.. notes in his answer, use of command expansion on a variable is not safe.

So, in summary, I've accepted @Jonathan's answer since it's the best solution given that you have a well-designed and well-behaved program to work with. Though passing a password or secret as a command-line argument is fundamentally unsafe, the other answers provide ways of mitigating the simple security concerns.

cemulate
  • 841
  • 6
    Not only that: Anybody on the same machine with permission to list running processes can potentially see that sha1pass mysecret is running, and hence know what mysecret is. (This only works for the few seconds while the program is actually running, of course...) – MathematicalOrchid Apr 23 '18 at 15:35
  • @MathematicalOrchid This could be avoided by running in a private virtual machine. But that may be too much work to set up to generate only a single password... :-) – Kusalananda Apr 23 '18 at 15:41
  • 5
    @Kusalananda My point was more "don't ever put sensitive data on the command line, even if you figure out how to turn off command history"... – MathematicalOrchid Apr 23 '18 at 16:14
  • @cemulate, please review and consider the comments on your accepted answer. It does keep sensitive content out of .bash_history, but does not prevent other processes from scraping that content out of procfs. – Charles Duffy Apr 23 '18 at 17:59
  • 2
    Just so you know, SHA-1 is deprecated for key or password hashes since quite a few years now. – David Foerster Apr 23 '18 at 21:20
  • @CharlesDuffy This question really blew up! I'm currently reviewing all the answers and comments. Just to be clear, if this were that big a secret I wouldn't be handling it on the command line anyway, I would use a proper tool for managing encrypted data. I'm just looking for a simple way to avoid exposing the secret in plain text in the shell or shell history, not necessarily protect against serious attempts to compromise it. – cemulate Apr 24 '18 at 03:41
  • After some discussions in other comment threads are resolved, I'll make a proper edit to the question to warn future viewers about security vulnerabilities, even if a more simple but vulnerable solution may suit my needs. – cemulate Apr 24 '18 at 03:48
  • 2
    Note that if the system is running an audit daemon, all commands and all arguments by all users are logged centrally by root, so anyone who can access those logs will see this. – Randall Apr 24 '18 at 09:57
  • If sha1passwd has no support for stdin, I'd suggest looking for another tool. Maybe mkpasswd (https://unix.stackexchange.com/a/198906/70524a) – muru Apr 24 '18 at 11:49

8 Answers8

47

Ideally, you never type a clear-text password on the command line as an argument to a command. Doing so makes the password an argument to the command, and command line arguments may be seen in the process table by using simple tools like ps or logged into some audit logs.

Having said that, there are certainly ways of hiding the actual password from the shell's command history.

sha1pass "$( head -n 1 )"

Then type in the password and press Enter. The head command used here accept exactly one line of input and the last newline that you type will not be part of the data that is passed to sha1pass.

To prevent the characters from echoing:

sha1pass "$( stty -echo; head -n 1; stty echo )"

The stty -echo command turns off echoing of the typed characters on the terminal. The echoing is then restored with stty echo.

To pass on standard input, that last command could be altered (you would have done this if sha1pass accepted data on standard input, but appears as if this particular utility is ignoring its standard input):

{ stty -echo; head -n 1; stty echo; } | somecommand

If you need multi-line input (the above assumes a single line should be passed, with no newline character at the end), then replace the whole head command with cat and terminate the input (assuming somecommand itself reads until end-of-file) with Ctrl+D (following Return if you want to include a newline character in the input, or twice if not).

This would work regardless of what shell you were using (as long as it was a Bourne-like or rc-like shell).

Some shells may be made to not save the typed-in commands in their history files if the command is preceded by a space. This usually involves having to set HISTCONTROL to the value ignorespace. This is supported by at least bash and ksh on OpenBSD, but not by e.g. ksh93 or dash. zsh users may use the histignorespace option or their HISTORY_IGNORE variable to define a pattern to ignore.

In shells that support reading with read without echoing characters to the terminal, you may also use

IFS= read -rs password     # -s turns off echoing in bash or zsh
                           # -r for reading backslashes as-is,
                           # IFS= to preserve leading and trailing blanks
sha1pass "$password"

but this obviously still has the same issue with potentially revealing the password in the process table.

If the utility reads from standard input, and if the shell supports "here-strings", the above could be changed to

IFS= read -rs password
somecommand <<<"$password"

Summary of comments below:

  • Executing a command with a password given on the command line, which all of the commands above does, except the one that pipes the data to the command, will potentially make the password visible to anyone running ps at the same time. None of the commands above will however save the typed-in password in the shell's history file if executed from an interactive shell.

  • Well behaved programs that reads clear-text passwords do so by reading from their standard input, from a file, or directly from the terminal.

  • sha1pass does requires the password on the command line, either typed in directly or using some form of command substitution.

  • If at all possible, use another tool.

Kusalananda
  • 333,661
  • 11
    This is incorrect. The result of command expansion $(...) will be part of the command line and visible in ps output except on a system with hardened /proc. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 15:42
  • @R.. Yes, this is true. This is an issue with all programs that takes data on the command line, unless they overwrite this information themselves (some do, but I don't know about sha1pass). The issue here was rather to not store the command in the history file. – Kusalananda Apr 23 '18 at 15:45
  • 3
    @Kusalananda, even overwriting the argv after you started leaves a vulnerable window before that overwrite takes place; it's a mitigation, not a fix. – Charles Duffy Apr 23 '18 at 15:45
  • Use the shell command read. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 15:47
  • @CharlesDuffy Unless sha1pass reads from standard input, it would be difficult to pass the password to it in any other way. – Kusalananda Apr 23 '18 at 15:53
  • 5
    @Kusalananda, no well-designed program for hashing passwords will require input on the command line, so that conditional is basically a given -- if it's not, one should choose a different tool. – Charles Duffy Apr 23 '18 at 15:54
  • @CharlesDuffy I know, but it doesn't make it easier for the OP if that is the command that he needs to use and it doesn't accept data from stdin. – Kusalananda Apr 23 '18 at 15:58
  • 4
    @CharlesDuffy: The tool here seems fundamentally broken. sha1pass run with no password on the command line seems to produce an output without reading anything... So OP needs to choose a different tool. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 15:59
  • @CharlesDuffy You would ave to do your own salting in that case. Another option would be to use openssl. – Kusalananda Apr 23 '18 at 16:03
  • 1
    If you'd like to also hide the fact that you even ran such a command by hiding it from (e.g. bash's) history, you could prefix your command with a space (given typical settings for ignorespace) – Jeff Schaller Apr 23 '18 at 16:56
  • 8
    This answer is unsafe from a security perspective, and the only advantage is that the password doesn't show up in the shell history, but that advantage is easier to accomplish by including a space in front of the command – Ferrybig Apr 23 '18 at 17:06
  • @Ferrybig I added a note about this. – Kusalananda Apr 23 '18 at 17:26
  • @CharlesDuffy: The sha1 "password hash" (KDF) is not just a sha1 hash but some iterative hashing with salt. I'm not sure exactly what procedure is used since sha1 was never in widespread use for this and is not supported by most crypt(3) implementations. Most use descrypt, bcrypt, and/or sha256/sha512-based KDFs. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 17:54
  • 1
    @Kusalananda Thanks for the answer, but I'm going to temporarily un-accept it until further discussions in the comment threads are resolved. I completely understand that a lot of solutions here still have security vulnerabilities, but it's important to note that I'm mainly attempting to conceal the data from the shell and shell history, not necessarily protect against serious attack vectors to compromise the secret data (for real such secrets, I would use dedicated tools for managing secure/encrypted data) – cemulate Apr 24 '18 at 03:46
  • You do whatever feels right, @cemulate. You are dealing with a utility that is flawed, and the discussions in the comments will not be resolved until someone comes up with a replacement for sha1pass. If there's any other aspects of your question that you believe that I have not addressed, I'm happy to further develop my answer. – Kusalananda Apr 24 '18 at 06:19
27

If using the zsh or bash shell, use the -s option to the read shell builtin to read a line from the terminal device without it echoing it.

IFS= read -rs VARIABLE < /dev/tty

Then you can use some fancy redirection to use the variable as stdin.

sha1pass <<<"$VARIABLE"

If anyone runs ps, all they'll see is "sha1pass".

That assumes that sha1pass reads the password from stdin (on one line, ignoring the line delimiter) when not given any argument.

Jonathan
  • 1,304
  • I believe that we established that sha1pass does not use its standard input stream. – Kusalananda Apr 23 '18 at 19:47
  • @Kusalananda Okay, but this method will work for programs that do read from stdin. – Jonathan Apr 24 '18 at 17:25
  • So, assuming the utility can take its secret input on STDIN, this is a sound and safe solution, security-wise? – cemulate Apr 24 '18 at 20:50
  • I've decided to accept this answer, given that it's the best solution given that you have a well-designed program to work with. sha1pass was just an example in my OP, and clearly not the best choice to use for this. – cemulate Apr 24 '18 at 21:00
  • @Jonathan would you consider editing your answer to note that using command expansion like "$VARIABLE" is not safe, as noted in @R's answer? I included this in my summary in the OP but it might help future viewers to have it here as well. – cemulate Apr 24 '18 at 21:10
  • 1
    @cemulate, ...not safe on a command line. In the context of a heredoc or herestring (as in the answer here), it's not exposed to ps, but may be written to a temporary file -- if one wants to avoid that, then sshpass < <(printf '%s\n' "$VARIABLE") might be considered (because printf is a builtin, not an external command, it isn't passed to execv and accessible via ps). – Charles Duffy Apr 25 '18 at 17:51
26

If you set HISTCONTROL like so:

HISTCONTROL=ignorespace

and start the command with a space:

~$  mycommand

it won't be stored in the history.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
vfbsilva
  • 3,697
10

Pass sensitive data via a pipe or here-doc:

command_with_secret_output | command_with_secret_input

or:

command_with_secret_input <<EOF
$secret
EOF

It's fine for secrets to be in (non-exported) shell variables, but you can never use those variables on command lines, only in here-documents and shell internals.

As noted by Kusalananda in a comment, if you're entering commands in an interactive shell, the lines you enter for a here document will be stored in the shell history, so it's not safe to type a password there, but it should still be safe to use shell variables containing secrets; the history will contain the text $secret rather than whatever $secret expanded to.

Use of command expansions is not safe:

command_with_secret_input "$(command_with_secret_output)"

because the output will be included on the command line and visible in ps output (or manually reading from /proc) except on systems with hardened /proc.

Assignment to a variable is okay too:

secret=$(command_with_secret_output)
  • What I'm looking for is an actual command command_with_secret_output that allows me to type the secret output. Does such a command exist that could be substituted here? – cemulate Apr 23 '18 at 15:50
  • Note that typing a here-document in an interactive bash session will save the document in the history file. – Kusalananda Apr 23 '18 at 15:50
  • @cemulate: Just use the shell builtin read, e.g. read -r secret. Then your secret is in $secret. You can run stty before/after it to hide the input if you like. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 15:58
  • @R.. And then? sha1pass "$secret"? How does that help? He want to do this in an interactive shell, not in a script. – Kusalananda Apr 23 '18 at 16:00
  • 3
    @Kusalananda: sha1pass seems fundamentally broken/unusable/unsafe since it can't read the password from stdin or a file. A different utility is needed to solve the problem, I think. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 16:03
  • @R.. Agreed, totally. – Kusalananda Apr 23 '18 at 16:05
  • FWIW, I think the Apache htpasswd utility does what OP wants and does it safely. – R.. GitHub STOP HELPING ICE Apr 23 '18 at 16:11
  • @Kusalananda why does this not help? read -s secret followed by sha1pass "$secret" works for me. I'm not quite sure I understand your qualm about interactive shell vs. a script – cemulate Apr 24 '18 at 03:42
  • @R.. this is a pretty simple solution. I see that use of command expansion is not safe - is this only in the sense that someone could read the output of ps and see the expanded command there? What do you mean by "output will be included on the command line"? – cemulate Apr 24 '18 at 03:44
  • 1
    @cemulate Your last comment to R is exactly on the spot. That's why it won't help. read -rs will do the job (if you include -r to be able to have passwords with backslashes in them), but then you're back at potentially showing off the password in the process list (and depending on whether process accounting is turned on and how that's configured, in the process accounting logs as well). – Kusalananda Apr 24 '18 at 06:31
  • Note that heredocs are written to temporary files. If we don't want a heredoc to touch disk, a process substitution (command_with_secret_input < <(printf '%s' "$var"), leveraging the builtin implementation of printf to avoid an execv visible from procfs) is safer. – Charles Duffy Apr 25 '18 at 18:21
  • On a modern system, /tmp is not on a disk, it's tmpfs, and "not touching disk" is usually an unreasonable requirement because of swap. Surely a shell implementation could use O_TMPFILE or memfd_create for heredocs if it wanted to be slightly more hardened, though. – R.. GitHub STOP HELPING ICE Apr 25 '18 at 19:09
6

Just write the value into a file and pass the file:

$ cat > mysecret
Big seecreeeet!
$ cat mysecret | sha1pass 

I'm not sure how sha1pass works, if it can take a file as input, you can use sha1pass < mysecret. If not, using cat might be a problem since it includes the final newline. If that's the case, use (if your head supports -c):

head -c-1 mysecret | sha1pass 
terdon
  • 242,166
  • 2
    Why write the password to disk in your first example when you can just do cat | sha1pass? cat mysecret | sha1pass and sha1pass < mysecret have the same behaviour with respect to final newlines. cat doesn't add newlines. Anyway, if sha1pass supports passing the password through standard input, I would expect it to ignore the final newline by itself. Files with lines that aren't terminated by newlines are unnatural in unix after all, so it'd be awkward to expect a file that is not terminated by a newline. – JoL Apr 23 '18 at 16:07
  • @JoL i) because I hadn't thought of that :/ But how would that work? cat | sha1pass seems to run sha1pass before giving me a chance to enter any input. ii) No, of course cat mysecret doesn't add a newline, I never said cat adds it, only that it includes it. – terdon Apr 23 '18 at 16:20
  • I see, but it's not like < mysecret removes it either. :) – JoL Apr 23 '18 at 16:24
  • Nevermind. Now that I re-read, I see that you said that to later propose head -c-1. I guess I just got confused as to why use cat mysecret | sha1pass and then later propose sha1pass < mysecret. I guess the worry is that sha1pass might complain about regular files for stdin; I'm not sure why. – JoL Apr 23 '18 at 16:31
  • @JoL it's more because I've never used sha1pass and couldn't find a manual or -h or any other form of documentation in the few minutes I spent searching and so couldn't figure out if running sha1pass foo treated foo as an input string or as an input file. I therefore gave options to deal with each possibility. – terdon Apr 23 '18 at 16:36
  • On i), if sha1pass accepts the password through standard input it should run, but block on reading stdin until the input reaches the end of file or a newline if line buffered. It depends on how it's implemented. On looking at it again, even cat | sha1pass seems redundant, sha1pass should already be enough to read from stdin, but it depends on how it was written (it could decide to fail unreasonably on seeing stdin being a tty). – JoL Apr 23 '18 at 16:46
1

If what terdon did is possible, then that's the best solution, passing through standard input. The only problem left is he wrote the password to disk. We can do this instead:

stty -echo
echo -n "password: "
head -1 | sha1pass
stty echo

Like Kusalananda said, stty -echo ensures what you type is not seen until you do stty echo again. head -1 will get one line from standard input and pass it on to sha1pass.

JoL
  • 4,735
1

I would use

sha1pass "$(cat)"

cat would read from stdin until EOF, which can be caused by pressing Ctrl+D. Then, the result would be passed as argument to sha1pass

oktupol
  • 111
0

If you are passing sensitive information around and use it regularly you are probably best encrypting it.

Putting something like

#create key as follows - will prompt for password
#echo -n 'secret you want encrypted' | openssl enc -aes-256-cbc  -a -salt -pbkdf2|base64
export MY_SECRET='VTJGc2RHVmtYMTlzVnBGWXNYUitLWlpYT3BWdStaQXJXeUVwc1JORnFsNWswZXJKT1dkRWpsWkxLWVFnK1hONQo='

Into your .bashrc will give you an encrypted environment variable that you can access where ever you need a secret, and you will be prompted for you passphrase/password that you used when creating the environment variable.

In the example above it is 'secret'

You access it is a command as follows

`echo $MY_SECRET|base64 --decode|openssl enc -aes-256-cbc -a -d -salt -pbkdf2 `

e.g.

xfreerpd /parameters.... /p:`echo $MY_SECRET|base64 --decode|openssl enc -aes-256-cbc -a -d -salt -pbkdf2` 

For your query using sha1pass. You can create and passin as follows

MY_SECRET=`echo -n 'secret you want encrypted' | openssl enc -aes-256-cbc  -a -salt -pbkdf2|base64`

Then use it as follows

sha1pass `echo $MY_SECRET|base64 --decode|openssl enc -aes-256-cbc -a -d -salt -pbkdf2 `

The base64 part is so that you can set the variable

MY_SECRET='VTJGc2RHVmtYMTlzVnBGWXNYUitLWlpYT3BWdStaQXJXeUVwc1JORnFsNWswZXJKT1dkRWpsWkxLWVFnK1hONQo='

And not have your secret stuck in the command history etc etc.

When you generate the secret sometimes the base64 output will have multiple lines so you need to take the line breaks out for your variable.

echo -n 'secret you want encrypted' | openssl enc -aes-256-cbc  -a -salt -pbkdf2|base64
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
VTJGc2RHVmtYMTlzVnBGWXNYUitLWlpYT3BWdStaQXJXeUVwc1JORnFsNWswZXJKT1dkRWpsWkxL
WVFnK1hONQo=

#take the above line break out MY_SECRET='VTJGc2RHVmtYMTlzVnBGWXNYUitLWlpYT3BWdStaQXJXeUVwc1JORnFsNWswZXJKT1dkRWpsWkxLWVFnK1hONQo='

openssh will prompt you for a password to encrypt and decrypt each time, you can supply one as part of the command, but then you are just hiding things from the history etc. Have a look at https://www.tecmint.com/generate-encrypt-decrypt-random-passwords-in-linux/ for some info on using openssh for this. https://www.serverlab.ca/tutorials/linux/administration-linux/how-to-base64-encode-and-decode-from-command-line/ for base64 and https://stackoverflow.com/questions/16072351/how-to-assign-an-output-to-a-shellscript-variable for different options on command substitution I have used back-tick ` above

  • If you don't want to be typing passwords in; generate a ssh key pair and use passwordless connections, either within a machine or between machines. Only the public key will be easily observable. – Jeremy Boden Jun 25 '21 at 11:58
  • There are quite a lot of utilities that take passwords or secrets, and have different qualities in terms of protecting their secrets. For ssh a key pair is much stronger than using passwords. Not all connection methods have this feature, and the more likely you are to want a strong/difficult to type password. – Ronald Duncan Jan 03 '23 at 16:11