From 98af06ad6658819884acf2123af5dcbcbb875441 Mon Sep 17 00:00:00 2001 From: Ryan Vo Date: Sun, 8 Jun 2025 10:51:05 -0500 Subject: [PATCH] fix(genpass): follow Apple password specification requirements --- plugins/genpass/README.md | 17 +++++----- plugins/genpass/genpass-apple | 59 ++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/plugins/genpass/README.md b/plugins/genpass/README.md index a5ff4a876..3ef8b68af 100644 --- a/plugins/genpass/README.md +++ b/plugins/genpass/README.md @@ -19,19 +19,20 @@ passwords in a script: ## genpass-apple Generates a pronounceable pseudoword passphrase of the "cvccvc" consonant/vowel -syntax, inspired by [Apple's iCloud Keychain password generator][1]. Each -password has exactly 1 digit placed at the edge of a "word" and exactly 1 -capital letter to satisfy most password security requirements. +syntax based on [Apple Passwords’ Generated Strong Password Format][1]. Each +password has exactly 1 digit placed on other either side of the hyphen or at the +end of the password and exactly 1 capital letter to satisfy most password security +requirements. % genpass-apple - gelcyv-foqtam-fotqoh-viMleb-lexduv-6ixfuk + nyvtyv-mYhmob-xyqby4 % genpass-apple 3 - japvyz-qyjti4-kajrod-nubxaW-hukkan-dijcaf - vydpig-fucnul-3ukpog-voggom-zygNad-jepgad - zocmez-byznis-hegTaj-jecdyq-qiqmiq-5enwom + kedfu4-nowryn-gezjeR + qoJsoh-fikgon-nugfa8 + fecvos-kiCliv-byvhi1 -[1]: https://developer.apple.com/password-rules/ +[1]: https://rmondello.com/2024/10/07/apple-passwords-generated-strong-password-format/ ## genpass-monkey diff --git a/plugins/genpass/genpass-apple b/plugins/genpass/genpass-apple index 963ab6447..451880d09 100755 --- a/plugins/genpass/genpass-apple +++ b/plugins/genpass/genpass-apple @@ -2,10 +2,10 @@ # # Usage: genpass-apple [NUM] # -# Generate a password made of 6 pseudowords of 6 characters each -# with the security margin of at least 128 bits. +# Generate a password made of 6 syllables of 3 characters each +# with the security margin of at least 71 bits. # -# Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp +# Example password: nukci1-zochob-Werfip # # If given a numerical argument, generate that many passwords. @@ -20,7 +20,7 @@ zmodload zsh/system zsh/mathfunc || return { local -r vowels=aeiouy - local -r consonants=bcdfghjklmnpqrstvwxz + local -r consonants=bcdfghjkmnpqrstvwxz local -r digits=0123456789 # Sets REPLY to a uniformly distributed random number in [1, $1]. @@ -35,34 +35,55 @@ zmodload zsh/system zsh/mathfunc || return typeset -g REPLY=$((#c % $1 + 1)) } - local REPLY chars + local REPLY chars i repeat ${1-1}; do - # Generate 6 pseudowords of the form cvccvc where c and v + # Generate 6 syllables of the form cvc where c and v # denote random consonants and vowels respectively. - local words=() + local syllables=() repeat 6; do - words+=('') - repeat 2; do - for chars in $consonants $vowels $consonants; do - -$0-rand $#chars || return - words[-1]+=$chars[REPLY] - done + syllables+=('') + for chars in $consonants $vowels $consonants; do + -$0-rand $#chars || return + syllables[-1]+=$chars[REPLY] done done - local pwd=${(j:-:)words} + # First concatenate all syllables without hyphens + local pwd_chars=${(j::)syllables} - # Replace either the first or the last character in one of - # the words with a random digit. + # Valid positions for digit in the 18-char string: 5, 6, 11, 12, 17 (0-indexed) + # In 1-indexed: 6, 7, 12, 13, 18 + local -a digit_positions=(6 7 12 13 18) + -$0-rand $#digit_positions || return + local digit_pos=$digit_positions[REPLY] + + # Generate random digit -$0-rand $#digits || return local digit=$digits[REPLY] - -$0-rand $((2 * $#words)) || return - pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit + + # Special handling for positions 7 and 13 (start of syllable pair) + if [[ $digit_pos == 7 || $digit_pos == 13 ]]; then + # Shift characters backwards to maintain cvcv pattern + for (( i = digit_pos + 5; i > digit_pos; i-- )); do + pwd_chars[i]=$pwd_chars[i-1] + done + fi + + # Place the digit + pwd_chars[digit_pos]=$digit + + # Now add hyphens to create the final password + local pwd="${pwd_chars[1,6]}-${pwd_chars[7,12]}-${pwd_chars[13,18]}" - # Convert one lower-case character to upper case. + # Convert one lower-case character to upper case (excluding digit position) + # Calculate digit position in final string (with hyphens) + local final_digit_pos=$((digit_pos + (digit_pos - 1) / 6)) + while true; do -$0-rand $#pwd || return + # Skip if it's the digit position or a hyphen + [[ REPLY == $final_digit_pos || $pwd[REPLY] == '-' ]] && continue [[ $vowels$consonants == *$pwd[REPLY]* ]] && break done # NOTE: We aren't using ${(U)c} here because its results are