Functions with default values
Suppose you would like to have a command generating a secure password for an online service at the command line. You would google for that and find 10 ways to generate a random password. At the end of his article, the author presents the ideal way to generate a secure password:
date | md5sum
The author of the article (Lowell Heddings, the founder and CEO of How-To Geek) states:
I’m sure that some people will complain that it’s not as random as some of the other options, but honestly, it’s random enough if you’re going to be using the whole thing.
Random enough? Sure, the 'whole thing' looks random enough:
9ec463af3db95e8e44de84417d9f408f
but the look is deceptive: this is in fact an extremely weak password. To understand why, let's look at the output of the 'date' command:
We see that without additional parameter (like +"%N"), 'date' gives us one password for each second of the year. How many passwords do we get in this way? Well,
i.e., 1,557,666,649 seconds has passed since 00:00:00, Jan 1, 1970 (Unix epoch time), and that's how many passwords we get.
Now, the possibility to order Pizza online came much later, namely, at August 22nd, 1994.
That leaves us with 780,160,249 passwords since this memorable day in 1994 or a complexity of 30 bits, corresponding to a 5-digit password with a character space of 62. Let's get one of these and see how difficult it is to crack:
Now, even my ancient GTX650Ti with its modest MD5 hashing performance of 1.5 GH/s cracks this password in 5 s (note that an RTX2080 delivers 36 GH/s...):
○ → hashcat -O -a 3 -m 0 myhashes.hash ?a?a?a?a?a hashcat (v5.1.0) starting... OpenCL Platform #1: NVIDIA Corporation ====================================== - Device #1: GeForce GTX 650 Ti, 243/972 MB allocatable, 4MCU 0b91091d40a8623891367459d5b2a406:p9iCN Session..........: hashcat Status...........: Cracked Hash.Type........: MD5 Hash.Target......: 0b91091d40a8623891367459d5b2a406 Time.Started.....: Mon May 13 12:48:58 2019 (5 secs) Time.Estimated...: Mon May 13 12:49:03 2019 (0 secs) Guess.Mask.......: ?a?a?a?a?a [5] Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 514.0 MH/s (6.21ms) @ Accel:64 Loops:47 Thr:1024 Vec:2 Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 2328363008/7737809375 (30.09%) Rejected.........: 0/2328363008 (0.00%) Restore.Point....: 24379392/81450625 (29.93%) Restore.Sub.#1...: Salt:0 Amplifier:0-47 Iteration:0-47 Candidates.#1....: s3v\, -> RPuJG Hardware.Mon.#1..: Temp: 44c Fan: 33%
But actually, it's even worse: instead of cracking the hash one can easily precompute all possible values of the 'date | md5sum' command, and thus create a dictionary containing these “passwords”. I could start right away:
for (( time=777506400; time<=1557666649; time++ )); do date -d@$time | md5sum | tr -d "-"; done > lowell_heddings_passwords.txt
On my desktop with its Xeon E3 v2, this command computes one million passwords in about half an hour, i.e, I'd need about 17 days for computing all passwords back to 1994. Writing a corresponding program running on the GPU would cut this down to seconds. Note that the resulting list of “random enough” passwords is static, i.e., it is indeed a dictionary, and not even a particularly large one.
Lowell Heddings himself mentions several alternative ways to generate a password in his article before turning to the worst possible solution. But if we desire cryptographically secure solutions, even apparently innocuous commands are beset with difficulties, as pointed out by, for example, the carpetsmoker (better carpets than mattresses). In the end, it all boils down to the following three choices that are available on virtually any Linux installation. If we limit ourselves to a character space of 62:
cat /dev/urandom | base64 | tr -d /=+ | head -c 25; echo openssl rand -base64 25 | tr -d /=+ | head -c 25; echo gpg2 --armor --gen-random 1 25 | tr -d /=+ | head -c 25; echo
If we insist of having almost all printable characters (which often calls for trouble):
cat /dev/urandom | base91 | head -c 25 openssl rand 25 | base91 | head -c 25; echo gpg2 --gen-random 1 25 | base91 | head -c 25; echo
One could, in principle, also utilize dedicated password generators, such as Theodore Tso's 'pwgen', Adel I. Mirzazhanov's 'apg', or haui's 'hpg':
pwgen -s 25 apg -a 1 -M ncl -m 25 -x 25 hpg --alphanum 25
All of these ways are cryptographically equivalent in the sense that the entropy of the passwords generated by either of them asymptotically approaches the theoretical value ($\log_2(62) \approx 5.954$ bits per character) when you average over many (10,000,000 or more). In the present context (functions with default values) the generators do not offer any advantage, but only add unnecessary complexity.
Now, whatever you chose as your favorite, you don't want to memorize the command or rely on the history of your favorite shell. One could define an alias with the password length as parameter, but I prefer to use a function for this case to have a default length of 25 characters with the option to change this value:
Here's how to implement this functionality for the three major shells. Note the very elegant way with which a default value can be implemented within the bash. Update: haui reminded me that the zsh is a drop-in replacement for the bash and thus of course implements all bash variable substitution, particularly ${var:-default}. Hence, we can use the same syntax for the bash and the zsh, and only the fish needs the comparatively clumsy construct shown below. 😎
bash
function pw62 { cat /dev/urandom | base64 | tr -d /=+ | head -c ${1:-25}; echo }
fish
function pw62 if set -q $argv set length 25 else set length $argv end cat /dev/urandom | base64 | tr -d /=+ | head -c $length; echo end
zsh (alternative to the bash syntax)
function pw62() { if [ "$1" != "" ] then integer length=$1 else integer length=25 fi cat /dev/urandom | base64 | tr -d /=+ | head -c $length; echo }