forked from status-im/doubleratchet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdefault_crypto.go
196 lines (158 loc) · 5.39 KB
/
default_crypto.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package doubleratchet
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"fmt"
"io"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
// 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.
type DefaultCrypto struct{}
// GenerateDH creates a new Diffie-Hellman key pair.
func (c DefaultCrypto) GenerateDH() (DHPair, error) {
var privKey [32]byte
if _, err := io.ReadFull(rand.Reader, privKey[:]); err != nil {
return dhPair{}, fmt.Errorf("couldn't generate privKey: %s", err)
}
privKey[0] &= 248
privKey[31] &= 127
privKey[31] |= 64
var pubKey [32]byte
curve25519.ScalarBaseMult(&pubKey, &privKey)
return dhPair{
privateKey: privKey[:],
publicKey: pubKey[:],
}, nil
}
// 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 (c DefaultCrypto) DH(dhPair DHPair, dhPub Key) (Key, error) {
var (
dhOut [32]byte
privKey [32]byte
pubKey [32]byte
)
if len(dhPair.PrivateKey()) != 32 {
return nil, fmt.Errorf("Invalid private key length: %d", len(dhPair.PrivateKey()))
}
if len(dhPub) != 32 {
return nil, fmt.Errorf("Invalid private key length: %d", len(dhPair.PrivateKey()))
}
copy(privKey[:], dhPair.PrivateKey()[:32])
copy(pubKey[:], dhPub[:32])
curve25519.ScalarMult(&dhOut, &privKey, &pubKey)
return dhOut[:], nil
}
// 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.
func (c DefaultCrypto) KdfRK(rk, dhOut Key) (Key, Key, Key) {
var (
r = hkdf.New(sha256.New, dhOut, rk, []byte("rsZUpEuXUqqwXBvSy3EcievAh4cMj6QL"))
buf = make([]byte, 96)
)
// The only error here is an entropy limit which won't be reached for such a short buffer.
_, _ = io.ReadFull(r, buf)
rootKey := make(Key, 32)
headerKey := make(Key, 32)
chainKey := make(Key, 32)
copy(rootKey[:], buf[:32])
copy(chainKey[:], buf[32:64])
copy(headerKey[:], buf[64:96])
return rootKey, chainKey, headerKey
}
// 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 (c DefaultCrypto) KdfCK(ck Key) (Key, Key) {
const (
ckInput = 15
mkInput = 16
)
chainKey := make(Key, 32)
msgKey := make(Key, 32)
h := hmac.New(sha256.New, ck[:])
_, _ = h.Write([]byte{ckInput})
copy(chainKey[:], h.Sum(nil))
h.Reset()
_, _ = h.Write([]byte{mkInput})
copy(msgKey[:], h.Sum(nil))
return chainKey, msgKey
}
// 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 (c DefaultCrypto) Encrypt(mk Key, plaintext, ad []byte) ([]byte, error) {
encKey, authKey, iv := c.deriveEncKeys(mk)
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
copy(ciphertext, iv[:])
var (
block, _ = aes.NewCipher(encKey[:]) // No error will occur here as encKey is guaranteed to be 32 bytes.
stream = cipher.NewCTR(block, iv[:])
)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return append(ciphertext, c.computeSignature(authKey[:], ciphertext, ad)...), nil
}
// Decrypt returns the AEAD decryption of ciphertext with message key mk.
func (c DefaultCrypto) Decrypt(mk Key, authCiphertext, ad []byte) ([]byte, error) {
var (
l = len(authCiphertext)
ciphertext = authCiphertext[:l-sha256.Size]
signature = authCiphertext[l-sha256.Size:]
)
// Check the signature.
encKey, authKey, _ := c.deriveEncKeys(mk)
if s := c.computeSignature(authKey[:], ciphertext, ad); !bytes.Equal(s, signature) {
return nil, fmt.Errorf("invalid signature")
}
// Decrypt.
var (
block, _ = aes.NewCipher(encKey[:]) // No error will occur here as encKey is guaranteed to be 32 bytes.
stream = cipher.NewCTR(block, ciphertext[:aes.BlockSize])
plaintext = make([]byte, len(ciphertext[aes.BlockSize:]))
)
stream.XORKeyStream(plaintext, ciphertext[aes.BlockSize:])
return plaintext, nil
}
// deriveEncKeys derive keys for message encryption and decryption. Returns (encKey, authKey, iv, err).
func (c DefaultCrypto) deriveEncKeys(mk Key) (Key, Key, [16]byte) {
// First, derive encryption and authentication key out of mk.
salt := make([]byte, 32)
var (
r = hkdf.New(sha256.New, mk[:], salt, []byte("pcwSByyx2CRdryCffXJwy7xgVZWtW5Sh"))
buf = make([]byte, 80)
)
// The only error here is an entropy limit which won't be reached for such a short buffer.
_, _ = io.ReadFull(r, buf)
var encKey Key = make(Key, 32)
var authKey Key = make(Key, 32)
var iv [16]byte
copy(encKey[:], buf[0:32])
copy(authKey[:], buf[32:64])
copy(iv[:], buf[64:80])
return encKey, authKey, iv
}
func (c DefaultCrypto) computeSignature(authKey, ciphertext, associatedData []byte) []byte {
h := hmac.New(sha256.New, authKey)
_, _ = h.Write(associatedData)
_, _ = h.Write(ciphertext)
return h.Sum(nil)
}
type dhPair struct {
privateKey Key
publicKey Key
}
func (p dhPair) PrivateKey() Key {
return p.privateKey
}
func (p dhPair) PublicKey() Key {
return p.publicKey
}
func (p dhPair) String() string {
return fmt.Sprintf("{privateKey: %s publicKey: %s}", p.privateKey, p.publicKey)
}