Skip to content

Secret

Manages read & write operations for files that contain sensitive encrypted data.

Each instance of this class represents a unique string containing secret information that must be encrypted before saving it to a file, and subsequently decrypted before it can be accessed again.

FAQ - How does the encryption work?

This class uses Fernet symmetric encryption from the cryptography package to encrypt and decrypt data, optionally with extra protection in the form of a password. The encrypted data is guaranteed to be unreadable without the original key that was used to encrypt it. Fernet keys are uniquely and randomly generated for each individual secret at encryption time.

A secret is therefore represented in the file system by two separate .key files, both of which are named using the secret's uid (unique ID). One of the files contains the encrypted content of the secret, and the other file contains the fernet key required to decrypt it. Each of these files is useless without the other. 🔐

Info - Fernet encryption with passwords

Factoring a password into the encryption of a secret can add an extra layer of protection because the password will not be stored anywhere on the file system. This means that even if a malicious actor were to gain access to both the content.key and fernet.key files of a secret, they still would not be able to decipher the original data.

If a password is provided when a secret's write() method is invoked, the data will be encrypted using an algorithm based on this reference implementation. The password will be run through the PBKDF2HMAC key derivation function to obtain the Fernet key for the secret. As a result, the original password will have to be accurately entered in order to "complete" the key every time the secret is decrypted.

__init__(uid, requires_password=False, display_name=None, storage_directory=None, valid_pattern=None)

Parameters:

Name Type Description Default
uid str

A unique string to identify this secret. Will be used in the names of the files containing this secret's data.

required
requires_password bool

Whether a user-provided password is required in order to read and/or write the data for this secret.

False
display_name str | None

A human-readable string describing this secret. Will be displayed in the CLI when referring to this secret. If omitted, the uid will be displayed instead.

None
storage_directory str | Path | None

Where to store the files containing this secret's data. If omitted, they will be saved in a default .botstrap_keys directory.

None
valid_pattern str | re.Pattern | Callable[[str], Any] | None

A string, regex Pattern, or function for determining whether a given input str fits the expected pattern for this secret. Will be used to validate the secret's data before encryption and after decryption. If omitted, any string will be considered "valid data".

None

Manages read & write operations for files that contain sensitive encrypted data.

Each instance of this class represents a unique string containing secret information that must be encrypted before saving it to a file, and subsequently decrypted before it can be accessed again.

FAQ - How does the encryption work?

This class uses Fernet symmetric encryption from the cryptography package to encrypt and decrypt data, optionally with extra protection in the form of a password. The encrypted data is guaranteed to be unreadable without the original key that was used to encrypt it. Fernet keys are uniquely and randomly generated for each individual secret at encryption time.

A secret is therefore represented in the file system by two separate .key files, both of which are named using the secret's uid (unique ID). One of the files contains the encrypted content of the secret, and the other file contains the fernet key required to decrypt it. Each of these files is useless without the other. 🔐

Info - Fernet encryption with passwords

Factoring a password into the encryption of a secret can add an extra layer of protection because the password will not be stored anywhere on the file system. This means that even if a malicious actor were to gain access to both the content.key and fernet.key files of a secret, they still would not be able to decipher the original data.

If a password is provided when a secret's write() method is invoked, the data will be encrypted using an algorithm based on this reference implementation. The password will be run through the PBKDF2HMAC key derivation function to obtain the Fernet key for the secret. As a result, the original password will have to be accurately entered in order to "complete" the key every time the secret is decrypted.

file_path() -> Path property

The path of the file that may contain this secret's encrypted data.

This property will only return the content file path, as the fernet file is irrelevant outside of this class. The return value will be an instance of pathlib.Path, but it is not guaranteed to point to an existing file (e.g. if this secret hasn't been created/saved yet, or has been deleted).

min_pw_length() -> int property

The minimum length for this secret's password, if one is required.

For secrets that require a password, this property will return 8 (chosen arbitrarily to try and balance security vs. convenience). For secrets that don't require a password, this will return 0.

clear() -> None

Deletes all files containing data related to this secret, if any exist.

This method does not scan the entire system to locate the files for a secret - it only checks the storage_directory that was specified upon instantiation.

Tip - Don't scramble your secrets!

If a secret's .key files are renamed or moved out of their original directory without a corresponding change to the uid and/or storage_directory constructor parameters (or vice versa), then the secret will behave as if there are no existing files associated with it. Fortunately, this can easily be resolved by either moving the files back into place or updating the constructor parameters in your code.

read(password: str | None = None) -> str | None

Returns the decrypted data from this secret's file if it exists and is valid.

The password param must be provided if this secret was originally encrypted with a password, and must not be provided if the opposite is true. If provided, it must match the original password or else the decrypted data will not be valid and this method will return None.

Parameters:

Name Type Description Default
password str | None

The password originally used to create this secret, if applicable. Otherwise, this should be None.

None

Returns:

Type Description
str | None

The data for this secret if it exists & can be decrypted, otherwise None.

write(data: str, password: str | None = None) -> None

Encrypts and writes the data to a file, optionally protected by a password.

If the password param is provided, it must be at least 8 characters long (see min_pw_length) and will have to be provided again whenever read() is invoked to decrypt this secret.

Omitting the password parameter means that only the secret's two .key files will be required in order to decrypt it. This is both more convenient and more dangerous, so choose wisely. 🧞

Parameters:

Name Type Description Default
data str

A string containing sensitive information to be encrypted before being stored in a file.

required
password str | None

An optional string that can improve the security of this secret. If omitted, a password will not be factored into the encryption algorithm for this secret.

None

Raises:

Type Description
ValueError

If the data is not considered valid according to the valid_pattern that was specified when this secret was instantiated.