Skip to content

Cryptography

This document contains the cryptographic primitives used by the SealVault application, their parameters if any, and the rationale for their choice.

CSPRNG

Rust's rand::rngs::ThreadRng is used to generate cryptographically secure random bytes. This uses SecRandomCopyBytes as the entropy source on iOS.

KDFs

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.

Argon2id

Our options for a memory-hard password-based key derivation function are:

We choose Argon2id by RustCrypto due to OWASP recommendation.

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.

For the rest of the parameters, we use 128-bit salt length and 256-bit secret and tag length. Salts and secrets are generated with the CSPRNG.

BLAKE3

We choose the official BLAKE3 Rust implementation to produce additional context specific keys from the root backup key for the following reasons:

  1. 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.
  2. 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).

AEAD

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:

  1. It's recommended by the IRTF1 and it's fully audited.
  2. It supports 192-bit nonces which means we can ensure nonce uniqueness without coordination by picking nonces at random.
  3. Its simple design makes it easier to use and audit than the AES constructs.

Nonces are generated with the CSPRNG. Encryption keys are either generated with the CSPRNG or derived with the KDFs. See the data section to see how encryption keys are stored.

Elliptic-Curve Cryptography

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 battle tested secp256k1 implementation. In spite of this we went the RustCrypto implementation for the following reasons:

  1. 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.
  2. We prefer pure Rust dependencies when possible for safety.

  1. 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. 

  2. iOS might kill a process after a large memory spike if there is memory pressure on the device. 

  3. We keep the parallelism count at 1 to avoid potential cross-platform issues later from threading.