+++ title = "ciphey" weight = 1 [taxonomies] tags = [] +++ Simply put, `ciphey` is a password and secret manager that is like [`pass`](https://passwordstore.org) if it used [`age`](https://age-encryption.org) instead of PGP. More than anything, it is an experiment to determine how cryptography can be combined in a minimalist manner to protect passwords in a way that accounts for the most realistic threats to their confidentiality, reliability, usability, and resiliency. It takes many of the database and key management ideas from [1Password](https://1password.com) and [Bitwarden](https://bitwarden.com) and combines them with the Unix-like philosophy of `pass`. To be clear, `ciphey` is still in development. [The remaining work is outlined below](#remaining-work). While I will be using it as my actual password manager, I strongly recommend that you only experiment with it and not rely on it for any actual passwords. *NOTE: This page is a work in progress.* ## The Basics My goal is for `ciphey` to be simple enough that someone with a bit of knowledge about cryptography who wants to understand how their passwords are protected can do so after a short explanation. This sections aims to be that explanation. ### Entries Everything you store in `ciphey` goes into an entry. Generally, you'll have one `entry` per password. However, passwords come in many different forms: as a result, entries are plaintext, which means any file can be used as an entry. This doesn't mean there is no structure to entries. For example, take a look at the entry for a hypothetical [`xkcd.com`](https://xkcd.com) account below: ``` correct horse battery staple name: xkcd.com tag: comics username: Tr0ub4dor&3 url: https://xkcd.com/936 To anyone who understands information theory and security and is in an infuriating argument with someone who does not (possibly involving mixed case), I sincerely apologize. ``` The first line of every entry is interpreted as the entry's "secret." In this case, the secret is `correct horse battery staple`, the password for the account. This is usually a password or private key, but can be any value that you might want to access by default. If this doesn't work for you for some reason, no worries; just leave the line blank. The secret is followed by any number of optional field-value pairs. Every field-value pair lives on its own line and can contain any information you want, so long as it follows the format `FIELD: VALUE`. Fields can be repeated and in any order. The only limitation is that your `FIELD` cannot contain a `: ` (`:` followed by a space), but the `VALUE` can. There are no `FIELD`s with any special meaning (yet), but keeping them consistent will help you find entries more quickly. I strongly recommend giving each entry a `name` field, as this is what `ciphey` uses to search for entries by default. Finally, the contents of the file from the first line that doesn't match the pattern `FIELD: VALUE` down to the end of the file is left uninterpreted, meaning that you can save any text that you'd like there. This is called the "note." In the example above, the "note" is the caption text from [xkcd.com/936](https://xkcd.com/936). Separate the notes and the preceding section with a newline. ### The Entries Your `ciphey` database is a folder of files that can live on any filesystem. This folder is completely encrypted, and thus can safely be backed up, shared, or otherwise accessed without any unauthorized party gaining access to the contents of the file. ``` ciphey/ └── entries/ ├── age1sdnql3ksstj7krh7azddygh860f6uttus5t3e9j52t4k5z34kutqyzgr4e.age ├── age1ryutmsg486w4y06kq7w0rqm2s08r3sgzlwcl9p9p00jducedevvqkgrvyw.age ├── age1mqr5htmadgzlmcdjks79d6ucfe5k46ruavnaxm08m64lpl6h5udqhkwwvp.age └── age1r908d20yj3ntp9q8g0448kwflfgf3au2qmhkkx7zp8xnesfdjfhqpd3r2d.age ``` Each file in the "entries" folder contains a single "entry." The name of each file is the `age` public key the entry is encrypted to. Every entry into your database is encrypted to a unique public key. Without that public key's corresponding private key, the contents of the entry cannot be accessed. However, keep in mind that some metadata does leak. Namely, any data associated with the filesystem (date created, date last modified, owner/group, etc.) is NOT protected by `ciphey` by default. It's still a good idea to keep your database away from any curious eyes when it's avoidable. ### Your Keys Every time you unlock your `ciphey` database, you must have two things: 1. Your `Primary Passphrase` (something you know) 2. A `Device Key` (something you have) Every "device" you have (i.e. your desktop, laptop, and phone) has its own `Device Key`. This key is generated and stored as a normal file when you first set up `ciphey` on the device, and never leaves it. Each `Device Key` is encrypted with your `Primary Passphrase`. Without your `Primary Passphrase`, the `Device Key` cannot be used. Alternatively, you can use a `Device Key` stored on a hardware token, such as a Yubikey or OnlyKey. This has the added benefit that the key material never leaves the hardware token, protecting against attacks that can steal key material from your computer's local disk. In order to decrypt your entries, an attacker would have to physically have access your hardware token, in addition to learning your `Primary Passphrase`. ### Entry Keys Every entry in your database is encrypted to a specific `Entry Key`. These entry keys are stored next to the `database` directory in the `keys` directory. Every entry gets its own key so that access can be cryptographically restricted on an entry-by-entry basis. The keys stored on the filesystem are encrypted to every `Device Key` that you've given access to the entry. ### Disaster Recovery When you set up your `ciphey` database for the first time, you'll be prompted to create a `Recovery Key`, which is an `age` key encrypted with your `Recovery Passphrase` (By default, your `Primary Passphrase`, but this can optionally be changed). This key should be written down and stored in a safe place, such as in a safe. This way, if you lose all of your devices, you can still regain access to your passwords. You can also give a copy of this key and your `Recovery Passphrase` to anybody else who should have access to your accounts in case of an emergency. This way, you can be sure that you're unlikely to ever lose access to your `ciphey` keys. ### Summary Every entry is encrypted with its own unique `Entry Key`. `Entry Key`s are encrypted to each `Device Key` that is granted access to the entry. They can be encrypted to other people's `Device Key`s to share an entry with them. `Device Key`s are encrypted with your `Primary Passphrase`. They can either live on your filesystem or on a hardware token, like a Yubikey. When you first set up `ciphey`, you also create a `Recovery Key`, which every `Entry Key` is encrypted to by default. This `Recovery Key` is encrypted with your `Recovery Passphrase`, which are to be stored offline in a safe place and/or given to trusted individuals so they can be used in case of an emergency. ### Usage ## Implementation ### Entry Parsing The entry format was designed to be easy to parse for both humans and programs. The following Rust snippet is a (simplified) example of parsing an Entry from a String `s`: ```rust // The entry's secret let mut secret: String; // A vector of key/value pairs let mut fields: (String, String) = Vec::new(); // The entry's notes let mut notes: Option = None; // An iterator over the decrypted lines of the entry file let mut lines = s.lines(); // The first line contains the secret secret = lines.next(); while let Some(line) = lines.by_ref().next() { if let Some((key, value)) = line.split_once(": ") { // Add the key and value to fields fields.push((key, value)); } else { // Take the rest of the lines and put them into notes notes = Some( std::iter::once(line) .chain(lines.by_ref()) .collect::>() .join("\n"), ); } } ``` This particular implementation has a lot of room for improvement. The biggest issue adding complexity is that Rust's `take_while` iterator method is consuming, not peekable, meaning that ownership of the lines it checks is passed to its closure. Thus, the following snippet, which is much easier to read, would not work: ```rust // An iterator over the decrypted lines of the entry file let mut lines = s.lines(); // The first line contains the secret secret = lines.next(); // Take lines while they can be parsed as key/value pairs split by ": " fields = lines.take_while(line.split_once(": ")).map(...); // first line failing take_while is incorrectly dropped here // Join the remaining lines as notes notes = lines.collect::>().join("\n"); ``` This is because even though the first line that fails the conditional is consumed by `take_while`, it would neither be added to the fields (as it cannot be parsed as one) nor collected into the vector over the lines of notes, instead being dropped, causing data loss. There are a few Rust crates that provide a peekable `take_while`, but they're too big to justify as adding as dependencies for just this feature. It's possible that a simpler approach is possible using an iterative method, but I have not been able to come up with one yet. ## Remaining Work ### Password History At the moment, there is no built-in support for keeping track of entry changes over time. `pass` uses git to keep track of changes at the filesystem level, while password managers like Bitwarden have built-in support for storing the history of passwords over time. Currently, there is nothing stopping someone from manually checking entries into git or another backup solution. However, one way to provide better integration into `ciphey` would be to allow for `pre-hooks` and `post-hooks`. These hooks are scripts that are run before or after specific actions are taken by `ciphey`. This would allow, for example, automatically committing the changes to a git repository and pushing those changes to a remote server after every change made by `ciphey`. ### File storage For the moment, entries must be plaintext files. If you want to encrypt an arbitrary file with `ciphey`, you have to manually create an encryption key, store that key as an entry in `ciphey`, and then manually encrypt said file with the newly-created key. It would be beneficial to devise a way to treat encrypted files just like any entry so that the key material for other files can be automatically managed in the same way. ### Backup While `ciphey` makes sure that you don't lose your secret key material through the use of `Recovery Key`s, `ciphey` does not have any built-in backup system for the encrypted `ciphey` database. Of course, you can still back up `ciphey` like any other directory (which is a benefit of it living as just a plain directory), but it may be useful to consider ways that `ciphey` can better integrate or accomodate backups.