doubleratchet

package module
v3.0.0+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2019 License: MIT Imports: 13 Imported by: 24

README

doubleratchet

Go Report Card Build Status Coverage Status GoDoc

The Double Ratchet Algorithm is used by two parties to exchange encrypted messages based on a shared secret key. Typically the parties will use some key agreement protocol (such as X3DH) to agree on the shared secret key. Following this, the parties will use the Double Ratchet to send and receive encrypted messages.

The parties derive new keys for every Double Ratchet message so that earlier keys cannot be calculated from later ones. The parties also send Diffie-Hellman public values attached to their messages. The results of Diffie-Hellman calculations are mixed into the derived keys so that later keys cannot be calculated from earlier ones. These properties gives some protection to earlier or later encrypted messages in case of a compromise of a party's keys.

Project status

The library is in beta version and ready for integration into production projects with care. Let me know if you face any problems or have any questions or suggestions.

Implementation notes

The Double Ratchet logic
  1. No more than 1000 messages can be skipped in a single chain.
  2. Skipped messages from a single ratchet step are deleted after 100 ratchet steps.
  3. Both parties' sending and receiving chains are initialized with the shared key so that both of them could message each other from the very beginning.
Cryptographic primitives
  1. GENERATE_DH(): Curve25519
  2. KDF_RK(rk, dh_out): HKDF with SHA-256
  3. KDF_CK(ck): HMAC with SHA-256 and constant inputs
  4. ENCRYPT(mk, pt, associated_data): AES-256-CTR with HMAC-SHA-256 and IV derived alongside an encryption key

Installation

go get github.com/status-im/doubleratchet

then cd into the project directory and install dependencies:

glide up

If glide is not installed, install it.

Usage

Basic usage example
package main

import (
	"fmt"
	"log"

	"github.com/status-im/doubleratchet"
)

func main() {
	// The shared key both parties have already agreed upon before the communication.
	sk := [32]byte{
		0xeb, 0x8, 0x10, 0x7c, 0x33, 0x54, 0x0, 0x20,
		0xe9, 0x4f, 0x6c, 0x84, 0xe4, 0x39, 0x50, 0x5a,
		0x2f, 0x60, 0xbe, 0x81, 0xa, 0x78, 0x8b, 0xeb,
		0x1e, 0x2c, 0x9, 0x8d, 0x4b, 0x4d, 0xc1, 0x40,
	}

	// Diffie-Hellman key pair generated by one of the parties during key exchange or
	// by any other means. The public key MUST be sent to another party for initialization
	// before the communication begins.
	keyPair, err := doubleratchet.DefaultCrypto{}.GenerateDH()
	if err != nil {
		log.Fatal(err)
	}

	// Bob MUST be created with the shared secret and a DH key pair.
	bob, err := doubleratchet.New([]byte("bob-session-id"), sk, keyPair, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Alice MUST be created with the shared secret and Bob's public key.
	alice, err := doubleratchet.NewWithRemoteKey([]byte("alice-session-id"), sk, keyPair.PublicKey(), nil)
	if err != nil {
		log.Fatal(err)
	}

	// Alice can now encrypt messages under the Double Ratchet session.
	m, err := alice.RatchetEncrypt([]byte("Hi Bob!"), nil)

	if err != nil {
		log.Fatal(err)
	}

	// Which Bob can decrypt.
	plaintext, err := bob.RatchetDecrypt(m, nil)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(plaintext))
}
Options

Additional options can be passed to constructors to customize the algorithm behavior:

doubleratchet.New(
    sk, keyPair,
    
    // Your own cryptography supplement implementing doubleratchet.Crypto.
    WithCrypto(c),
    
    // Custom storage for skipped keys implementing doubleratchet.KeysStorage.
    WithKeysStorage(ks),
    
    // The maximum number of skipped keys. Error will be raised in an attempt to store more keys
    // in a single chain while decrypting.
    WithMaxSkip(1200),
    
    // The number of Diffie-Hellman ratchet steps skipped keys will be stored.
    WithMaxKeep(90),
)

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithCrypto

func WithCrypto(c Crypto) option

WithCrypto replaces the default cryptographic supplement with the specified. nolint: golint

func WithKeysStorage

func WithKeysStorage(ks KeysStorage) option

WithKeysStorage replaces the default keys storage with the specified. nolint: golint

func WithMaxKeep

func WithMaxKeep(n int) option

WithMaxKeep specifies how long we keep message keys, counted in number of messages received nolint: golint

func WithMaxMessageKeysPerSession

func WithMaxMessageKeysPerSession(n int) option

WithMaxMessageKeysPerSession specifies the maximum number of message keys per session nolint: golint

func WithMaxSkip

func WithMaxSkip(n int) option

WithMaxSkip specifies the maximum number of skipped message in a single chain. nolint: golint

Types

type Crypto

type Crypto interface {
	// GenerateDH creates a new Diffie-Hellman key pair.
	GenerateDH() (DHPair, error)

	// DH returns the output from the Diffie-Hellman calculation between
	// the private key from the DH key pair dhPair and the DH public key dbPub.
	DH(dhPair DHPair, dhPub Key) (Key, error)

	// Encrypt returns an AEAD encryption of plaintext with message key mk. The associated_data
	// is authenticated but is not included in the ciphertext. The AEAD nonce may be set to a constant.
	Encrypt(mk Key, plaintext, ad []byte) (authCiphertext []byte, err error)

	// Decrypt returns the AEAD decryption of ciphertext with message key mk.
	Decrypt(mk Key, ciphertext, ad []byte) (plaintext []byte, err error)

	KDFer
}

Crypto is a cryptography supplement for the library.

type DHPair

type DHPair interface {
	PrivateKey() Key
	PublicKey() Key
}

DHPair is a general interface for DH pairs representation.

type DefaultCrypto

type DefaultCrypto struct{}

DefaultCrypto is an implementation of Crypto with cryptographic primitives recommended by the Double Ratchet Algorithm specification. However, some details are different, see function comments for details.

func (DefaultCrypto) DH

func (c DefaultCrypto) DH(dhPair DHPair, dhPub Key) (Key, error)

DH returns the output from the Diffie-Hellman calculation between the private key from the DH key pair dhPair and the DH public key dbPub.

func (DefaultCrypto) Decrypt

func (c DefaultCrypto) Decrypt(mk Key, authCiphertext, ad []byte) ([]byte, error)

Decrypt returns the AEAD decryption of ciphertext with message key mk.

func (DefaultCrypto) Encrypt

func (c DefaultCrypto) Encrypt(mk Key, plaintext, ad []byte) ([]byte, error)

Encrypt uses a slightly different approach than in the algorithm specification: it uses AES-256-CTR instead of AES-256-CBC for security, ciphertext length and implementation complexity considerations.

func (DefaultCrypto) GenerateDH

func (c DefaultCrypto) GenerateDH() (DHPair, error)

GenerateDH creates a new Diffie-Hellman key pair.

func (DefaultCrypto) KdfCK

func (c DefaultCrypto) KdfCK(ck Key) (Key, Key)

KdfCK returns a pair (32-byte chain key, 32-byte message key) as the output of applying a KDF keyed by a 32-byte chain key ck to some constant.

func (DefaultCrypto) KdfRK

func (c DefaultCrypto) KdfRK(rk, dhOut Key) (Key, Key, Key)

KdfRK returns a pair (32-byte root key, 32-byte chain key) as the output of applying a KDF keyed by a 32-byte root key rk to a Diffie-Hellman output dhOut.

type InMemoryKey

type InMemoryKey struct {
	// contains filtered or unexported fields
}

type KDFer

type KDFer interface {
	// KdfRK returns a pair (32-byte root key, 32-byte chain key) as the output of applying
	// a KDF keyed by a 32-byte root key rk to a Diffie-Hellman output dhOut.
	KdfRK(rk, dhOut Key) (rootKey, chainKey, newHeaderKey Key)

	// KdfCK returns a pair (32-byte chain key, 32-byte message key) as the output of applying
	// a KDF keyed by a 32-byte chain key ck to some constant.
	KdfCK(ck Key) (chainKey, msgKey Key)
}

KDFer performs key derivation functions for chains.

type Key

type Key []byte

Key is any byte representation of a key.

func (Key) String

func (k Key) String() string

Stringer interface compliance.

type KeysStorage

type KeysStorage interface {
	// Get returns a message key by the given key and message number.
	Get(k Key, msgNum uint) (mk Key, ok bool, err error)

	// Put saves the given mk under the specified key and msgNum.
	Put(sessionID []byte, k Key, msgNum uint, mk Key, keySeqNum uint) error

	// DeleteMk ensures there's no message key under the specified key and msgNum.
	DeleteMk(k Key, msgNum uint) error

	// DeleteOldMKeys deletes old message keys for a session.
	DeleteOldMks(sessionID []byte, deleteUntilSeqKey uint) error

	// TruncateMks truncates the number of keys to maxKeys.
	TruncateMks(sessionID []byte, maxKeys int) error

	// Count returns number of message keys stored under the specified key.
	Count(k Key) (uint, error)

	// All returns all the keys
	All() (map[string]map[uint]Key, error)
}

KeysStorage is an interface of an abstract in-memory or persistent keys storage.

type KeysStorageInMemory

type KeysStorageInMemory struct {
	// contains filtered or unexported fields
}

KeysStorageInMemory is an in-memory message keys storage.

func (*KeysStorageInMemory) All

func (s *KeysStorageInMemory) All() (map[string]map[uint]Key, error)

All returns all the keys

func (*KeysStorageInMemory) Count

func (s *KeysStorageInMemory) Count(pubKey Key) (uint, error)

