API Reference#

Installation#

The libary is available from PyPI. You can install it like so:

$ pip install papass

API#

papass is a simple library to generate passphrases and passwords.

Everything from the papass module is considered public. Everything else is considered private.

Usage#

Passphrase generation#

First we create two word lists:

>>> wordlist_1 = WordList(["bear", "dog", "duck"])
>>> wordlist_2 = WordList(["duck", "cat"])

Word lists implement Sequence, are sorted, can be written to disk, and read from disk.

>>> wordlist = wordlist_1 + wordlist_2
>>> assert wordlist == WordList(["bear", "dog", "duck", "cat"]), "It deduplicates"
>>> assert list(wordlist) == ["bear", "cat", "dog", "duck"], "It is sorted"
>>>
>>> file_path = "/tmp/my_wordlist.txt"
>>> wordlist.to_file(file_path)
>>> assert wordlist == WordList.from_file(file_path)

Next we create a phrase generator. To do that we need a random source. Here we take the systems most secure random source for simplicity but you can also take the one which requires physical dice to be thrown.

>>> rng = SystemRng()
>>> ppg = PassphraseGenerator(wordlist=wordlist, rng=rng, delimiter=" ")

This can now be used to create a random phrase:

>>> num_words = 5
>>> ppg.generate(num_words)
PassphraseResult(passphrase=..., entropy=10.0, entropy_is_guaranteed=True)

The actual passphrase is random of course. It could be something like 'dog duck cat duck duck'. The entropy is 10.0 in this example because there are 2**10==4**5 possible phrases made up from 5 words with 4 possibilities each.

The entropy_is_guaranteed=True tells us that the entropy is indeed not lower than expected. Note that in principle it might be possible that there are less possible phrases if e.g. you choose '' (empty string) as the delimiter and some words contain some of the other words.

Consider for example the wordlist foo bar foobar barfoo with num_words=5. Then foobarfoo can come from foobar, foo or from foo, barfoo.

If entropy_is_guaranteed=False this doesn’t necessarily mean that the entropy is lower (and even if, it is probably not much lower). The check relies on a simple heuristic which has the property that True is always correct and False basically means don’t know.

Password generation#

Create a password generator like so

>>> rng = SystemRng()
>>> pwg = PasswordGenerator(alphabet="0123", rng=rng)

A password of length 10 can be generated like this

>>> pwg.generate(10)
PasswordResult(password=..., entropy=20.0)

The actual password is random of course. It could be any sequence of characters from the alphabet like e.g '3002212230'. The entropy is 20.0 because there are 2**20.0 possible passwords of length 10 over this alphabet.

class papass.DiceRng(*, query_for_dice: QueryForDice | None = None, num_sides: int = 6, required_success_probability: float = 0.999)#

Bases: RngBase

Random number generator relying on the user to throw physical dice.

__init__(*, query_for_dice: QueryForDice | None = None, num_sides: int = 6, required_success_probability: float = 0.999)#

Create a DiceRng.

Parameters:
  • query_for_dice – A callback to query for dice rolls. None means use default.

  • num_sides – Number of sides of the dice.

  • required_success_probability – The minimal required probability that randbelow does not reject a roll. If upper is a power of num_sides this probability is always 100%. But in general the number of required rolls increases with this probability.

randbelow(upper: int) int#

Generate random integers i with 0 <= i < upper.

If the dice are fair (all sides occur with the same probability) and the rolls are independent the distribution of i is uniform.

class papass.PassphraseGenerator(*, wordlist: WordList, rng: RngBase, delimiter: str = ' ')#

Bases: object

Generate phrases from a wordlist using a random number generator.

__init__(*, wordlist: WordList, rng: RngBase, delimiter: str = ' ')#

Create a passphrase generator.

Parameters:
  • wordlist – The words to draw from.

  • rng – The randomness source to be used to draw words.

  • delimiter – At most a single character to be put between the generated words.

generate(length: int) PassphraseResult#

Generate a random passphrase.

The passphrase is essentially created by a single application of rng.choice to the set of all possible passwords (lazy iterators make this possible even for large passphrase spaces).

Parameters:

length – The number of words in the passphrase.

Returns:

A result object containing the generated passphrase.

class papass.PassphraseResult(passphrase: str, entropy: float, entropy_is_guaranteed: bool)#

Bases: object

Represents the result of passphrase generation.

__eq__(other)#

Return self==value.

__init__(passphrase: str, entropy: float, entropy_is_guaranteed: bool) None#
entropy: float#

Estimates how secure the passphrase is against brute-force cracking.

Ideally the number of passphrases which could have been generated with the same settings is 2**entropy. We use a simple formula to estimate the entropy, hence the estimate might not be guaranteed to be exact.

entropy_is_guaranteed: bool#

Whether we can guarantee that the entropy is correctly estimated.

True means that the entropy estimate is exact. False means that our heuristic could not prove that the estimate is exact (it might be exact though).

Note that even if the entropy estimate is not exact it probably does not overestimate it too much in most cases.

