-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathx25519xchacha20poly1305.go
168 lines (143 loc) · 5.45 KB
/
x25519xchacha20poly1305.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
// Cipherman
// For the full copyright and license information, please view the LICENSE.txt file.
package cipherman
import (
"crypto/rand"
"crypto/sha256"
"errors"
"fmt"
"io"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
const (
// X25519HKDFInfoSK represents the derived key info for the shared key.
X25519HKDFInfoSK = "X25519"
)
// NewX25519XChaCha20Poly1305 creates a new X25519XChaCha20Poly1305 instance.
// By design (similar to ephemeral-static Diffie-Hellman) this cipher:
// 1. Generates an ephemeral private key (instead of using the given private key) during encryption.
// 2. Extracts public key from the ciphertext (instead of using the given public key).
//
// Because the encryption always uses the given public key and the decryption always uses the given private key
// it doesn't require both keys to be present at the same time.
func NewX25519XChaCha20Poly1305(privateKey, publicKey []byte) (*X25519XChaCha20Poly1305, error) {
// Init the instance
x25519cc20p1305 := X25519XChaCha20Poly1305{
privateKey: privateKey,
publicKey: publicKey,
}
// Check the keys
if x25519cc20p1305.privateKey == nil && x25519cc20p1305.publicKey == nil {
return nil, errors.New("missing key")
}
if x25519cc20p1305.privateKey != nil {
if l := len(x25519cc20p1305.privateKey); l != curve25519.PointSize {
return nil, fmt.Errorf("invalid private key size (%d)", l)
}
x25519cc20p1305.canDecrypt = true
}
if x25519cc20p1305.publicKey != nil {
if l := len(x25519cc20p1305.publicKey); l != curve25519.PointSize {
return nil, fmt.Errorf("invalid public key size (%d)", l)
}
x25519cc20p1305.canEncrypt = true
}
return &x25519cc20p1305, nil
}
// X25519XChaCha20Poly1305 represents an X25519XChaCha20Poly1305 cipher.
type X25519XChaCha20Poly1305 struct {
privateKey []byte
publicKey []byte
canEncrypt bool
canDecrypt bool
}
// Encrypt encrypts plaintext by the given arguments and returns ciphertext.
func (x25519cc20p1305 *X25519XChaCha20Poly1305) Encrypt(plaintext, additionalData, nonce []byte) ([]byte, error) {
// Check the instance
if !x25519cc20p1305.canEncrypt {
return nil, errors.New("invalid cipher instance")
}
// Generate an ephemeral asymmetric key
ephemeral := make([]byte, curve25519.ScalarSize)
if _, err := rand.Read(ephemeral); err != nil {
return nil, fmt.Errorf("couldn't generate the ephemeral private key: %s", err)
}
ephemeralPubKey, err := curve25519.X25519(ephemeral, curve25519.Basepoint)
if err != nil {
return nil, fmt.Errorf("couldn't generate the ephemeral public key: %s", err)
}
// Generate the shared secret between peers
sharedKey, err := curve25519.X25519(ephemeral, x25519cc20p1305.publicKey)
if err != nil {
return nil, fmt.Errorf("couldn't generate the shared secret: %s", err)
}
// Derive a key from the shared secret for better security
salt := make([]byte, 0, len(ephemeralPubKey)+len(x25519cc20p1305.publicKey))
salt = append(salt, ephemeralPubKey...)
salt = append(salt, x25519cc20p1305.publicKey...)
h := hkdf.New(sha256.New, sharedKey, salt, []byte(X25519HKDFInfoSK))
cipherKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, cipherKey); err != nil {
return nil, fmt.Errorf("couldn't generate the cipher key: %s", err)
}
// Init the underlying cipher instance
c, err := NewXChaCha20Poly1305(cipherKey)
if err != nil {
return nil, err
}
// Encrypt
// chacha20poly1305 cipher generates a random nonce (recommended) when nonce is not set.
ciphertext, err := c.Encrypt(plaintext, additionalData, nonce)
if err != nil {
return nil, err
}
// Add the ephemeral public key to the ciphertext for decryption
ret := make([]byte, 0, len(ephemeralPubKey)+len(ciphertext))
ret = append(ret, ephemeralPubKey...)
ret = append(ret, ciphertext...)
return ret, nil
}
// Decrypt decrypts ciphertext by the given arguments and returns plaintext.
func (x25519cc20p1305 *X25519XChaCha20Poly1305) Decrypt(ciphertext, additionalData, nonce []byte) ([]byte, error) {
// Check the instance
if !x25519cc20p1305.canDecrypt {
return nil, errors.New("invalid cipher instance")
}
// Determine the public key
publicKey, err := curve25519.X25519(x25519cc20p1305.privateKey, curve25519.Basepoint)
if err != nil {
return nil, fmt.Errorf("couldn't determine the public key: %s", err)
}
// Extract the ephemeral public key from the ciphertext
ephemeralPubKey := ciphertext[:curve25519.PointSize]
// Generate the shared secret between peers
sharedKey, err := curve25519.X25519(x25519cc20p1305.privateKey, ephemeralPubKey)
if err != nil {
return nil, fmt.Errorf("couldn't determine the shared secret: %s", err)
}
// Determine the derived key
salt := make([]byte, 0, len(ephemeralPubKey)+len(publicKey))
salt = append(salt, ephemeralPubKey...)
salt = append(salt, publicKey...)
h := hkdf.New(sha256.New, sharedKey, salt, []byte(X25519HKDFInfoSK))
cipherKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, cipherKey); err != nil {
return nil, fmt.Errorf("couldn't generate the cipher key: %s", err)
}
// Remove the ephemeral public key from the ciphertext
ciphertext = ciphertext[curve25519.PointSize:]
// Init the underlying cipher instance
c, err := NewXChaCha20Poly1305(cipherKey)
if err != nil {
return nil, err
}
// Decrypt
// chacha20poly1305 cipher extracts the nonce from the ciphertext
plaintext, err := c.Decrypt(ciphertext, additionalData, nonce)
if err != nil {
return nil, err
}
return plaintext, nil
}