Count returns number of message keys stored under the specified key.

func (*KeysStorageInMemory) DeleteMk

func (s *KeysStorageInMemory) DeleteMk(pubKey Key, msgNum uint) error

DeleteMk ensures there's no message key under the specified key and msgNum.

func (*KeysStorageInMemory) DeleteOldMks

func (s *KeysStorageInMemory) DeleteOldMks(sessionID []byte, deleteUntilSeqKey uint) error

DeleteOldMKeys deletes old message keys for a session.

func (*KeysStorageInMemory) Get

func (s *KeysStorageInMemory) Get(pubKey Key, msgNum uint) (Key, bool, error)

Get returns a message key by the given key and message number.

func (*KeysStorageInMemory) Put

func (s *KeysStorageInMemory) Put(sessionID []byte, pubKey Key, msgNum uint, mk Key, seqNum uint) error

Put saves the given mk under the specified key and msgNum.

func (*KeysStorageInMemory) TruncateMks

func (s *KeysStorageInMemory) TruncateMks(sessionID []byte, maxKeys int) error

TruncateMks truncates the number of keys to maxKeys.

type Message

type Message struct {
	Header     MessageHeader `json:"header"`
	Ciphertext []byte        `json:"ciphertext"`
}

Message is a single message exchanged by the parties.

type MessageEncHeader

type MessageEncHeader []byte

MessageEncHeader is a binary-encoded representation of a message header.

func (MessageEncHeader) Decode

func (mh MessageEncHeader) Decode() (MessageHeader, error)

Decode message header out of the binary-encoded representation.

type MessageHE

type MessageHE struct {
	Header     []byte `json:"header"`
	Ciphertext []byte `json:"ciphertext"`
}

MessageHE contains ciphertext and an encrypted header.

type MessageHeader

type MessageHeader struct {
	// DHr is the sender's current ratchet public key.
	DH Key `json:"dh"`

	// N is the number of the message in the sending chain.
	N uint32 `json:"n"`

	// PN is the length of the previous sending chain.
	PN uint32 `json:"pn"`
}

MessageHeader that is prepended to every message.

func (MessageHeader) Encode

func (mh MessageHeader) Encode() MessageEncHeader

Encode the header in the binary format.

type Session

type Session interface {
	// RatchetEncrypt performs a symmetric-key ratchet step, then AEAD-encrypts the message with
	// the resulting message key.
	RatchetEncrypt(plaintext, associatedData []byte) (Message, error)

	// RatchetDecrypt is called to AEAD-decrypt messages.
	RatchetDecrypt(m Message, associatedData []byte) ([]byte, error)

	//DeleteMk remove a message key from the database
	DeleteMk(Key, uint32) error
}

Session of the party involved in the Double Ratchet Algorithm.

func Load

func Load(id []byte, store SessionStorage, opts ...option) (Session, error)

Load a session from a SessionStorage implementation and apply options.

func New

func New(id []byte, sharedKey Key, keyPair DHPair, storage SessionStorage, opts ...option) (Session, error)

New creates session with the shared key.

func NewWithRemoteKey

func NewWithRemoteKey(id []byte, sharedKey, remoteKey Key, storage SessionStorage, opts ...option) (Session, error)

NewWithRemoteKey creates session with the shared key and public key of the other party.

type SessionStorage

type SessionStorage interface {
	// Save state keyed by id
	Save(id []byte, state *State) error

	// Load state by id
	Load(id []byte) (*State, error)
}

type State

type State struct {
	Crypto Crypto

	// DH Ratchet public key (the remote key).
	DHr Key

	// DH Ratchet key pair (the self ratchet key).
	DHs DHPair

	// Symmetric ratchet root chain.
	RootCh kdfRootChain

	// Symmetric ratchet sending and receiving chains.
	SendCh, RecvCh kdfChain

	// Number of messages in previous sending chain.
	PN uint32

	// Dictionary of skipped-over message keys, indexed by ratchet public key or header key
	// and message number.
	MkSkipped KeysStorage

	// The maximum number of message keys that can be skipped in a single chain.
	// WithMaxSkip should be set high enough to tolerate routine lost or delayed messages,
	// but low enough that a malicious sender can't trigger excessive recipient computation.
	MaxSkip uint

	// Receiving header key and next header key. Only used for header encryption.
	HKr, NHKr Key

	// Sending header key and next header key. Only used for header encryption.
	HKs, NHKs Key

	// How long we keep messages keys, counted in number of messages received,
	// for example if MaxKeep is 5 we only keep the last 5 messages keys, deleting everything n - 5.
	MaxKeep uint

	// Max number of message keys per session, older keys will be deleted in FIFO fashion
	MaxMessageKeysPerSession int

	// The number of the current ratchet step.
	Step uint

	// KeysCount the number of keys generated for decrypting
	KeysCount uint
}

The double ratchet state.

func DefaultState

func DefaultState(sharedKey Key) State

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL