I've finalized the encryption architecture, here's an overview in as laymen terms as possible.
The encryption scheme is based on the Double-ratchet/3x Diffie-Hellman protocol. With a few distinct changes (see below). This scheme is entirely custom made for the extension as there is no current implementation in JS.
The term “Double Ratchet” comes from how the protocol makes sure each message gets a new key: their Diffie-Hellman keys are “ratcheted” with each new message exchange. So are the send/receive
keys (the symmetric ratchet).
OVERVIEW
IdentityKeys
Each peer in the protocol has an IdentityKey
, these are secp256r1 keys. These keys are used to authenticate both PreKeys
and ExchangeKeys
. IdentityKeys
are used similarly to the public key in an X.509 certificate.
ExchangeKeys
ExchangeKeys are unique to my implementation, they are used to derive PreKeys
. The ExchangeKey
is signed by a peers IdentityKey
.
PreKeys
A PreKey is a secp256r1 public key with an associated unique id. These PreKeys
are signed by the IdentityKey
.
On first use, clients generate a single signed PreKey, as well as a large list of unsigned PreKeys, and transmit all of them to a server. Once a user "exhausts" their prekeys, new conversations with that user cannot be started until he/she generates more. (This will be automated in the ext)
Server
The server in the protocol is an untrusted entity, it simply stores preKeys and the encrypted identity (containing the respective private keys) for retrieval when the peer may be offline and unreachable or the identities owner changes devices/clears their cache.
Sessions
The Double Ratchet protocol is session-oriented. Peers establish a session
with each other, this is then used for all subsequent exchanges. These sessions can remain open and be re-used since each message is encrypted with a new and unique cryptographic key. This is not like a websocket session where there is an active open connection socket, but more like a chain that is asynchronously maintained.
DIFFERENCES
I am using secp256r1 with ECDSA and ECDH instead of ed25519 and x25519 because browsers do not have support for these algorithms in their WebCrypto implementations at this time. The use of these browser supported algorithms has several benefits, including:
- Native cryptographic implementations that should be more resilient to subtle implementation issues such as side channels,
- Ability to utilize non-exportable keys for both identity and authentication keys when used in the browser,
- The use of WebCrypto should also provide both increased performance and better battery life,
- Reduced bandwidth requirements because the crypto implementation is available nativly,
- Keeping your identity and exchange keys on easily availble smart cards like the YubiKey Neo which supports secp256r1.
The decision to use secp256r1 also meant i needed to extend the protocol to support separate keys for signing and encryption. ed25519 is based on EC-Schnorr which is believed to not leak details about the key, the NIST EC curves do not have this property, hence the change. The change includes the newly introduced encryption key being signed by the corresponding identity key.
Due to patent concerns I also utilized uncompressed keys in the wire protocol, these uncompressed keys are larger but I believe them to be unencumbered.
Unlike the original double ratchet protocol, my protocol uses Protobufs instead of TLV for packing messages, this simplifies parsing for easy import and export.
PRIVATE KEY ENCRYPTION
As I mentioned in another thread, the identity that is sent to the server containing the private keys is encrypted using PBKDF
on the user's password. This stretches (or compresses if the user has a ridiculously long password) the password into a 256 bit key (salted of course) which can be used for AES-GCM-256. PBKDF
is iterated 128000 times (for reference, last time apple was asked, they run 100000 iterations for iMessage). This means that it would take upwards of a 100,000 years for the world's fastest supercomputer to crack the key and decrypt the identity. (A hacker with an average sized botnet would take about 834,000,000 years to crack it) but it only takes the user about 80ms to decrypt or encrypt it. If the user has not setup their keys, or are on a new device, the extension will ask for the user's password a second time, preventing admins using impersonate from seeing the user's messages.
Finally (this is new as of today) the key and salt are stored in different locations in the buffer, this is called buffer address space layout randomization (BASLR, similar to ASLR). This will make it impossible for 99% of forum admins to decrypt the identity key, even with the user's password, it will make it a massive pain in the a** for the other 1%. This portion of the extension is coded in C and compiled into webassembly and thus is completely read only (cannot be easily modified without the source code).
When the key is on the user's computer, it is stored decrypted in the indexeddb which is sandboxed on each website protecting it from XSS.
The extension's JS source code will not be public at any point in the future. If you would like to conduct a security audit you may contact me for a copy of the source code (excluding BASLR).