genpass-apple 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. #!/usr/bin/env zsh
  2. #
  3. # Usage: genpass-apple [NUM]
  4. #
  5. # Generate a password made of 6 pseudowords of 6 characters each
  6. # with the security margin of at least 128 bits.
  7. #
  8. # Example password: xudmec-4ambyj-tavric-mumpub-mydVop-bypjyp
  9. #
  10. # If given a numerical argument, generate that many passwords.
  11. emulate -L zsh -o no_unset -o warn_create_global -o warn_nested_var
  12. if [[ ARGC -gt 1 || ${1-1} != ${~:-<1-$((16#7FFFFFFF))>} ]]; then
  13. print -ru2 -- "usage: $0 [NUM]"
  14. return 1
  15. fi
  16. zmodload zsh/system zsh/mathfunc || return
  17. {
  18. local -r vowels=aeiouy
  19. local -r consonants=bcdfghjklmnpqrstvwxz
  20. local -r digits=0123456789
  21. # Sets REPLY to a uniformly distributed random number in [1, $1].
  22. # Requires: $1 <= 256.
  23. function -$0-rand() {
  24. local c
  25. while true; do
  26. sysread -s1 c || return
  27. # Avoid bias towards smaller numbers.
  28. (( #c < 256 / $1 * $1 )) && break
  29. done
  30. typeset -g REPLY=$((#c % $1 + 1))
  31. }
  32. local REPLY chars
  33. repeat ${1-1}; do
  34. # Generate 6 pseudowords of the form cvccvc where c and v
  35. # denote random consonants and vowels respectively.
  36. local words=()
  37. repeat 6; do
  38. words+=('')
  39. repeat 2; do
  40. for chars in $consonants $vowels $consonants; do
  41. -$0-rand $#chars || return
  42. words[-1]+=$chars[REPLY]
  43. done
  44. done
  45. done
  46. local pwd=${(j:-:)words}
  47. # Replace either the first or the last character in one of
  48. # the words with a random digit.
  49. -$0-rand $#digits || return
  50. local digit=$digits[REPLY]
  51. -$0-rand $((2 * $#words)) || return
  52. pwd[REPLY/2*7+2*(REPLY%2)-1]=$digit
  53. # Convert one lower-case character to upper case.
  54. while true; do
  55. -$0-rand $#pwd || return
  56. [[ $vowels$consonants == *$pwd[REPLY]* ]] && break
  57. done
  58. # NOTE: We aren't using ${(U)c} here because its results are
  59. # locale-dependent. For example, when upper-casing 'i' in Turkish
  60. # locale we would get 'İ', a.k.a. latin capital letter i with dot
  61. # above. We could set LC_CTYPE=C locally but then we would run afoul
  62. # of this zsh bug: https://www.zsh.org/mla/workers/2020/msg00588.html.
  63. local c=$pwd[REPLY]
  64. printf -v c '%o' $((#c - 32))
  65. printf "%s\\$c%s\\n" "$pwd[1,REPLY-1]" "$pwd[REPLY+1,-1]" || return
  66. done
  67. } always {
  68. unfunction -m -- "-${(b)0}-*"
  69. } </dev/urandom