Aléas numériques

Linux, infosec and whatever crosses my mind.


» Generate secure passwords

For a few years now, I’ve been using pass(1) as my day-to-day password manager. It works like a charm, especially with rofi-pass. pass has a dedicated command to generate passwords, but it stores them in an encrypted file:

$ pass generate foo
[master 2db1669] Add generated password for foo.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 foo.gpg
The generated password for foo is:
=>Jw'3;.#[3g6#Ey:k{BDuk^K

It’s really cool, but as far as I know it is not possible to generate a password and print it on STDOUT without storing it in a file. However, I sometimes need to generate a password that I do not want to be stored in my password manager (passwords for PoC databases, etc.).

A few month ago I wrote a little script that does what I needed:

#!/bin/bash

PASS_LEN=$1
[ -z $PASS_LEN ] && PASS_LEN=32
PASS=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-${PASS_LEN}};echo;)
printf "Pass: %s\n" "$PASS"
ENTROPY=$(echo $PASS | $HOME/.bin/pass-checker | grep -i entropy)
printf "%s\n" "$ENTROPY"

It generates a password with a size specified in $1 (or 32 by default) using data from /dev/urandom. The output is sanitized to have printable characters only using tr -dc, which strips out non-printable characters. Then, it prints the password and tries to calculate and print its entropy using pass-checker, a small utility I wrote a long time ago while doing my apprenticeship at my previous company:

$ pgen
Pass: mu2BKXq_PFpR-oq4nEHdNPP9pRCHzDUk
Entropy: 203.192 bits

This works fairly well, but it relies on a script shell that is not present on every system I might work on. So instead of having to clone/scp/wget it, I recently discovered that I could use openssl:

$ openssl rand -base64 16
mt5XVSD9s0Vs2R8myzxJTA==

$ openssl rand -hex 16
d6985c68822179c6b2789e3b3cc764c5

There are two main options:

  • -hex generates a password under the form of a hexadecimal number;
  • -base64 generates a password under the form of a base64-encoded string.

The integer following the option is the size of the password in bytes.

It’s really handy, but is it secure?

We know that a password’s robustness is measured with its entropy. The entropy, expressed in bits, is the logarithm in base 2 of the number of possible combinations:

$$ H = \log_2 N^{L} = L\log_2N = L \times \dfrac{\log N}{\log 2} $$

So the entropy is highly dependent of the size of the charset used ($N$).

Let’s take the example above where I generated a 16-bytes password using openssl with the -hex option. The charset used is [a-fA-F0-9], so 22 different characters. If we apply the formula:

$$ \log_2(22^{16}) = 16\times\dfrac{\log(22)}{\log(2)} \approx 53,51 $$

The entropy is thus about 53.51 bits, which is… not a lot. According to a table I randomly found on Reddit, cracking such password could take between 14.3 millenia and 45 seconds:

A motivated bad actor could easily buy enough compute power to achieve at least 100 billion guesses per second, so cracking a 16-byte password with only hexadecimal characters is a matter of a few hours/days.

Let’s use a password generated with the -base64 option instead. As stated by its name, base64 supports 64 different characters: [a-zA-Z0-9+/]. In addition to those characters, the equal sign (=) is used for padding. As we’re not using the base64 string for encoding/decoding, we’ll count = as being part of the chatset. So $N = 65$:

$$ \log_2(65^{16}) = 16\times\dfrac{\log(65)}{\log(2)} \approx 96,36 $$

96.36 bits, that’s better! Accoding to the previous table, the same attacker could break our password between 25 billion and 2.5 trillion years! Phew!

Let’s compare those results with what my script generates. The charset length is $N = 68$: [a-zA-Z0-9_-].

As the entropy is also computed on the fly depending on the charset detected from the password, we can generate few passwords, grab their entropy, and compare the average value:

$ for i in {1..10}; do pgen 16 | grep Entropy | cut -d' ' -f2; done
95.267
95.267
104.873
104.873
95.267
95.267
104.873
95.267
95.267
98.319

We clearly see the influence of the - and _ chars on the entropy. We can calculate the mean value using the formula:

$$ \bar{x} = \dfrac{\sum_{i=1}^{n}x_i}{n} $$

We obtain an average entropy of 98.454 bits. Not so bad! But we could further improve those resultst by adding new characters to the charset given to tr -dc. But that’s a job for another day!

What’s the moral of the story? Length of a password is not the sole factor that impacts a password robustness: the size of the charset is also extremely important. But everyone knows that, right? ¯\(ツ)

Until next time!

31/01/2023 addition

I was fiddling around tonight while preparing my lesson for tomorrow morning about PRNG when I remembered about random.org! After having a quick look, I discovered that they have a public API that is dedicated to generating password:

$ curl "https://www.random.org/passwords/?num=1&len=16&format=plain&rnd=new"
zPCWaM895MB8p2kT

The charset seems to be limited to [a-zA-Z0-9] so better than OpenSSL in -hex mode but worse than OpenSSL in -base64 mode, but you can be sure to have a truly random password as the randomness comes from atmospheric noise!


I did a mistake? You want to say something about this post? Feel free to send me an email about this article by clicking here!