11 KiB
+++ title = "ciphey" weight = 1 [taxonomies] tags = [] +++
Simply put, ciphey
is a password and secret manager that is like
pass
if it used
age
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 and Bitwarden and
combines them with the Unix-like philosophy of pass
.
To be clear, ciphey
is still in development. The remaining work is outlined
below. 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
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. 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:
- Your
Primary Passphrase
(something you know) - 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
:
// 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<String> = 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::<Vec<String>>()
.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:
// 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::<Vec<String>>().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.