This document contains the cryptographic primitives used by the SealVault application, their parameters if any, and the rationale for their choice.
We need a memory-hard password-based key derivation function to produce a root backup key from the backup password. We need an additional cheap to compute key-derivation function to derive context-specific keys from the root backup key.
We need the KDFs to have cross-platform Rust implementations. We'd prefer the KDFs to have audited Rust implementations, but there aren't any. Compliance to a particular standard is not a goal in the choice of KDF constructs.
Our options for a memory-hard password-based key derivation function are:
When picking Argon2id parameters, we must be careful not to trigger iOS memory safeguards2 and not to drain the user's battery or heat the device noticeably. Therefore, we choose a memory size of 64MiB, iteration count of 10 and a parallelism count of 1.3 This will produce hashes in 500-600ms on modern iPhones.
We choose the official BLAKE3 Rust implementation to produce additional context specific keys from the root backup key for the following reasons:
- It's a secure KDF that supports binding keys to contexts, and it has an official Rust implementation with an ergonomic interface that is compatible with RustCrypto traits.
- We already use the BLAKE3 hash function for deterministic ids.
The alternative to BLAKE3 would be HKDF, but it has an awkward interface for applications that already have a cryptographically secure pseudorandom key (and thus don't need its extract phase), and it can cause subtle problems in applications that use standalone HMAC for other purposes (which we will possibly do in the future).
We need an authenticated encryption with associated data (AEAD) construct to encrypt cloud backups on the device and authenticate backup metadata. We need an authenticated encryption construct to encrypt secret keys of asymmetric keys in device storage. For simplicity, we'll use an AEAD for both cases.
We need the AEAD to have an audited, cross-platform Rust implementation. We choose a 256-bit key size to provide 128-bit post-quantum security level. Compliance to a particular standard is not a goal in the choice of AEAD constructs.
The RustCrypto AEADs library offers two fully audited and one partially audited AEAD construct:
We choose XChaCha20Poly1305 for our AEAD construct for the following reasons:
- It's recommended by the IRTF1 and it's fully audited.
- It supports 192-bit nonces which means we can ensure nonce uniqueness without coordination by picking nonces at random.
- Its simple design makes it easier to use and audit than the AES constructs.
ECDSA-secp256k1 for Ethereum
We use the RustCrypto secp256k1 crate for Ethereum protocol signatures. This is a constant time pure Rust implementation by reputable authors that is popular in the Rust Ethereum ecosystem, but it has not been audited independently.
The alternative would be the Rust
wrapper around Bitcoin core's C
libsecp256k1 which is the most
secp256k1 implementation. In spite of this we went the
RustCrypto implementation for the following reasons:
- We already have RustCrypto dependencies, we like the project, and we will be using more of their ECC crates in the future as they simplify our implementation with common traits.
- We prefer pure Rust dependencies when possible for safety.
The linked RFC is actually about ChaCha20Poly1305, not XChaCha20Poly1305. The difference is that XChaCha20Poly1305 supports 192-bit nonces which make it possible to avoid nonce reuse simply by picking them at random. ↩
iOS might kill a process after a large memory spike if there is memory pressure on the device. ↩
We keep the parallelism count at 1 to avoid potential cross-platform issues later from threading. ↩