extensible-keys

Reference implementation for the Extensible Key Format file format


License
GPL-3.0
Install
pip install extensible-keys==0.2.0

Documentation

Extensible Keys

Extensible Key Format:

The extensible key format was designed to serve as an alternative to .PEM with a (slightly) less verbose syntax and more importantly, built in password protection and authetification of your keys.


File Syntax:

All .ekf files follow this format:

[Base64-Encoded 128-Bit Salt]:[Base64-Encoded 128-Bit IV]
BEGIN KEYS:
[Key Data]
END KEYS!
CHECKSUM: [Base64-encoded SHA256 checksum]

128-Bit Salt:
This randomly generated salt is SHA256 hashed with your password to create an AES256 key which is used both for checksum validation, and encrypting/decrypting keys.

128-Bit IV:
The randomly generated Initilization Vector to encrypt/decrypt keys in AES CBC Mode

Key Data:
Keys are stored in a relational database with titles (i.e., "RSA PUB", "THE VAULT*") The titles are largely arbitrary with two notes:
1) No colons are allowed as this would break the formatting of the file
2) Titles for encrypted keys must end with an asterisk, as this tells the parser to encrypt/decrypt the associated key before saving/returning them.
Each new line in the file between "START KEYS:" and "STOP KEYS!" is counted as seperate entry in the key database, the proper syntax for one of these entries is as follows: [Key Title]: [Base64-Encoded (possibly encrypted) Key][*]

SHA256 Checksum: This SHA256 hash is created by hashing all of the keys together, and the encrypting the resulting bytes. This checksum is checked when reading an .ekf file, by generating the same hash, and instead of decrypting the original, the newly generated hash is encrypted instead, this simultaneously autheticates the password, and verifies the integrity of the stored keys


API:

This repository includes a python modue entitled "ExtensibleKeys.py" this should be considered the reference implementation. This module includes four functions:


write_file(file_name: str, keys: dict, pw: str):
Creates .ekf files given the desired file path and name, dictionary of keys, and and a password. The dictionary is in this format: {title(str): key(bytes)}. The password will be utf-8 encoded. Using the name of a prexisting file will raise an error to prevent accidental overwriting.

read_file(file_name: str, pw: str) -> dict:
Reads .ekf files, given a file name and password, raises a DecryptionError if the password is incorrect or if the checksum can't be passed.

append_keys(file_name: str, new_keys: dict, pw: str):
Appends keys to a file, performs exactly like write_file except for that it appends to an existing file as opposed to creating one.

delete_keys(file_name: str, target_keys: tuple, pw: str):
Will remove specified keys from a file. Raises a ValueError if a non-existant key is passed.

Security considerations and other notes:

Implement standard password requirements for protecting these files.

As with any other password, randomized passwords should be implemented if possible

The same error being used for any time decryption fails is intentional, as it prevents Padding Orace attacks. All future implementations should provide simmilar ambiguity in such cases.

It's advisable to name your keys in such a way that makes their function non-obvious, ideally slowing down any attacker that may manage to gain access to the password for a large file

These files are suceptible to brute-force attacks, so it's still strongly recomended to not publicize files with sensitive keys, even if they are encrypted.

One could make an implementation of this file format where accessing a file with no encrypted keys (i.e., a list of PGP public keys) doesn't require a password, however this could be considered a security risk as the password is also used to validate the authenticity of the stored keys.