-
Notifications
You must be signed in to change notification settings - Fork 170
/
Copy pathecies.go
129 lines (116 loc) · 3.48 KB
/
ecies.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
// Package ecies implements the Elliptic Curve Integrated Encryption Scheme (ECIES).
package ecies
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"errors"
"hash"
"go.dedis.ch/kyber/v3"
"go.dedis.ch/kyber/v3/util/random"
"golang.org/x/crypto/hkdf"
)
// Encrypt first computes a shared DH key using the given public key, then
// HKDF-derives a symmetric key (and nonce) from that, and finally uses these
// values to encrypt the given message via AES-GCM. If the hash input parameter
// is nil then SHA256 is used as a default. Encrypt returns a byte slice
// containing the ephemeral elliptic curve point of the DH key exchange and the
// ciphertext or an error.
func Encrypt(group kyber.Group, public kyber.Point, message []byte, hash func() hash.Hash) ([]byte, error) {
if hash == nil {
hash = sha256.New
}
// Generate an ephemeral elliptic curve scalar and point
r := group.Scalar().Pick(random.New())
R := group.Point().Mul(r, nil)
// Compute shared DH key
dh := group.Point().Mul(r, public)
// Derive symmetric key and nonce via HKDF (NOTE: Since we use a new
// ephemeral key for every ECIES encryption and thus have a fresh
// HKDF-derived key for AES-GCM, the nonce for AES-GCM can be an arbitrary
// (even static) value. We derive it here simply via HKDF as well.)
l := 32 + 12
buf, err := deriveKey(hash, dh, l)
if err != nil {
return nil, err
}
key := buf[:32]
nonce := buf[32:l]
// Encrypt message using AES-GCM
aes, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
c := aesgcm.Seal(nil, nonce, message, nil)
// Serialize ephemeral elliptic curve point and ciphertext
var ctx bytes.Buffer
_, err = R.MarshalTo(&ctx)
if err != nil {
return nil, err
}
_, err = ctx.Write(c)
if err != nil {
return nil, err
}
return ctx.Bytes(), nil
}
// Decrypt first computes a shared DH key using the received ephemeral elliptic
// curve point (stored in the first part of ctx), then HKDF-derives a symmetric
// key (and nonce) from that, and finally uses these values to decrypt the
// given ciphertext (stored in the second part of ctx) via AES-GCM. If the hash
// input parameter is nil then SHA256 is used as a default. Decrypt returns the
// plaintext message or an error.
func Decrypt(group kyber.Group, private kyber.Scalar, ctx []byte, hash func() hash.Hash) ([]byte, error) {
if hash == nil {
hash = sha256.New
}
// Reconstruct the ephemeral elliptic curve point
R := group.Point()
l := group.PointLen()
if len(ctx) < l {
return nil, errors.New("invalid ecies cipher")
}
if err := R.UnmarshalBinary(ctx[:l]); err != nil {
return nil, err
}
// Compute shared DH key and derive the symmetric key and nonce via HKDF
dh := group.Point().Mul(private, R)
length := 32 + 12
buf, err := deriveKey(hash, dh, length)
if err != nil {
return nil, err
}
key := buf[:32]
nonce := buf[32:length]
// Decrypt message using AES-GCM
aes, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(aes)
if err != nil {
return nil, err
}
return aesgcm.Open(nil, nonce, ctx[l:], nil)
}
func deriveKey(hash func() hash.Hash, dh kyber.Point, l int) ([]byte, error) {
dhb, err := dh.MarshalBinary()
if err != nil {
return nil, err
}
hkdf := hkdf.New(hash, dhb, nil, nil)
key := make([]byte, l)
n, err := hkdf.Read(key)
if err != nil {
return nil, err
}
if n < l {
return nil, errors.New("ecies: hkdf-derived key too short")
}
return key, nil
}