See also: https://papass.readthedocs.io/en/stable/usage_cli.html#entropy-guarantee

passphrase: str#

The generated passphrase.

class papass.PasswordGenerator(*, alphabet: Sequence[str], rng: RngBase)#

Bases: object

Generate passwords from a list of characters using a random number generator.

__init__(*, alphabet: Sequence[str], rng: RngBase)#

Create a password generator.

Parameters:
  • alphabet – A sequence of characters to be used in password creation.

  • rng – The randomness source to be used to draw characters.

The the alphabet gets deduplicated internally.

generate(length: int) PasswordResult#

Generate a random password.

The password is essentially created by a single application of rng.choice to the set of all possible passwords (lazy iterators make this possible even for large password spaces).

Parameters:

length – The length of the password.

Returns:

A result object containing the generated password.

class papass.PasswordResult(password: str, entropy: float)#

Bases: object

Represents the result of password generation.

__eq__(other)#

Return self==value.

__init__(password: str, entropy: float) None#
entropy: float#

Estimates how secure the password is against brute-force cracking.

The number of possible passwords which could have been generated with the same settings is 2**entropy.

password: str#

The generated password.

class papass.QueryForDice(*args, **kwargs)#

Bases: Protocol

Interface for the query_for_dice option of DiceRng.

__call__(*, num_sides: int, required_num_rolls: int) list[int]#

Return dice rolls to be turned into the output of DiceRng.randbelow.

Called within DiceRng.randbelow until its output is not rejected.

Parameters:
  • num_sides – Number of sides of the dice.

  • require_num_rolls – The number of rolls.

Returns:

A list of rolls of the required length.

__init__(*args, **kwargs)#
notify_rejection() None#

React to rejected dice rolls.

Called by DiceRng if output of __call__ got rejected.

class papass.QueryUserForDice#

Bases: object

Asks the user to roll some dice.

__call__(*, num_sides: int, required_num_rolls: int) list[int]#

Ask user for desired number of dice rolls.

Return None if user gives invalid input.

notify_rejection() None#

Print a message on the rejection to stdout.

Does nothing else.

class papass.RngBase#

Bases: ABC

Base for all random number generators.

final choice(items: Sequence[T]) T#

Return a random item from items.

The choices are determined by consecutive calls to randbelow which determines the index of the item to be chosen.

abstract randbelow(upper: int) int#

Return a random integer i with 0 <= i < upper.

class papass.SystemRng#

Bases: RngBase

Random number generator using the most secure rng of the operating system.

randbelow(upper: int) int#

Get a random integer i with 0 <= i < upper.

i is uniformly distributed.

class papass.WordList(words: Iterable[str] = [], *, min_word_size: int = 1, max_word_size: int | None = None, remove_leading_digits: bool = False)#

Bases: Sequence[str]

Represents an sorted sequence of unique words.

Internally the list of words is deduplicated and sorted (via sorted).

Example#

>>> wordlist = WordList(["c", "b", "a"])
>>> wordlist
WordList(['a', 'b', 'c'])
>>> assert wordlist[0] == "a"
>>> assert list(wordlist) == ["a", "b", "c"]
>>> assert wordlist == WordList(["a", "b", "c"])
__add__(other: WordList) WordList#
__add__(other: list[str]) WordList

Combine two word lists to a new word list made of the union of their words.

If the second summand is a list of words it behaves as if this list was converted to a word list first.

__eq__(other: object) bool#

Two word lists are equal if they contain the same words.

__getitem__(index: int) str#
__getitem__(index: slice) WordList

Get a word at an index or a new word list from a slice.

__init__(words: Iterable[str] = [], *, min_word_size: int = 1, max_word_size: int | None = None, remove_leading_digits: bool = False)#

Construct a wordlist from words.

Parameters:
  • words – Words to construct wordlist from.

  • min_word_size – Filter out words which are shorter than this.

  • max_word_size – Filter out words which are longer than this. None means no filtering.

  • trim_leading_digits – Some word lists contain lines like 12345  someword. If True we just read someword.

__len__() int#

Return the number of words.

static from_file(file_path: Path | str, **options: Any) WordList#

Create a wordlist from a file of words (newline separated).

The options are the same as those for __init__.

static from_frequency_file(file_path: Path | str, *, min_frequency: int = 1, max_frequency: int | None = None, **options: Any) WordList#

Create a wordlist from a frequency file.

We assume the following format for each line:

LINENO TAB WORD TAB FREQUENCY

We ignore the LINENO (digits). We are only interested in WORD (str) and FREQUENCY (int). Example:

1   der     1000
2   haus    120
3   felsen  100
4   foo bar-baz     100
5   rareword        1

Note that the three columns must be separated by tabs and that no tabs appear elsewhere.

Parameters:
  • min_frequency – Consider only words with at least this frequency.

  • max_frequency – Consider only words with at most this frequency. None means infinite.

to_file(file_path: Path | str) None#

Write this wordlist to a file (overwrites if file exists).