I’ve been on the lookout for ways to generate random passwords from the shell. One of the ways to do that, as I’ve learned from people, is to run the slappaswd
utility a couple of times, get enough random output, and then just concatenate them together to form a long enough password. Crude, but suffices, until you realise that the slappaswd
utility comes from OpenLDAP and you’d rather not pollute your computer with that filth. What next?
These passwords aren’t for to be remembered by humans; I’d probably store these passwords in an encrypted text file and just copy-paste them wherever they’re required. So they can be completly random. This makes our job so much easier.
Entropy
The first step to generating a random password is to get enough entropy. Fortunately, on Linux and most *NIX systems, you’ll have either /dev/random
and /dev/urandom
to help you along. Just start by dumping enough bytes from it into stdout
.
Something to take note of though - historically, /dev/random
used to be a source of completely random bytes captured from the environment, electrical noise, system events, etc., and /dev/urandom
would be a pseudorandom number generator that could either be cryptographically secure, or not. That difference has blurred somewhat these days, and you should consult the documentation for your operating system to see where those bytes are coming from.
You can use something like Haveged to inject more deterministically generated entropy (yes, I get the irony there) into the system.
First Shot
Once you’ve decided on a source of random bytes, the password generation is simple - just get enough bytes, and convert them into an ASCII string. The best way to do that is to simply base64-encode raw bytes, like so:
$: dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 -w0
4QK3nyD+ONf1ZnubDOYlZ9a4qI9CBYT5TWUrcc24YJY=
This is as simple as it can get - read the PRNG (/dev/urandom
) with dd
, get 1 block (count
) of 32 bytes (bs
), redirect stderr
to /dev/null
to suppress dd
’s status report, and base64-encode the resulting output. The -w0
switch is to prevent base64
from inserting newlines in the output after every few characters (the default is 76 characters).
Although having symbols in your password makes it more secure (more possible characters to choose from when brute-forcing the password), it’s possible to remove the non-alphanumeric characters in the password. Just pipe the output to tr
, like so:
$: dd if=/dev/urandom bs=32 count=1 2> /dev/null | base64 -w0 | tr -cd "[[:alnum:]]"
AB7qAV5bnDZb4Q0QydQFVFyuMmU7UsZDWn2dvwY0bKs
I really recommend you just use the first example, and increase the bs
value given to dd
to get longer passwords. However, you might want to get fancier, and because it’s shell, there are ways to.
Entropy, Redux
You might want to have relatively short (30-60 character) passwords that are generated from massive amounts of entropy, say over 8 kilobytes of random data. While this is overkill, you could actually do it by throwing a hash function into the mix.
Here’s what the pipe would have to do:
- Get a massive amount of random data and dump it into
stdout
, possibly withdd if=/dev/urandom bs=8192 count=1
. - Hash that data to reduce the number of bytes in the output. You shouldn’t really think in the direction of MD5 at all - choose between
sha{1,224,256,384,512}sum
, and strip all extra data from the output - The standard
sha*sum
commands output the hex digest of the hash bytes, so you’ll have to convert them back into raw bytes. - Now just run
base64
to encode the data, and pull out the symbols if you want to.
Now I’m not sure about what the math will look like here, but something tells me this extra effort is pretty much useless. 32 bytes of purely random data will require the same brute-force effort to crack as 32 bytes of hash generated from 8 kilobytes of purely random data. In any case, I’ve outlined the pipe below.
Password Generation, Redux.
I’ll explain the pipe in bits.
First, the hash. I’m going to use MD5 to demonstrate here because the hashes are nice and short, but you should never use MD5 in practice because the hashes are nice and short.
Here’s how the ouput of the md5sum
command (and all the sha*sum
commands) look like:
$: echo hello | md5sum
b1946ac92492d2347c6235b4d2611184 -
You have the hash, followed by a couple of spaces, and then the filename. Since it’s reading from stdin
, the filename is simply -
.
To get just the hash, pipe it to cut
, like so:
$: echo hello | md5sum | cut -d " " -f 1
b1946ac92492d2347c6235b4d2611184
You could just base64-encode this string, like so:
$: echo hello | md5sum | cut -d " " -f 1 | base64 -w0
YjE5NDZhYzkyNDkyZDIzNDdjNjIzNWI0ZDI2MTExODQK
Notice that there are no symbols in this string? That’s because you’re encoding bytes that are already alphanumeric, and base64 encodes alphanumeric ASCII values to alphanumeric ASCII values.
Another way to do this would be to convert the hash into its raw bytes, and then base64-encode that. You’d get a shorter password (half as many characters; hex-encoding a byte takes two bytes), but you’d get symbols. You’ll be trading password length for a bigger alphabet space.
To convert the hash to raw bytes, you can use Perl, Python, shell builtins, sed
and what not, but the simplest way is to simply use xxd
, a command that comes with vim
. Here’s how to base64-encode the raw bytes of the hash:
$: echo hello | md5sum | cut -d " " -f 1 | xxd -r -p | base64 -w0
sZRqySSS0jR8YjW00mERhA==
And that’s basically it. Use a longer hash function and some actual random input, and you’re done:
$: dd id=/dev/urandom bs=16384 count=1 2> /dev/null | sha384sum | cut -d " " -f 1 | xxd -r -p | base64 -w0
OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb
Shell Shortcuts
To make this easier, you can just create a small function and put that into your ~/.zshrc
or ~/.bashrc
. This is what I’ve got:
function genpasswd () {
local RPASSWD=$(dd if=/dev/urandom bs=$1 count=1 2> /dev/null | base64 -w0)
if [[ $2 = "-n" ]]; then
echo $RPASSWD | tr -cd "[[:alnum:]]"
else
echo $RPASSWD
fi
}
This function takes one mandatory argument, and one optional one. The first argument is the number of random bytes you want, and the second is -n
if you want symbols to be stripped. Works like so:
$: genpasswd 32
XAXVnxe6UoigD1oSyaGFPGGqhkaJdZtTcIWGW1kJQQU=
And if you want no symbols, then:
$: genpasswd 32 -n
uwhCmpnfG6ELBT7j8QOzk2wqFa8S3JUH8Fq2fIDCLY4
Fun?
Till next time!