SECURITY

Architecture

Phase is built on an end-to-end encryption architecture used to derive encryption keys, provision access, and securely store and transmit data across the platform.

Here we provide a detailed description of all the pieces of this architecture, including the processes used to generate root secrets, derive encryption keys, encrypt data, and cryptographically grant or revoke access to secrets.

Phase uses libsodium for all primitive cryptographic operations including encryption, decryption, signatures, hashing and more.


Generic operations

The following standardized processes are used across the platform to generate random numbers, compute session keys, and perform encryption and decryption operations.

Random number generation

Phase uses the ChaCha20 stream cipher via libsodium's crypto_kdf_keygen utility for cryptographically secure random number generation (CSPRNG). A reliable entropy source such as /dev/urandom in server environments, or Web Crypto's crypto.getRandomValues() in browser environments is used to seed the CSPRNG.

Key exchange

Phase uses elliptic-curve cryptography based on Curve25519. Entities such as Users and Environments are initiated with Curve25519 public/private key-pairs, denoted as (K, k).

To securely encrypt data for transmission between entities A and B, a symmetric session key is computed via the X25519 key exchange protocol. This process allows A to compute a session key Ksession using their private key kA and B's public key kB.

X25519(kA, KB) → Ksession

B can independently compute this session key using their private key kB and A's public key KA.

X25519(kB, KA) → Ksession

This allows data that is encrypted with the session key to be securely stored or transmitted over the network, as only the intended recipient B can re-compute the session key and decrypt the ciphertext.

In most cases Phase uses an ephemeral Curve25519 keypair (Keph, keph) for one side of this operation, along with the recipient's public key Krecipient to derive the symmetric session key Ksession.

Encryption

All encryption operations are done asymmetrically. Each operation uses an ephemeral keypair (Keph, keph) in combination with the public key Krecipient of the 'recipient' or 'target' to derive a symmetric session key Ksession.

The plaintext data D is then encrypted with Ksession using the XChaCha-Poly1305 algorithm with a random IV to compute the ciphertext C. The ephemeral public key Keph is prepended to the ciphertext, and the corresponding private key is discarded.

The output of the encryption operation is a string that can be safely stored or transmitted over the network, and contains Keph || C || IV

phase encryption

Decryption

All decryption operations are performed asymmetrically. Each operation uses the recipient's private key krecipient in combination with the ephemeral public key Keph prepended to the ciphertext to recreate the session key Ksession. The ciphertext C is decrypted using the appended IV.

phase decryption


Key Derivation

There are two primary classes of keys that must be derived to facilitate Phase's end-to-end encryption architecture:

  • User keys: used for granular cryptographic access control
  • Environment keys: used to encrypt and decrypt Secrets

User Keys

Each Phase user account has a unique set of keys associated with it. Primarily, this consists of an Ed25519 signing key pair (Kusersign, kusersign), derived deterministically from a high-entropy seed. This signing key pair is also used to derive an X25519 key-exchange keypair (Kuserkx, kuserkx) for asymmetric encryption operations.

User keys are derived in two stages:

  1. An Account Seed is derived deterministically from a combination of a high-entropy seed and the user's organization id.
  2. The Account Seed is used to derive a set of keys used for encryption and decryption operations.

Account Seed Derivation

When setting up a new Phase account, a random high-entropy 32-byte seed is encoded as a 24-word mnemonic using the BIP39 wordlist. This mnemonic serves as the account recovery phrase. The mnemonic is hashed with Argon2ID with the following parameters:

  • Input: Mnemonic with spaces replaced by - hyphens.
  • Salt: 16-byte Blake2b hash of organization UUID.
  • Memory: 1GB
  • Iterations: 4

The output of this hash is used as the Account Seed.

Account Key Derivation from Seed

The Account Seed is then used to derive the following keys:

  1. PublicKey/PrivateKey Ed25519 keypair:

A 32 byte Signing Key Seed is derived using libsodium crypto_kdf_derive_from_key (implemented using Blake2B) with constants:

  • KEY_ID = 1
  • CONTEXT = __sign__

The Ed25519 signing key pair (Kusersign, kusersign) is then derived from the Signing Key Seed using libsodium crypto_sign_seed_keypair(SigningKeySeed).

  1. Curve25519 key-exchange Keys (Kuserkx, kuserkx) are derived from the Ed25519 PublicKey/PrivateKey pair using libsodium utils crypto_sign_ed25519_pk_to_curve25519 and crypto_sign_ed25519_sk_to_curve25519 respectively.

  2. A Symmetric Key is also derived from the Account Seed, although this is currently not used to encrypt or decrypt any data. This symmetric key is computed used libsodium crypto_kdf_derive_from_key with constants:

    • KEY_ID = 0
    • CONTEXT = _secret_

user key derivation

Device Key and Sudo Password

The account keys derived above are encrypted using a Device Key for secure storage on the device as well as the backend. This allows the user keyring to be decrypted by entering a single password, and held in memory for the duration of the session.

The Device Key is computed as a Argon2ID hash with the following parameters:

  • Input: User "sudo" password.
  • Salt: Blake2b hash of user email (16 bytes).
  • Memory: 64MB
  • Iterations: 2

The following data is encrypted using the Device Key and stored on the backend:

  • Encrypted Keyring: account keys comprising Signing Key Pair and Symmetric Key.
  • Encrypted Mnemonic: mnemonic recovery phrase.

sudo password setup

When a user begins a session on the Phase Console, the encrypted keyring is retrieved from the server. The sudo password is used to compute the Device Key, which decrypts the keyring and makes the user's keys available in memory for the duration of the session.

