Skip to content

Submodule vault

derivepassphrase.vault

Python port of the vault(1) password generation scheme.

Vault

Vault(
    *,
    phrase: bytes | bytearray | str = b"",
    length: int = 20,
    repeat: int = 0,
    lower: int | None = None,
    upper: int | None = None,
    number: int | None = None,
    space: int | None = None,
    dash: int | None = None,
    symbol: int | None = None
)

A work-alike of James Coglan’s vault.

Store settings for generating (actually: deriving) passphrases for named services, with various constraints, given only a master passphrase. Also, actually generate the passphrase. The derivation is deterministic and non-secret; only the master passphrase need be kept secret. The implementation is compatible with vault.

James Coglan explains the passphrase derivation algorithm in great detail in his blog post on said topic: A principally infinite bit stream is obtained by running a key-derivation function on the master passphrase and the service name, then this bit stream is fed into a Sequin to generate random numbers in the correct range, and finally these random numbers select passphrase characters until the desired length is reached.

Parameters:

Name Type Description Default
phrase bytes | bytearray | str

The master passphrase from which to derive the service passphrases. If a string, then the UTF-8 encoding of the string is used.

b''
length int

Desired passphrase length.

20
repeat int

The maximum number of immediate character repetitions allowed in the passphrase. Disabled if set to 0.

0
lower int | None

Optional constraint on ASCII lowercase characters. If positive, include this many lowercase characters somewhere in the passphrase. If 0, avoid lowercase characters altogether.

None
upper int | None

Same as lower, but for ASCII uppercase characters.

None
number int | None

Same as lower, but for ASCII digits.

None
space int | None

Same as lower, but for the space character.

None
dash int | None

Same as lower, but for the hyphen-minus and underscore characters.

None
symbol int | None

Same as lower, but for all other hitherto unlisted ASCII printable characters (except backquote).

None

Raises:

Type Description
ValueError

Conflicting passphrase constraints. Permit more characters, or increase the desired passphrase length.

create_hash classmethod

create_hash(
    phrase: bytes | bytearray | str,
    service: bytes | bytearray | str,
    *,
    length: int = 32
) -> bytes

Create a pseudorandom byte stream from phrase and service.

Create a pseudorandom byte stream from phrase and service by feeding them into the key-derivation function PBKDF2 (8 iterations, using SHA-1).

Parameters:

Name Type Description Default
phrase bytes | bytearray | str

A master passphrase, or sometimes an SSH signature. Used as the key for PBKDF2, the underlying cryptographic primitive. If a string, then the UTF-8 encoding of the string is used.

required
service bytes | bytearray | str

A vault service name. Will be suffixed with Vault._UUID, and then used as the salt value for PBKDF2. If a string, then the UTF-8 encoding of the string is used.

required
length int

The length of the byte stream to generate.

32

Returns:

Type Description
bytes

A pseudorandom byte string of length length.

Note

Shorter values returned from this method (with the same key and message) are prefixes of longer values returned from this method. (This property is inherited from the underlying PBKDF2 function.) It is thus safe (if slow) to call this method with the same input with ever-increasing target lengths.

Examples:

>>> # See also Vault.phrase_from_key examples.
>>> phrase = bytes.fromhex('''
... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
... 00 00 00 40
... f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86
... 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd
... 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
... 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
... ''')
>>> Vault.create_hash(phrase, 'some_service', length=4)
b'M\xb1<S'
>>> Vault.create_hash(phrase, b'some_service', length=16)
b'M\xb1<S\x827E\xd1M\xaf\xf8~\xc8n\x10\xcc'
>>> Vault.create_hash(phrase, b'NOSUCHSERVICE', length=16)
b'\x1c\xc3\x9c\xd9\xb6\x1a\x99CS\x07\xc41\xf4\x85#s'

generate

generate(
    service_name: bytes | bytearray | str,
    /,
    *,
    phrase: bytes | bytearray | str = b"",
) -> bytes

Generate a service passphrase.

Parameters:

Name Type Description Default
service_name bytes | bytearray | str

The service name. If a string, then the UTF-8 encoding of the string is used.

required
phrase bytes | bytearray | str

If given, override the passphrase given during construction. If a string, then the UTF-8 encoding of the string is used.

b''

Returns:

Type Description
bytes

The service passphrase.

Examples:

>>> phrase = b'She cells C shells bye the sea shoars'
>>> # Using default options in constructor.
>>> Vault(phrase=phrase).generate(b'google')
b': 4TVH#5:aZl8LueOT\\{'
>>> # Also possible:
>>> Vault().generate(b'google', phrase=phrase)
b': 4TVH#5:aZl8LueOT\\{'

phrase_from_key classmethod

phrase_from_key(key: bytes | bytearray) -> bytes

Obtain the master passphrase from a configured SSH key.

vault allows the usage of certain SSH keys to derive a master passphrase, by signing the vault UUID with the SSH key. The key type must ensure that signatures are deterministic.

Parameters:

Name Type Description Default
key bytes | bytearray

The (public) SSH key to use for signing.

required

Returns:

Type Description
bytes

The signature of the vault UUID under this key, unframed but encoded in base64.

Raises:

Type Description
ValueError

The SSH key is principally unsuitable for this use case. Usually this means that the signature is not deterministic.

Examples:

>>> import base64
>>> # Actual Ed25519 test public key.
>>> public_key = bytes.fromhex('''
... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
... 00 00 00 20
... 81 78 81 68 26 d6 02 48 5f 0f ff 32 48 6f e4 c1
... 30 89 dc 1c 6a 45 06 09 e9 09 0f fb c2 12 69 76
... ''')
>>> expected_sig_raw = bytes.fromhex('''
... 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39
... 00 00 00 40
... f0 98 19 80 6c 1a 97 d5 26 03 6e cc e3 65 8f 86
... 66 07 13 19 13 09 21 33 33 f9 e4 36 53 1d af fd
... 0d 08 1f ec f8 73 9b 8c 5f 55 39 16 7c 53 54 2c
... 1e 52 bb 30 ed 7f 89 e2 2f 69 51 55 d8 9e a6 02
... ''')
>>> # Raw Ed25519 signatures are 64 bytes long.
>>> signature_blob = expected_sig_raw[-64:]
>>> phrase = base64.standard_b64encode(signature_blob)
>>> Vault.phrase_from_key(phrase) == expected
True