Skip to content

Instantly share code, notes, and snippets.

@yunruse
Created December 2, 2022 17:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yunruse/53774958ae69b875766892e549168240 to your computer and use it in GitHub Desktop.
Save yunruse/53774958ae69b875766892e549168240 to your computer and use it in GitHub Desktop.
Phonetic password schema, a la iCloud
'''
Small tool that generates a phonetic password with a schema
identical to that used in iCloud's password generators.
Pronouncable-ish with 20 characters and ~70 bits of entropy.
Useful for password managers or sysadmins.
'''
# Example password from schema: fivhAp-riznux-0mizhi
# Let V be the set of all lowercase vowels "aiueoy".
# Let C be the set of all lowercase consonants in the Latin (English) alphabet.
# A password is three blocks separated by a "-" character.
# Two blocks are CVCCVC;
# one (randomly positioned) is CVCCV with a digit (0-9) prepended or appended.
# One letter, randomly selected, is capitalised.
# I have my own personal criticisms of the schema.
# semivowels Y and W are a bit awkward sometimes in English
# and there is a chance of capitalising I which is sometimes ambiguous.
# The random capital placement also makes it annoying to pronounce.
# The ability to more easily communicate over a clear line / store in short term memory
# is worth losing 4 bits of entropy, in my mind.
# That said, it's close enough to the phonotactics of most languages
# that I must admit it is very charming.
# It's no xkcd 936 but it's a lot nicer than a random string.
from secrets import choice as r, randbelow
from string import ascii_lowercase, digits as D
__all__ = ('password', )
V = tuple('aeiouy')
C = tuple(set(ascii_lowercase) - set(V))
def _block(digit: bool) -> str:
block = r(C)+r(V)+r(C)+r(C)+r(V)
if digit:
if randbelow(2): # <- half and half
return r(D) + block
else:
return block + r(D)
else:
return block + r(C)
def password() -> str:
'''Generate an iCloud-like password.'''
blocks = [_block(False), _block(False)]
blocks.insert(randbelow(3), _block(True))
password = list('-'.join(blocks))
# this is a slightly silly but relatively simple method.
# password generating isn't subject to timing attacks
# or at least I'm quite confident they really shouldn't be...
i = 6 # "-"
while password[i] not in ascii_lowercase:
i = randbelow(len(password))
password[i] = password[i].upper()
return ''.join(password)
def _bits_of_entropy():
from math import log2
N_vowels = 6
N_consonants = 11
return(
log2(len(V)) * N_vowels
+ log2(len(C)) * N_consonants
+ log2(3) # places to insert numeric block
+ log2(2) # places to insert number
+ log2(17) # letters, one of which is capitalised
)
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser(description=__doc__)
parser.add_argument('-n', '-N', type=int, nargs='?', default=1, help='Number of passwords to generate (default 1)')
args = parser.parse_args()
for i in range(args.n):
print(password())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment