Documentation
¶
Overview ¶
Package dr implements the Double Ratchet scheme.
Overview ¶
What follows is a high-level overview of the Double Ratchet scheme, mostly paraphrased from the whitepaper [signal].
Double Ratchet Algorithm ¶
The Double Ratchet Algorithm is comprised of two "ratchets" over three KDF chains. A ratchet is a construction where each step forward is constructed with a one-way function, making it impossible to recover previous keys (forward secrecy).
KDF Chains ¶
KDF chains are the core construction of the Double Ratchet Algorithm.
A KDF chain is a construction where part of the output of the KDF is used to key the next invocation of the KDF, and the rest is used for some other purpose (like message encryption).
key v ┌─────┐ input > │ KDF │ └──┬──┘ ├─> output key v key v ┌─────┐ input > │ KDF │ └──┬──┘ ├─> output key v key
This construction has some desirable properties, including forward security and resilience against attackers that can manipulate the KDF inputs.
In a Double Ratchet session both parties have three chains:
- root chain
- sending chain
- receiving chain
Each party's sending chain will match the other's receiving chain and vice versa. The root chain is the same for both parties.
Diffie-Hellman Ratchet ¶
Both parties have their own ephemeral ratchet key pair. Each time a message is sent the sender generates a new key pair and attaches the new public key to the message. The sender then uses the shared Diffie-Hellman value as input to the sending chain, advancing it one step. Likewise, when the recipient receives the message (and is informed of the sender's new public key), the recipient uses the shared Diffie-Hellman value as input to the receiving chain, advancing it one step and keeping it in sync with the other party's sending chain.
In other words, when Alice sends Bob a message she creates a new Diffie-Hellman key pair and uses her private key and Bob's public key to compute the shared Diffie-Hellman value. When Bob receives the message, he uses Alice's new public key and his private key to also compute the shared Diffie-Hellman value.
Symmetric-Key Ratchet ¶
As each message is sent and received the sending and receiving chains are advanced. The output of advancing each chain is used as a message key to encrypt each individual message.
Notes ¶
This package does not implement encrypted headers.
References ¶
More information can be found in the following links.
[signal]: https://signal.org/docs/specifications/doubleratchet/doubleratchet.pdf
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("dr: key not found")
ErrNotFound is returned by Store when a message key is not found in the Store.
Functions ¶
Types ¶
type ChainKey ¶
type ChainKey []byte
ChainKey is an ephemeral key used to key the KDF used to generate message keys.
chain key v ┌─────┐ constant > │ kdf │ └──┬──┘ ├─> message key v chain key v ┌─────┐ constant > │ kdf │ └──┬──┘ ├─> message key v chain key
ChainKeys are always 32 bytes.
type Header ¶
type Header struct { // PublicKey is the sender's new public key. PublicKey []byte // PN is the previous chain length. PN int // N is the current message number. N int }
Header is generated alongside each message.
type MessageKey ¶
type MessageKey []byte
MessageKey is an ephemeral key used to encrypt a single message.
MessageKeys are output from the sending and receiving KDF chains.
MessageKeys are always 32 bytes.
type Option ¶
type Option func(*Session)
Option configures a Session.
func WithStore ¶
WithStore configures some backing store for saving state and skipped messages.
Saving session state allows the session to be paused and resumed at a later time.
Messages are skipped and queued when they arrive out of order.
By default, skipped messages are stored in memory and sessions are ephemeral.
type Ratchet ¶
type Ratchet interface { // Generate creates a new Diffie-Hellman pair. // // Generate might use entropy from the provided Reader. Generate(io.Reader) (PrivateKey, error) // Public returns a copy of the public key portion of the key // pair. Public(PrivateKey) PublicKey // DH returns the Diffie-Hellman value computed with the key // pair and public key. DH(PrivateKey, PublicKey) ([]byte, error) // KDFrk applies a KDF keyed by the root key to the // Diffie-Hellman value and returns a (root key, chain key) // pair. KDFrk(RootKey, []byte) (RootKey, ChainKey) // KDFck applies a KDF keyed by the chain key to some // constant value and returns a (root key, chain key) pair. KDFck(ChainKey) (ChainKey, MessageKey) // Seal encrypts and authenticates plaintext, authenticates // additionalData, and appends the ciphertext to dst. // // Because each message key is only used once the nonce can // be handled in one of several ways: // // 1. fixed to a constant // 2. derived from mk alongside an independent AEAD // encryption key // 3. derived as additional output of KDFck // 4. chosen randomly and transmitted // Seal(key MessageKey, plaintext, additionalData []byte) []byte // Open decrypts and authenticates ciphertext, authenticates // additionalData, and appends the plaintext to dst. Open(key MessageKey, ciphertext, additionalData []byte) ([]byte, error) // Header creates a message header from the key pair, // previous chain length, and current message number. // // The header contains the Diffie-Hellman public ratchet key. Header(priv PrivateKey, prevChainLength, messageNum int) Header // Concat encodes a message header and prepends the // additional data. // // Concact should ensure that the additional data and header // can be differentiated. // // See the Concat function for a default implementation. Concat(additionalData []byte, h Header) []byte }
Ratchet implements the Double Ratchet scheme.
Ratchet should be safe for concurrent use by multiple distinct goroutines.
type RootKey ¶
type RootKey []byte
RootKey is a key generated by each step in the root chain.
RootKeys are always 32 bytes.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session encapsulates an asynchronous conversation between two parties.
func NewRecv ¶
NewRecv creates a new Session for receiving communication initiated by some peer.
The shared key SK must be negotiated with the peer ahead of time.
func NewSend ¶
NewSend creates a new Session for initiating communication with some peer.
The shared key SK must be negotiated with the peer ahead of time.
type State ¶
type State struct { // DHs is the sending (self) ratchet key pair. DHs PrivateKey // DHr is the peer's ratchet public key. DHr PublicKey // RK is the current root key. RK RootKey // CKs is the sending chain key. CKs ChainKey // CKr is the receivinb chain key. CKr ChainKey // NS is the sending message number. Ns int // Nr is the receiving message number. Nr int // PN is the number of messages in the previous sending // chain. PN int }
State is the current state of a session.
type Store ¶
type Store interface { // Save saves the state. Save(s *State) error // StoreKey stores a skipped message's key under the (Nr, // PublicKey) tuple. // // StoreKey must return an error if too many messages have // been Skipped. StoreKey(Nr int, pub PublicKey, key MessageKey) error // LoadKey retrieves a message key using the (Nr, PublicKey) // tuple. // // If the message key is not found LoadKey returns // ErrNotFound. LoadKey(Nr int, pub PublicKey) (MessageKey, error) // DeleteKey removes a message key using the (Nr, PublicKey) // tuple. DeleteKey(Nr int, pub PublicKey) error }
Store saves session state.