sudo password login

Environment Keys

Each Environment in a Phase App has a unique encryption key pair to allow secrets to be asymmetrically encrypted and decrypted.

A 32-byte high entropy seed Seedenv is used to derive an X25519 keypair (Kenv, kenv).

A 32-byte random salt Saltenv is used to compute hashes of secret keys client-side, which in turn are used for server-side key lookups.

environment keys

Key digests

Secret keys are hashed client-side using Blake2B with the corresponding Environments' salt. These hashes are stored along side encrypted keys on the backend as a key_digest field. To perform a server-side key lookups, the client computes the hash and sends it to the backend as a query. The backend attempts to match the queried hash against the key_digest field across all secrets in the environment, and returns the matched secret if one is found.

Blake2B(input=secret.key, salt=Saltenv, length=32bytes)

Environment Access Provisioning

Users are granted access to environments cryptographically, by encrypting the environments' seed Seedenv and salt Saltenv with the user account's public key Kuserkx to compute a wrapped seed and wrapped salt respectively. These are stored on the backend as an EnvironmentKey object.

environment key wrapping for user

These "wrapped" keys are fetched from the server and decrypted client-side with the user's private key kuserkx to retrieve the environment seed and salt. The seed can then be used to re-derive (Kenv, kenv) for secret encryption and decryption.

environment key unwrapping for user


Secret Encryption / Decryption

Secret properties key, value, and comment are encrypted asymmetrically with XChaChaPoly1305 using the environment's public key Kenv. Each field encryption operation uses an ephemeral keypair and a random IV, as described in the generic encryption description.

secret encryption

Each field is decrypted using the environment's private key kenv, the ephemeral public key and the IV that are appended to the ciphertext string as described in the generic decryption description.

secret decryption

Putting together the understanding of environment keys, access provisioning and secret encryption gives us a complete picture of how a user may encrypt and decrypt a single secret:

e2e secret encryption
User encrypting a single secret

e2e secret decryption
User decrypting a single secret


Tokens

Tokens allow programmatic access to resources such as environments and secrets via the Phase CLI, API, SDKs or Kubernetes Operator. Tokens facilitate authentication to these resources, and some additionally contain the keys required to encrypt and decrypt secrets.

User Tokens (PATs)

User tokens are are used to programmatically access secrets stored in Phase as a User. The scope of the user token depends on the Apps and Environments the user has access to, and the role associated with their account.

User tokens are generated be securely splitting the user's private key and distributing these shares between the server and client token string:

  • A random 32-byte Wrapping key Kwrapping is generated.
  • The User's private key kuser is split into 2 shares, Kuser and s1.
  • The token string is assembled using s0, Kwrapping and the user's public key Kuser.
  • s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend.
  • The token string has the format: Kuser||s0||Kwrapping

user token construction

When the user token is invoked, the private key is reassembled by fetching the wrapped share from the server, decrypting it client-side and combining it with the share embedded in the client token:

  • Fetch s1wrapped from the server
  • Decrypt s1wrapped with Kwrapping to retrieve s1
  • Reconstruct the User's private key kuser from s0 and s1

user token usage

Once reconstructed, the user's private key can be used to unwrap the Wrapped Seed from an EnvironmentToken object, derive a given environment's keypair and decrypt secrets. See environment access provisioning for details.

Service Tokens

Service tokens are are used to programmatically access secrets stored in Phase. They can be scoped to one or more environments of an application. Service tokens work analogously to User tokens, but instead of using a pre-existing asymmetric keypair, a new random key pair is generated.

  • A random public/private Curve25519 keypair (Ktoken, ktoken) is generated
  • Additionally, a 32-byte Token ID and a random Wrapping Key Kwrapping are generated
  • The token private key ktoken is split into 2 shares: s0 and s1
  • The token string is assembled using s0, Kwrapping, the token's public key Ktoken, and the Token ID
  • s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend
  • The token string has the format: Ktoken||s0||Kwrapping||TokenId

service token construction

For each environment that the service token is scoped to, the respective Environment Salt and Environment Seed are encrypted asymmetrically (wrapped) with the service token publicKey Ktoken and stored on the backend.

When a service token is invoked, the token private key is re-assembled client-side:

  • Fetch s1wrapped from the server
  • Decrypt s1wrapped with Kwrapping to retrieve s1
  • Reconstruct the token private key ktoken from s0 and s1.

service token usage

The private key is used to unwrap the Environment Seed, which in turn can be used to derive the Environment keys and decrypt secrets. See environment access provisioning for details.

Lockbox

Lockbox allows secrets to be shared via a single link with a zero-trust encryption scheme.

To create a new lockbox link with some plaintext data P:

  • A random 32-byte Box Seed is generated and used to derive a Curve25519 key pair (Kbox, kbox)
  • P is encrypted with the public key Kbox to compute ciphertext C, and the corresponding private key is discarded
  • The ciphertext C is stored on the backend along with metadata such as the number of allowed views, link expiry etc and given a unique Box ID
  • The Box ID and Box Seed are used to generate the link: /lockbox/${boxId}#${boxSeed}. Note that the Box Seed is added as a URL fragment prepended with #, and thus only is only parsed by the browser, and never sent to the server.

create lockbox

To retrieve and decrypt a secret shared via Lockbox:

  • The Box ID is used to retrieve the ciphertext C from the server
  • The Box Seed in the link URL fragment is used to compute the keypair (Kbox, kbox) client-side
  • Ciphertext C is decrypted with the box private key kbox to get the original secret P

open lockbox