algs.go raw
1 package hpke
2
3 import (
4 "crypto"
5 "crypto/aes"
6 "crypto/cipher"
7 "crypto/ecdh"
8 _ "crypto/sha256" // Linking sha256.
9 _ "crypto/sha512" // Linking sha512.
10 "fmt"
11 "hash"
12 "io"
13
14 "github.com/cloudflare/circl/dh/x25519"
15 "github.com/cloudflare/circl/dh/x448"
16 "github.com/cloudflare/circl/kem"
17 "github.com/cloudflare/circl/kem/kyber/kyber768"
18 "github.com/cloudflare/circl/kem/xwing"
19 "golang.org/x/crypto/chacha20poly1305"
20 "golang.org/x/crypto/hkdf"
21 )
22
23 type KEM uint16
24
25 //nolint:golint,stylecheck
26 const (
27 // KEM_P256_HKDF_SHA256 is a KEM using P256 curve and HKDF with SHA-256.
28 KEM_P256_HKDF_SHA256 KEM = 0x10
29 // KEM_P384_HKDF_SHA384 is a KEM using P384 curve and HKDF with SHA-384.
30 KEM_P384_HKDF_SHA384 KEM = 0x11
31 // KEM_P521_HKDF_SHA512 is a KEM using P521 curve and HKDF with SHA-512.
32 KEM_P521_HKDF_SHA512 KEM = 0x12
33 // KEM_X25519_HKDF_SHA256 is a KEM using X25519 Diffie-Hellman function
34 // and HKDF with SHA-256.
35 KEM_X25519_HKDF_SHA256 KEM = 0x20
36 // KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and
37 // HKDF with SHA-512.
38 KEM_X448_HKDF_SHA512 KEM = 0x21
39 // KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256)
40 // and Kyber768Draft00
41 KEM_X25519_KYBER768_DRAFT00 KEM = 0x30
42 // KEM_XWING is a hybrid KEM using X25519 and ML-KEM-768.
43 KEM_XWING KEM = 0x647a
44 )
45
46 // IsValid returns true if the KEM identifier is supported by the HPKE package.
47 func (k KEM) IsValid() bool {
48 switch k {
49 case KEM_P256_HKDF_SHA256,
50 KEM_P384_HKDF_SHA384,
51 KEM_P521_HKDF_SHA512,
52 KEM_X25519_HKDF_SHA256,
53 KEM_X448_HKDF_SHA512,
54 KEM_X25519_KYBER768_DRAFT00,
55 KEM_XWING:
56 return true
57 default:
58 return false
59 }
60 }
61
62 // Scheme returns an instance of a KEM that supports authentication. Panics if
63 // the KEM identifier is invalid.
64 func (k KEM) Scheme() kem.Scheme {
65 switch k {
66 case KEM_P256_HKDF_SHA256:
67 return dhkemp256hkdfsha256
68 case KEM_P384_HKDF_SHA384:
69 return dhkemp384hkdfsha384
70 case KEM_P521_HKDF_SHA512:
71 return dhkemp521hkdfsha512
72 case KEM_X25519_HKDF_SHA256:
73 return dhkemx25519hkdfsha256
74 case KEM_X448_HKDF_SHA512:
75 return dhkemx448hkdfsha512
76 case KEM_X25519_KYBER768_DRAFT00:
77 return hybridkemX25519Kyber768
78 case KEM_XWING:
79 return kemXwing
80 default:
81 panic(ErrInvalidKEM)
82 }
83 }
84
85 type KDF uint16
86
87 //nolint:golint,stylecheck
88 const (
89 // KDF_HKDF_SHA256 is a KDF using HKDF with SHA-256.
90 KDF_HKDF_SHA256 KDF = 0x01
91 // KDF_HKDF_SHA384 is a KDF using HKDF with SHA-384.
92 KDF_HKDF_SHA384 KDF = 0x02
93 // KDF_HKDF_SHA512 is a KDF using HKDF with SHA-512.
94 KDF_HKDF_SHA512 KDF = 0x03
95 )
96
97 func (k KDF) IsValid() bool {
98 switch k {
99 case KDF_HKDF_SHA256,
100 KDF_HKDF_SHA384,
101 KDF_HKDF_SHA512:
102 return true
103 default:
104 return false
105 }
106 }
107
108 // ExtractSize returns the size (in bytes) of the pseudorandom key produced
109 // by KDF.Extract.
110 func (k KDF) ExtractSize() int {
111 switch k {
112 case KDF_HKDF_SHA256:
113 return crypto.SHA256.Size()
114 case KDF_HKDF_SHA384:
115 return crypto.SHA384.Size()
116 case KDF_HKDF_SHA512:
117 return crypto.SHA512.Size()
118 default:
119 panic(ErrInvalidKDF)
120 }
121 }
122
123 // Extract derives a pseudorandom key from a high-entropy, secret input and a
124 // salt. The size of the output is determined by KDF.ExtractSize.
125 func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) {
126 return hkdf.Extract(k.hash(), secret, salt)
127 }
128
129 // Expand derives a variable length pseudorandom string from a pseudorandom key
130 // and an information string. Panics if the pseudorandom key is less
131 // than N bytes, or if the output length is greater than 255*N bytes,
132 // where N is the size returned by KDF.Extract function.
133 func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte {
134 extractSize := k.ExtractSize()
135 if len(pseudorandomKey) < extractSize {
136 panic(fmt.Errorf("pseudorandom key must be %v bytes", extractSize))
137 }
138 maxLength := uint(255 * extractSize)
139 if outputLen > maxLength {
140 panic(fmt.Errorf("output length must be less than %v bytes", maxLength))
141 }
142 output := make([]byte, outputLen)
143 rd := hkdf.Expand(k.hash(), pseudorandomKey[:extractSize], info)
144 _, err := io.ReadFull(rd, output)
145 if err != nil {
146 panic(err)
147 }
148 return output
149 }
150
151 func (k KDF) hash() func() hash.Hash {
152 switch k {
153 case KDF_HKDF_SHA256:
154 return crypto.SHA256.New
155 case KDF_HKDF_SHA384:
156 return crypto.SHA384.New
157 case KDF_HKDF_SHA512:
158 return crypto.SHA512.New
159 default:
160 panic(ErrInvalidKDF)
161 }
162 }
163
164 type AEAD uint16
165
166 //nolint:golint,stylecheck
167 const (
168 // AEAD_AES128GCM is AES-128 block cipher in Galois Counter Mode (GCM).
169 AEAD_AES128GCM AEAD = 0x01
170 // AEAD_AES256GCM is AES-256 block cipher in Galois Counter Mode (GCM).
171 AEAD_AES256GCM AEAD = 0x02
172 // AEAD_ChaCha20Poly1305 is ChaCha20 stream cipher and Poly1305 MAC.
173 AEAD_ChaCha20Poly1305 AEAD = 0x03
174 )
175
176 // New instantiates an AEAD cipher from the identifier, returns an error if the
177 // identifier is not known.
178 func (a AEAD) New(key []byte) (cipher.AEAD, error) {
179 switch a {
180 case AEAD_AES128GCM, AEAD_AES256GCM:
181 block, err := aes.NewCipher(key)
182 if err != nil {
183 return nil, err
184 }
185 return cipher.NewGCM(block)
186 case AEAD_ChaCha20Poly1305:
187 return chacha20poly1305.New(key)
188 default:
189 panic(ErrInvalidAEAD)
190 }
191 }
192
193 func (a AEAD) IsValid() bool {
194 switch a {
195 case AEAD_AES128GCM,
196 AEAD_AES256GCM,
197 AEAD_ChaCha20Poly1305:
198 return true
199 default:
200 return false
201 }
202 }
203
204 // KeySize returns the size in bytes of the keys used by the AEAD cipher.
205 func (a AEAD) KeySize() uint {
206 switch a {
207 case AEAD_AES128GCM:
208 return 16
209 case AEAD_AES256GCM:
210 return 32
211 case AEAD_ChaCha20Poly1305:
212 return chacha20poly1305.KeySize
213 default:
214 panic(ErrInvalidAEAD)
215 }
216 }
217
218 // NonceSize returns the size in bytes of the nonce used by the AEAD cipher.
219 func (a AEAD) NonceSize() uint {
220 switch a {
221 case AEAD_AES128GCM,
222 AEAD_AES256GCM,
223 AEAD_ChaCha20Poly1305:
224 return 12
225 default:
226 panic(ErrInvalidAEAD)
227 }
228 }
229
230 // CipherLen returns the length of a ciphertext corresponding to a message of
231 // length mLen.
232 func (a AEAD) CipherLen(mLen uint) uint {
233 switch a {
234 case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305:
235 return mLen + 16
236 default:
237 panic(ErrInvalidAEAD)
238 }
239 }
240
241 var (
242 dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM
243 dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM
244 hybridkemX25519Kyber768 hybridKEM
245 kemXwing genericNoAuthKEM
246 )
247
248 func init() {
249 dhkemp256hkdfsha256.Curve = ecdh.P256()
250 dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256
251 dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
252 dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256
253 dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256
254
255 dhkemp384hkdfsha384.Curve = ecdh.P384()
256 dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384
257 dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
258 dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384
259 dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384
260
261 dhkemp521hkdfsha512.Curve = ecdh.P521()
262 dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512
263 dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
264 dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512
265 dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512
266
267 dhkemx25519hkdfsha256.size = x25519.Size
268 dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256
269 dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
270 dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256
271 dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256
272
273 dhkemx448hkdfsha512.size = x448.Size
274 dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512
275 dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
276 dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512
277 dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512
278
279 hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00
280 hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256"
281 hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256
282 hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256
283 hybridkemX25519Kyber768.kemB = kyber768.Scheme()
284
285 kemXwing.Scheme = xwing.Scheme()
286 kemXwing.name = "HPKE_KEM_XWING"
287 }
288