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.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 |
None
|
number
|
int | None
|
Same as |
None
|
space
|
int | None
|
Same as |
None
|
dash
|
int | None
|
Same as |
None
|
symbol
|
int | None
|
Same as |
None
|
Raises:
Type | Description |
---|---|
ValueError
|
Conflicting passphrase constraints. Permit more characters, or increase the desired passphrase length. |
Warning
Because of repetition constraints, it is not always possible to detect conflicting passphrase constraints at construction time.
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
|
required |
length
|
int
|
The length of the byte stream to generate. |
32
|
Returns:
Type | Description |
---|---|
bytes
|
A pseudorandom byte string of 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. |
Raises:
Type | Description |
---|---|
ValueError
|
Conflicting passphrase constraints. Permit more characters, or increase the desired passphrase length. |
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\\{'
Conflicting constraints are sometimes only found during generation.
>>> # Note: no error here...
>>> v = Vault(
... lower=0,
... upper=0,
... number=0,
... space=2,
... dash=0,
... symbol=1,
... repeat=2,
... length=3,
... )
>>> # ... but here.
>>> v.generate(
... '0', phrase=b'\x00'
... )
Traceback (most recent call last):
...
ValueError: no allowed characters left
is_suitable_ssh_key
staticmethod
¶
is_suitable_ssh_key(
key: bytes | bytearray,
/,
*,
client: SSHAgentClient | None = None,
) -> bool
Check whether the key is suitable for passphrase derivation.
Some key types are guaranteed to be deterministic. Other keys types are only deterministic if the SSH agent supports this feature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
key
|
bytes | bytearray
|
SSH public key to check. |
required |
client
|
SSHAgentClient | None
|
An optional SSH agent client to check for additional deterministic key types. If not given, assume no such types. |
None
|
Returns:
Type | Description |
---|---|
bool
|
True if and only if the key is guaranteed suitable for use in deriving a passphrase deterministically (perhaps restricted to the indicated SSH agent). |
phrase_from_key
classmethod
¶
phrase_from_key(
key: bytes | bytearray,
/,
*,
conn: SSHAgentClient | socket | None = None,
) -> 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 (perhaps only in conjunction with the given SSH agent).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
key
|
bytes | bytearray
|
The (public) SSH key to use for signing. |
required |
conn
|
SSHAgentClient | socket | None
|
An optional connection hint to the SSH agent. See
|
None
|
Returns:
Type | Description |
---|---|
bytes
|
The signature of the vault UUID under this key, unframed but encoded in base64. |
Raises:
Type | Description |
---|---|
KeyError
|
|
NotImplementedError
|
|
OSError
|
|
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