Random discussion in a Matrix chat today on people apparently asking AIs for random passwords today, linking to this article in The Register and I said:

Not that I'd ask an AI for a random anything. I might ask it for "some code to draw four random words from /usr/share/wordlist and combine them with some punctuation, and then make up a little story about the words so I can remember it" - that would meet the xkcd good password guidelines, and probably actually would be random. And it'd even be auditable.

Of course having said that, I had to actually do it as well...

So, here's the final script, which - in line with the XKCD correct horse battery staple approach:

  • Gets four random words from /usr/share/dict/words
  • Gets three random punctuation characters from a list
  • Asks a local AI for a little story about them

#!/bin/bash
# Password generator by Andrew McMillan licensed CC-0
# See: https://creativecommons.org/publicdomain/zero/1.0/
#
# Configuration
DICT_FILE="/usr/share/dict/words"
AI_URL="http://localhost:1234/v1/chat/completions"
AI_MODEL="local-model" 

# Check dependencies
if ! command -v jq &> /dev/null; then
    echo "Error: jq is required but not installed."
    exit 1
fi

if [ ! -f "$DICT_FILE" ]; then
    echo "Error: Dictionary file not found at $DICT_FILE"
    exit 1
fi

# 1. Generate Words
# Filter for lowercase, 4-8 chars, shuffle, pick 4
raw_words=$(grep -E '^[a-z]{4,8}$' "$DICT_FILE" | shuf -n 4)
words=($raw_words)

# 2. Generate Random Punctuation
# Define allowed separators (safe for passwords and typing)
separators=('-' '_' '.' '+' '!' '?' '=' ':')
num_seps=${#separators[@]}

# Pick 3 random separators
s1=${separators[$((RANDOM % num_seps))]}
s2=${separators[$((RANDOM % num_seps))]}
s3=${separators[$((RANDOM % num_seps))]}

# 3. Format Password
password="${words[0]}${s1}${words[1]}${s2}${words[2]}${s3}${words[3]}"

echo "----------------------------------------"
echo "PASSWORD: $password"
echo "----------------------------------------"
echo "Asking local AI for a story..."

# 4. Construct JSON Payload using jq (handles escaping safely)
# We ask the AI to uppercase the specific words so they stand out.
make_prompt() {
	local w1="$(echo $1 | tr 'a-z' 'A-Z')"
	local w2="$(echo $2 | tr 'a-z' 'A-Z')"
	local w3="$(echo $3 | tr 'a-z' 'A-Z')"
	local w4="$(echo $4 | tr 'a-z' 'A-Z')"

	cat <<EOF
Create a very short, memorable, bizarre one-sentence story using
the following four words, in order, in uppercase. DO NOT USE
DIFFERENT WORDS: The story must contain these EXACT words, in
EXACTLY this sequence:"
 ${w1} ${w2} ${w3} ${w4} .

Your story will be scored according to how many of these words
are used, how many are in the correct order, and how many are
in UPPERCASE.
EOF
}

prompt="$(make_prompt ${words[0]} ${words[1]} ${words[2]} ${words[3]})"

json_payload=$(jq -n \
          --arg model "$AI_MODEL" \
          --arg prompt "$prompt" \
          '{
              model: $model,
              messages: [
              {
                  role: "system",
                  content: "You are a creative assistant."
              },
              { role: "user", content: $prompt }
           ],
           temperature: 0.7,
           max_tokens: 150
        }')

# 5. Send Request via Curl and parse immediately with jq
story=$(curl -s -X POST "$AI_URL" \
     -H "Content-Type: application/json" \
     -d "$json_payload" | jq -r '.choices[0].message.content')

# 6. Output
if [ "$story" == "null" ] || [ -z "$story" ]; then
    echo "Error: Could not retrieve story from AI."
else
    echo ""
    echo -e "\033[1;32m$story\033[0m"
    echo ""
fi
echo "----------------------------------------"

Now that's how to get an AI to give you some passwords.