Skip to content

Commit c00c036

Browse files
functions: Simplify dictionary word selection
The dice-rolls method was relatively complex and somewhat biased (~2.4% biased toward 1-4 on each roll due to modulo bias). Just pick a line from the dictionary at random. Using all 32 bits of entropy to pick a line once distributes the modulo bias so it is only 0.000003% biased toward the first 1263 words. Signed-off-by: Jonathon Hall <jonathon.hall@puri.sm>
1 parent 7051fc8 commit c00c036

1 file changed

Lines changed: 13 additions & 43 deletions

File tree

initrd/etc/functions

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -881,32 +881,18 @@ generate_passphrase() {
881881
echo " [--lowercase|-l] Use lowercase words (default: false)."
882882
}
883883

884-
# Helper subfunction to get a word from the dictionary based on dice rolls
885-
get_word_from_dictionary() {
886-
local rolls="$1"
887-
local dictionary_file="$2"
888-
local word=""
889-
890-
word=$(grep "^$rolls" "$dictionary_file" | awk -F ' ' '{print $2}')
891-
echo "$word"
892-
}
893-
894-
# Helper subfunction to generate dice rolls
895-
generate_dice_rolls() {
896-
TRACE_FUNC
897-
local num_rolls="$1"
898-
local rolls=""
899-
local random_bytes
900-
901-
# Read num_rolls bytes from /dev/random, fed by CPU RRAND in one go
902-
random_bytes=$(dd if=/dev/random bs=1 count="$num_rolls" 2>/dev/null | hexdump -e '1/1 "%u\n"')
903-
904-
# Process each byte to generate a dice roll
905-
while read -r byte; do
906-
roll=$((byte % 6 + 1))
907-
rolls+=$roll
908-
done <<<"$random_bytes"
909-
echo "$rolls"
884+
# Helper subfunction to get a random word from the dictionary
885+
get_random_word_from_dictionary() {
886+
local dictionary_file="$1" lines random
887+
888+
lines="$(wc -l <"$dictionary_file")"
889+
# 4 random bytes are used to reduce modulo bias to an acceptable
890+
# level. 4 bytes with modulus 1296 results in 0.000003% bias
891+
# toward the first 1263 words.
892+
random="$(dd if=/dev/random bs=4 count=1 status=none | hexdump -e '1/4 "%u\n"')"
893+
((random%=lines))
894+
((++random)) # tail's line count is 1-based
895+
tail -n +"$random" "$dictionary_file" | head -1 | cut -d$'\t' -f2
910896
}
911897

912898
TRACE_FUNC
@@ -961,25 +947,9 @@ generate_passphrase() {
961947

962948
local passphrase=""
963949
local word=""
964-
local key=""
965-
local digits=0
966-
967-
# Read the number of digits from the first line of the dictionary file
968-
read -r key _ <"$dictionary_file"
969-
970-
# Validate that the key is composed entirely of digits
971-
if ! [[ $key =~ ^[0-9]+$ ]]; then
972-
echo "Error: Dictionary is not compliant with EFF diceware dictionaries."
973-
echo "The first line of the dictionary should be in the format: <digits> <word>"
974-
echo "Example: 11111 word"
975-
exit 1
976-
fi
977-
978-
digits=${#key} #Number of digits in dice rolls
979950

980951
for ((i = 0; i < num_words; ++i)); do
981-
key=$(generate_dice_rolls "$digits")
982-
word=$(get_word_from_dictionary "$key" "$dictionary_file")
952+
word=$(get_random_word_from_dictionary "$dictionary_file")
983953
if [[ "$lowercase" == "false" ]]; then
984954
word=${word^} # Capitalize the first letter
985955
fi

0 commit comments

Comments
 (0)