keyper 0.6.0

TUI password manager
Documentation
# Keyper

A basic password manager with a TUI interface.

## Storage

The [sled] database is used for on-disk storage. However, [sled] does not natively support encryption.

Instead, we use an encryption scheme composed from well-tested primitives implemented in the [RustCrypto] cryptographic libraries.

### Salt Encryption

A salt is a cryptographic value used to add randomness to inputs, and is commonly used in key-derivation algorithms.

We use the following scheme for password-based key derivation (in pseudo-code):

```text
SALT = {random 32-bytes}
PASSWORD = {user-input password}
ENCRYPTION_KEY = sha3::TurboShake256::hash(SALT | PASSWORD);
```

However, we also want to keep the salt value secret when storing to disk (maybe a bit of paranoia is a good thing? :3)

The following scheme is used to encrypt the salt for storage (in pseudo-code):

```text
SALT = {random 32-bytes}
PASSWORD = {user-input password}

SALT_KEY = sha3::TurboShake256::hash(argon2id::hash(PASSWORD | <32-bytes of zeroes>));
SALT_NONCE = {random 12-bytes}
SALT

ENCRYPTED_SALT = chacha20poly1305::encrypt(SALT_KEY, SALT_NONCE, SALT)
```

The entry is then stored in the database as:

```text
SALT_DB_KEY = sha3::TurboShake256::hash("salt");
sled::Db::insert(SALT_DB_KEY, SALT_NONCE | ENCRYPTED_SALT);
```

### Password Key-derivation

The user-input password is combined with the random salt to derive the encryption key for database entries:

```text
SALT = (random 32-bytes from previous step)
PASSWORD = (user-input password)

DB_ENCRYPTION_KEY = sha3::TurboShake256::hash(argon2id::hash(PASSWORD | SALT))
```

The `DB_ENCRYPTION_KEY` is never stored in the database, even in an encrypted form.

It is only stored in-memory to decrypt database entries when loading them into memory, and encrypting new entries for storage in the database.

### Entry Encryption

Entries are encrypted in much the same way as the database `SALT`, with the slight change that the fields need to be length-prefix encoded.

We currently use 32-bit, little-endian length fields.

```text
TITLE = Entry.title;
CONTENT = Entry.content;
TITLE_LEN = (TITLE.len() as u32).to_le_bytes();
CONTENT_LEN = (CONTENT.len() as u32).to_le_bytes();
ENTRY_NONCE = {random 12-bytes}
ENTRY_INDEX = EntryList.len()
ENTRY_PLAINTEXT = TITLE_LEN | TITLE | CONTENT_LEN | CONTENT

ENTRY_DB_KEY = sha3::TurboShake256::hash(ENTRY_INDEX)
ENCRYPTED_ENTRY = chacha20poly1305::encrypt(DB_ENCRYPTION_KEY, ENTRY_NONCE, ENTRY_PLAINTEXT)

sled::Db::insert(ENTRY_DB_KEY, ENTRY_NONCE | ENCRYPTED_ENTRY) 
```

Using the length-prefixed value encoding is very common, and allows for extending the `Entry` fields almost indefinitely.

By using the AEAD ChaCha20Poly1305 algorithm, we also get the benefit of database corruption protection.

If someone messes with your database entries, they won't decrypt properly.

## Credits

- TUI powered by [ratatui] + [crossterm]
- Crypto provided by [RustCrypto]
- Storage provided by [sled]
- TOTP provided by [totp-rs]
- Clipboard support provided by [crossterm] + [arboard]
- Keepass support provided by [keepass]

[ratatui]: https://docs.rs/ratatui
[RustCrypto]: https://github.com/RustCrypto
[sled]: https://docs.rs/sled
[totp-rs]: https://docs.rs/totp-rs
[crossterm]: https://docs.rs/crossterm
[arboard]: https://docs.rs/arboard
[keepass]: https://docs.rs/keepass

## Fuck AI

This application was made with 100% human engineering, entirely without the aid of LLMs.