doubleratchet

package module
v0.0.0-...-9d7f37c Latest Latest
Warning

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

Go to latest
Published: Jun 20, 2017 License: MIT Imports: 12 Imported by: 0

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.
  4. Both plain and encrypted header versions are implemented.
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/tiabc/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/tiabc/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(sk, keyPair)
	if err != nil {
		log.Fatal(err)
	}

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

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

	// 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),
)
Header encryption

If you don't want anybody to see message ordering and your ratchet keys, you can utilize header encryption. It makes your communication even more secure in a sense that an eavesdropper can only see ciphertexts and nothing else. However, it adds more complexity to the implementation, namely:

  1. Parties should agree on 2 more secret keys for encrypting headers before the double ratchet session.
  2. When a recipient receives a message she must first associate the message with its relevant Double Ratchet session (assuming she has different sessions with different parties). How this is done is outside of the scope of this library, although the Pond protocol offers some ideas as stated in the Double Ratchet specification.
  3. Header encryption makes messages 48 bytes longer. For example, if you're sending message how are you? in a version without header encryption, it will be encrypted into iv + len(pt) + signature = 16 + 12 + 32 = 60 bytes plus a header rk + pn + n = 32 + 4 + 4 = 40 bytes with 100 bytes in total. In case of the header encryption modification the header will also be encrypted which will add 48 more bytes with the total of 148 bytes. Note that the longer your message, the more resulting length it takes.
  4. It does a bit more computations especially for skipped messages and will work more slowly.
Example

In order to create a header-encrypted session, parties should agree upon 3 different shared keys and Alice should know Bob's public key:

package main

import (
	"fmt"
	"log"

	"github.com/tiabc/doubleratchet"
)

func main() {
	// Shared keys both parties have already agreed upon before the communication.
	var (
		// The key for message keys derivation.
		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,
		}

		// Header encryption keys.
		sharedHka = [32]byte{
			0xbd, 0x29, 0x18, 0xcb, 0x18, 0x6c, 0x26, 0x32,
			0xd5, 0x82, 0x41, 0x2d, 0x11, 0xa4, 0x55, 0x87,
			0x1e, 0x5b, 0xa3, 0xb5, 0x5a, 0x6d, 0xe1, 0x97,
			0xde, 0xf7, 0x5e, 0xc3, 0xf2, 0xec, 0x1d, 0xd,
		}
		sharedNhkb = [32]byte{
			0x32, 0x89, 0x3a, 0xed, 0x4b, 0xf0, 0xbf, 0xc1,
			0xa5, 0xa9, 0x53, 0x73, 0x5b, 0xf9, 0x76, 0xce,
			0x70, 0x8e, 0xe1, 0xa, 0xed, 0x98, 0x1d, 0xe3,
			0xb4, 0xe9, 0xa9, 0x88, 0x54, 0x94, 0xaf, 0x23,
		}
	)

	keyPair, err := doubleratchet.DefaultCrypto{}.GenerateDH()
	if err != nil {
		log.Fatal(err)
	}

	// Bob MUST be created with the shared secret, shared header keys and a DH key pair.
	bob, err := doubleratchet.NewHE(sk, sharedHka, sharedNhkb, keyPair)
	if err != nil {
		log.Fatal(err)
	}

	// Alic MUST be created with the shared secret, shared header keys and Bob's public key.
	alice, err := doubleratchet.NewHEWithRemoteKey(sk, sharedHka, sharedNhkb, keyPair.PublicKey())
	if err != nil {
		log.Fatal(err)
	}

	// Encryption and decryption is done the same way as in the basic version.
	m := alice.RatchetEncrypt([]byte("Hi Bob!"), nil)

	plaintext, err := bob.RatchetDecrypt(m, nil)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(plaintext))
}

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.

func WithKeysStorage

func WithKeysStorage(ks KeysStorage) option

WithKeysStorage replaces the default keys storage with the specified.

func WithMaxKeep

func WithMaxKeep(n int) option

WithMaxKeep specifies the maximum number of ratchet steps before a message is deleted.

func WithMaxSkip

func WithMaxSkip(n int) option

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

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

	// 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)

	// 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

See the Crypto interface.

func (DefaultCrypto) Decrypt

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

See the Crypto interface.

func (DefaultCrypto) Encrypt

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

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)

See the Crypto interface.

func (DefaultCrypto) KdfCK

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

See the Crypto interface.

func (DefaultCrypto) KdfRK

func (c DefaultCrypto) KdfRK(rk, dhOut Key) (rootKey, chainKey, headerKey Key)

See the Crypto interface.

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 [32]byte

Keys is any 32-byte key. It's created for the possibility of pretty hex output.

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)

	// Put saves the given mk under the specified key and msgNum.
	Put(k Key, msgNum uint, mk Key)

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

	// DeletePk ensures there's no message keys under the specified key.
	DeletePk(k Key)

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

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

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[Key]map[uint]Key

See KeysStorage.

func (*KeysStorageInMemory) Count

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

See KeysStorage.

func (*KeysStorageInMemory) DeleteMk

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

See KeysStorage.

func (*KeysStorageInMemory) DeletePk

func (s *KeysStorageInMemory) DeletePk(pubKey Key)

See KeysStorage.

func (*KeysStorageInMemory) Get

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

See KeysStorage.

func (*KeysStorageInMemory) Put

func (s *KeysStorageInMemory) Put(pubKey Key, msgNum uint, mk Key)

See KeysStorage.

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

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

Session of the party involved in the Double Ratchet Algorithm.

func New

func New(sharedKey Key, keyPair DHPair, opts ...option) (Session, error)

New creates session with the shared key.

func NewWithRemoteKey

func NewWithRemoteKey(sharedKey, remoteKey Key, opts ...option) (Session, error)

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

type SessionHE

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

	// RatchetDecrypt is called to AEAD-decrypt header-encrypted messages.
	RatchetDecrypt(m MessageHE, associatedData []byte) ([]byte, error)
}

SessionHE is the session of the party involved the Double Ratchet Algorithm with encrypted header modification.

func NewHE

func NewHE(sharedKey, sharedHka, sharedNhkb Key, keyPair DHPair, opts ...option) (SessionHE, error)

NewHE creates session with the shared keys.

func NewHEWithRemoteKey

func NewHEWithRemoteKey(sharedKey, sharedHka, sharedNhkb, remoteKey Key, opts ...option) (SessionHE, error)

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

Jump to

Keyboard shortcuts

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