324 lines
11 KiB
Python
324 lines
11 KiB
Python
from __future__ import print_function, unicode_literals
|
|
|
|
import os
|
|
from binascii import unhexlify
|
|
|
|
from zope.interface import implementer
|
|
|
|
from ._interfaces import IWordlist
|
|
|
|
# The PGP Word List, which maps bytes to phonetically-distinct words. There
|
|
# are two lists, even and odd, and encodings should alternate between then to
|
|
# detect dropped words. https://en.wikipedia.org/wiki/PGP_Words
|
|
|
|
# Thanks to Warren Guy for transcribing them:
|
|
# https://github.com/warrenguy/javascript-pgp-word-list
|
|
|
|
|
|
raw_words = {
|
|
'00': ['aardvark', 'adroitness'],
|
|
'01': ['absurd', 'adviser'],
|
|
'02': ['accrue', 'aftermath'],
|
|
'03': ['acme', 'aggregate'],
|
|
'04': ['adrift', 'alkali'],
|
|
'05': ['adult', 'almighty'],
|
|
'06': ['afflict', 'amulet'],
|
|
'07': ['ahead', 'amusement'],
|
|
'08': ['aimless', 'antenna'],
|
|
'09': ['Algol', 'applicant'],
|
|
'0A': ['allow', 'Apollo'],
|
|
'0B': ['alone', 'armistice'],
|
|
'0C': ['ammo', 'article'],
|
|
'0D': ['ancient', 'asteroid'],
|
|
'0E': ['apple', 'Atlantic'],
|
|
'0F': ['artist', 'atmosphere'],
|
|
'10': ['assume', 'autopsy'],
|
|
'11': ['Athens', 'Babylon'],
|
|
'12': ['atlas', 'backwater'],
|
|
'13': ['Aztec', 'barbecue'],
|
|
'14': ['baboon', 'belowground'],
|
|
'15': ['backfield', 'bifocals'],
|
|
'16': ['backward', 'bodyguard'],
|
|
'17': ['banjo', 'bookseller'],
|
|
'18': ['beaming', 'borderline'],
|
|
'19': ['bedlamp', 'bottomless'],
|
|
'1A': ['beehive', 'Bradbury'],
|
|
'1B': ['beeswax', 'bravado'],
|
|
'1C': ['befriend', 'Brazilian'],
|
|
'1D': ['Belfast', 'breakaway'],
|
|
'1E': ['berserk', 'Burlington'],
|
|
'1F': ['billiard', 'businessman'],
|
|
'20': ['bison', 'butterfat'],
|
|
'21': ['blackjack', 'Camelot'],
|
|
'22': ['blockade', 'candidate'],
|
|
'23': ['blowtorch', 'cannonball'],
|
|
'24': ['bluebird', 'Capricorn'],
|
|
'25': ['bombast', 'caravan'],
|
|
'26': ['bookshelf', 'caretaker'],
|
|
'27': ['brackish', 'celebrate'],
|
|
'28': ['breadline', 'cellulose'],
|
|
'29': ['breakup', 'certify'],
|
|
'2A': ['brickyard', 'chambermaid'],
|
|
'2B': ['briefcase', 'Cherokee'],
|
|
'2C': ['Burbank', 'Chicago'],
|
|
'2D': ['button', 'clergyman'],
|
|
'2E': ['buzzard', 'coherence'],
|
|
'2F': ['cement', 'combustion'],
|
|
'30': ['chairlift', 'commando'],
|
|
'31': ['chatter', 'company'],
|
|
'32': ['checkup', 'component'],
|
|
'33': ['chisel', 'concurrent'],
|
|
'34': ['choking', 'confidence'],
|
|
'35': ['chopper', 'conformist'],
|
|
'36': ['Christmas', 'congregate'],
|
|
'37': ['clamshell', 'consensus'],
|
|
'38': ['classic', 'consulting'],
|
|
'39': ['classroom', 'corporate'],
|
|
'3A': ['cleanup', 'corrosion'],
|
|
'3B': ['clockwork', 'councilman'],
|
|
'3C': ['cobra', 'crossover'],
|
|
'3D': ['commence', 'crucifix'],
|
|
'3E': ['concert', 'cumbersome'],
|
|
'3F': ['cowbell', 'customer'],
|
|
'40': ['crackdown', 'Dakota'],
|
|
'41': ['cranky', 'decadence'],
|
|
'42': ['crowfoot', 'December'],
|
|
'43': ['crucial', 'decimal'],
|
|
'44': ['crumpled', 'designing'],
|
|
'45': ['crusade', 'detector'],
|
|
'46': ['cubic', 'detergent'],
|
|
'47': ['dashboard', 'determine'],
|
|
'48': ['deadbolt', 'dictator'],
|
|
'49': ['deckhand', 'dinosaur'],
|
|
'4A': ['dogsled', 'direction'],
|
|
'4B': ['dragnet', 'disable'],
|
|
'4C': ['drainage', 'disbelief'],
|
|
'4D': ['dreadful', 'disruptive'],
|
|
'4E': ['drifter', 'distortion'],
|
|
'4F': ['dropper', 'document'],
|
|
'50': ['drumbeat', 'embezzle'],
|
|
'51': ['drunken', 'enchanting'],
|
|
'52': ['Dupont', 'enrollment'],
|
|
'53': ['dwelling', 'enterprise'],
|
|
'54': ['eating', 'equation'],
|
|
'55': ['edict', 'equipment'],
|
|
'56': ['egghead', 'escapade'],
|
|
'57': ['eightball', 'Eskimo'],
|
|
'58': ['endorse', 'everyday'],
|
|
'59': ['endow', 'examine'],
|
|
'5A': ['enlist', 'existence'],
|
|
'5B': ['erase', 'exodus'],
|
|
'5C': ['escape', 'fascinate'],
|
|
'5D': ['exceed', 'filament'],
|
|
'5E': ['eyeglass', 'finicky'],
|
|
'5F': ['eyetooth', 'forever'],
|
|
'60': ['facial', 'fortitude'],
|
|
'61': ['fallout', 'frequency'],
|
|
'62': ['flagpole', 'gadgetry'],
|
|
'63': ['flatfoot', 'Galveston'],
|
|
'64': ['flytrap', 'getaway'],
|
|
'65': ['fracture', 'glossary'],
|
|
'66': ['framework', 'gossamer'],
|
|
'67': ['freedom', 'graduate'],
|
|
'68': ['frighten', 'gravity'],
|
|
'69': ['gazelle', 'guitarist'],
|
|
'6A': ['Geiger', 'hamburger'],
|
|
'6B': ['glitter', 'Hamilton'],
|
|
'6C': ['glucose', 'handiwork'],
|
|
'6D': ['goggles', 'hazardous'],
|
|
'6E': ['goldfish', 'headwaters'],
|
|
'6F': ['gremlin', 'hemisphere'],
|
|
'70': ['guidance', 'hesitate'],
|
|
'71': ['hamlet', 'hideaway'],
|
|
'72': ['highchair', 'holiness'],
|
|
'73': ['hockey', 'hurricane'],
|
|
'74': ['indoors', 'hydraulic'],
|
|
'75': ['indulge', 'impartial'],
|
|
'76': ['inverse', 'impetus'],
|
|
'77': ['involve', 'inception'],
|
|
'78': ['island', 'indigo'],
|
|
'79': ['jawbone', 'inertia'],
|
|
'7A': ['keyboard', 'infancy'],
|
|
'7B': ['kickoff', 'inferno'],
|
|
'7C': ['kiwi', 'informant'],
|
|
'7D': ['klaxon', 'insincere'],
|
|
'7E': ['locale', 'insurgent'],
|
|
'7F': ['lockup', 'integrate'],
|
|
'80': ['merit', 'intention'],
|
|
'81': ['minnow', 'inventive'],
|
|
'82': ['miser', 'Istanbul'],
|
|
'83': ['Mohawk', 'Jamaica'],
|
|
'84': ['mural', 'Jupiter'],
|
|
'85': ['music', 'leprosy'],
|
|
'86': ['necklace', 'letterhead'],
|
|
'87': ['Neptune', 'liberty'],
|
|
'88': ['newborn', 'maritime'],
|
|
'89': ['nightbird', 'matchmaker'],
|
|
'8A': ['Oakland', 'maverick'],
|
|
'8B': ['obtuse', 'Medusa'],
|
|
'8C': ['offload', 'megaton'],
|
|
'8D': ['optic', 'microscope'],
|
|
'8E': ['orca', 'microwave'],
|
|
'8F': ['payday', 'midsummer'],
|
|
'90': ['peachy', 'millionaire'],
|
|
'91': ['pheasant', 'miracle'],
|
|
'92': ['physique', 'misnomer'],
|
|
'93': ['playhouse', 'molasses'],
|
|
'94': ['Pluto', 'molecule'],
|
|
'95': ['preclude', 'Montana'],
|
|
'96': ['prefer', 'monument'],
|
|
'97': ['preshrunk', 'mosquito'],
|
|
'98': ['printer', 'narrative'],
|
|
'99': ['prowler', 'nebula'],
|
|
'9A': ['pupil', 'newsletter'],
|
|
'9B': ['puppy', 'Norwegian'],
|
|
'9C': ['python', 'October'],
|
|
'9D': ['quadrant', 'Ohio'],
|
|
'9E': ['quiver', 'onlooker'],
|
|
'9F': ['quota', 'opulent'],
|
|
'A0': ['ragtime', 'Orlando'],
|
|
'A1': ['ratchet', 'outfielder'],
|
|
'A2': ['rebirth', 'Pacific'],
|
|
'A3': ['reform', 'pandemic'],
|
|
'A4': ['regain', 'Pandora'],
|
|
'A5': ['reindeer', 'paperweight'],
|
|
'A6': ['rematch', 'paragon'],
|
|
'A7': ['repay', 'paragraph'],
|
|
'A8': ['retouch', 'paramount'],
|
|
'A9': ['revenge', 'passenger'],
|
|
'AA': ['reward', 'pedigree'],
|
|
'AB': ['rhythm', 'Pegasus'],
|
|
'AC': ['ribcage', 'penetrate'],
|
|
'AD': ['ringbolt', 'perceptive'],
|
|
'AE': ['robust', 'performance'],
|
|
'AF': ['rocker', 'pharmacy'],
|
|
'B0': ['ruffled', 'phonetic'],
|
|
'B1': ['sailboat', 'photograph'],
|
|
'B2': ['sawdust', 'pioneer'],
|
|
'B3': ['scallion', 'pocketful'],
|
|
'B4': ['scenic', 'politeness'],
|
|
'B5': ['scorecard', 'positive'],
|
|
'B6': ['Scotland', 'potato'],
|
|
'B7': ['seabird', 'processor'],
|
|
'B8': ['select', 'provincial'],
|
|
'B9': ['sentence', 'proximate'],
|
|
'BA': ['shadow', 'puberty'],
|
|
'BB': ['shamrock', 'publisher'],
|
|
'BC': ['showgirl', 'pyramid'],
|
|
'BD': ['skullcap', 'quantity'],
|
|
'BE': ['skydive', 'racketeer'],
|
|
'BF': ['slingshot', 'rebellion'],
|
|
'C0': ['slowdown', 'recipe'],
|
|
'C1': ['snapline', 'recover'],
|
|
'C2': ['snapshot', 'repellent'],
|
|
'C3': ['snowcap', 'replica'],
|
|
'C4': ['snowslide', 'reproduce'],
|
|
'C5': ['solo', 'resistor'],
|
|
'C6': ['southward', 'responsive'],
|
|
'C7': ['soybean', 'retraction'],
|
|
'C8': ['spaniel', 'retrieval'],
|
|
'C9': ['spearhead', 'retrospect'],
|
|
'CA': ['spellbind', 'revenue'],
|
|
'CB': ['spheroid', 'revival'],
|
|
'CC': ['spigot', 'revolver'],
|
|
'CD': ['spindle', 'sandalwood'],
|
|
'CE': ['spyglass', 'sardonic'],
|
|
'CF': ['stagehand', 'Saturday'],
|
|
'D0': ['stagnate', 'savagery'],
|
|
'D1': ['stairway', 'scavenger'],
|
|
'D2': ['standard', 'sensation'],
|
|
'D3': ['stapler', 'sociable'],
|
|
'D4': ['steamship', 'souvenir'],
|
|
'D5': ['sterling', 'specialist'],
|
|
'D6': ['stockman', 'speculate'],
|
|
'D7': ['stopwatch', 'stethoscope'],
|
|
'D8': ['stormy', 'stupendous'],
|
|
'D9': ['sugar', 'supportive'],
|
|
'DA': ['surmount', 'surrender'],
|
|
'DB': ['suspense', 'suspicious'],
|
|
'DC': ['sweatband', 'sympathy'],
|
|
'DD': ['swelter', 'tambourine'],
|
|
'DE': ['tactics', 'telephone'],
|
|
'DF': ['talon', 'therapist'],
|
|
'E0': ['tapeworm', 'tobacco'],
|
|
'E1': ['tempest', 'tolerance'],
|
|
'E2': ['tiger', 'tomorrow'],
|
|
'E3': ['tissue', 'torpedo'],
|
|
'E4': ['tonic', 'tradition'],
|
|
'E5': ['topmost', 'travesty'],
|
|
'E6': ['tracker', 'trombonist'],
|
|
'E7': ['transit', 'truncated'],
|
|
'E8': ['trauma', 'typewriter'],
|
|
'E9': ['treadmill', 'ultimate'],
|
|
'EA': ['Trojan', 'undaunted'],
|
|
'EB': ['trouble', 'underfoot'],
|
|
'EC': ['tumor', 'unicorn'],
|
|
'ED': ['tunnel', 'unify'],
|
|
'EE': ['tycoon', 'universe'],
|
|
'EF': ['uncut', 'unravel'],
|
|
'F0': ['unearth', 'upcoming'],
|
|
'F1': ['unwind', 'vacancy'],
|
|
'F2': ['uproot', 'vagabond'],
|
|
'F3': ['upset', 'vertigo'],
|
|
'F4': ['upshot', 'Virginia'],
|
|
'F5': ['vapor', 'visitor'],
|
|
'F6': ['village', 'vocalist'],
|
|
'F7': ['virus', 'voyager'],
|
|
'F8': ['Vulcan', 'warranty'],
|
|
'F9': ['waffle', 'Waterloo'],
|
|
'FA': ['wallet', 'whimsical'],
|
|
'FB': ['watchword', 'Wichita'],
|
|
'FC': ['wayside', 'Wilmington'],
|
|
'FD': ['willow', 'Wyoming'],
|
|
'FE': ['woodlark', 'yesteryear'],
|
|
'FF': ['Zulu', 'Yucatan']
|
|
}
|
|
|
|
byte_to_even_word = dict([(unhexlify(k.encode("ascii")), both_words[0])
|
|
for k, both_words in raw_words.items()])
|
|
|
|
byte_to_odd_word = dict([(unhexlify(k.encode("ascii")), both_words[1])
|
|
for k, both_words in raw_words.items()])
|
|
|
|
even_words_lowercase, odd_words_lowercase = set(), set()
|
|
|
|
for k, both_words in raw_words.items():
|
|
even_word, odd_word = both_words
|
|
even_words_lowercase.add(even_word.lower())
|
|
odd_words_lowercase.add(odd_word.lower())
|
|
|
|
|
|
@implementer(IWordlist)
|
|
class PGPWordList(object):
|
|
def get_completions(self, prefix, num_words=2):
|
|
# start with the odd words
|
|
count = prefix.count("-")
|
|
if count % 2 == 0:
|
|
words = odd_words_lowercase
|
|
else:
|
|
words = even_words_lowercase
|
|
last_partial_word = prefix.split("-")[-1]
|
|
lp = len(last_partial_word)
|
|
completions = set()
|
|
for word in words:
|
|
if word.startswith(last_partial_word):
|
|
if lp == 0:
|
|
suffix = prefix + word
|
|
else:
|
|
suffix = prefix[:-lp] + word
|
|
# append a hyphen if we expect more words
|
|
if count + 1 < num_words:
|
|
suffix += "-"
|
|
completions.add(suffix)
|
|
return completions
|
|
|
|
def choose_words(self, length):
|
|
words = []
|
|
for i in range(length):
|
|
# we start with an "odd word"
|
|
if i % 2 == 0:
|
|
words.append(byte_to_odd_word[os.urandom(1)].lower())
|
|
else:
|
|
words.append(byte_to_even_word[os.urandom(1)].lower())
|
|
return "-".join(words)
|