Skip to content

Commit 5645437

Browse files
alexclaude
andauthored
Add AES-256-GCM support to the HPKE implementation (#14397)
Add AES_256_GCM (AEAD ID 0x0002) as an AEAD option for HPKE, alongside the existing AES-128-GCM and ChaCha20Poly1305 options. This completes support for all three AEAD algorithms defined in RFC 9180. https://claude.ai/code/session_013SgdaoQ9Jn8qAYqkNPWByV Co-authored-by: Claude <noreply@anthropic.com>
1 parent d41476b commit 5645437

4 files changed

Lines changed: 26 additions & 10 deletions

File tree

docs/hazmat/primitives/hpke.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ HPKE is a standard for public key encryption that combines a Key Encapsulation
99
Mechanism (KEM), a Key Derivation Function (KDF), and an Authenticated
1010
Encryption with Associated Data (AEAD) scheme. It is defined in :rfc:`9180`.
1111

12-
This implementation supports Base mode with DHKEM(X25519, HKDF-SHA256),
13-
HKDF-SHA256, and either AES-128-GCM or ChaCha20Poly1305.
14-
1512
HPKE provides authenticated encryption: the recipient can be certain that the
1613
message was encrypted by someone who knows the recipient's public key, but
1714
the sender is anonymous. Each call to :meth:`Suite.encrypt` generates a fresh
@@ -100,6 +97,10 @@ specifying auxiliary authenticated information.
10097

10198
AES-128-GCM
10299

100+
.. attribute:: AES_256_GCM
101+
102+
AES-256-GCM
103+
103104
.. attribute:: CHACHA20_POLY1305
104105

105106
ChaCha20Poly1305

src/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class KDF:
1414

1515
class AEAD:
1616
AES_128_GCM: AEAD
17+
AES_256_GCM: AEAD
1718
CHACHA20_POLY1305: AEAD
1819

1920
class Suite:

src/rust/src/backend/hpke.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ mod aead_params {
3131
pub const AES_128_GCM_NN: usize = 12;
3232
pub const AES_128_GCM_NT: usize = 16;
3333

34+
pub const AES_256_GCM_ID: u16 = 0x0002;
35+
pub const AES_256_GCM_NK: usize = 32;
36+
pub const AES_256_GCM_NN: usize = 12;
37+
pub const AES_256_GCM_NT: usize = 16;
38+
3439
pub const CHACHA20_POLY1305_ID: u16 = 0x0003;
3540
pub const CHACHA20_POLY1305_NK: usize = 32;
3641
pub const CHACHA20_POLY1305_NN: usize = 12;
@@ -96,34 +101,39 @@ impl KDF {
96101
#[derive(Clone, PartialEq, Eq, Hash)]
97102
pub(crate) enum AEAD {
98103
AES_128_GCM,
104+
AES_256_GCM,
99105
CHACHA20_POLY1305,
100106
}
101107

102108
impl AEAD {
103109
fn id(&self) -> u16 {
104110
match self {
105111
AEAD::AES_128_GCM => aead_params::AES_128_GCM_ID,
112+
AEAD::AES_256_GCM => aead_params::AES_256_GCM_ID,
106113
AEAD::CHACHA20_POLY1305 => aead_params::CHACHA20_POLY1305_ID,
107114
}
108115
}
109116

110117
fn key_length(&self) -> usize {
111118
match self {
112119
AEAD::AES_128_GCM => aead_params::AES_128_GCM_NK,
120+
AEAD::AES_256_GCM => aead_params::AES_256_GCM_NK,
113121
AEAD::CHACHA20_POLY1305 => aead_params::CHACHA20_POLY1305_NK,
114122
}
115123
}
116124

117125
fn nonce_length(&self) -> usize {
118126
match self {
119127
AEAD::AES_128_GCM => aead_params::AES_128_GCM_NN,
128+
AEAD::AES_256_GCM => aead_params::AES_256_GCM_NN,
120129
AEAD::CHACHA20_POLY1305 => aead_params::CHACHA20_POLY1305_NN,
121130
}
122131
}
123132

124133
fn tag_length(&self) -> usize {
125134
match self {
126135
AEAD::AES_128_GCM => aead_params::AES_128_GCM_NT,
136+
AEAD::AES_256_GCM => aead_params::AES_256_GCM_NT,
127137
AEAD::CHACHA20_POLY1305 => aead_params::CHACHA20_POLY1305_NT,
128138
}
129139
}
@@ -306,7 +316,7 @@ impl Suite {
306316
let key_obj = key.clone().unbind().into_any();
307317
let nonce_buf = CffiBuf::from_bytes(py, nonce.as_bytes());
308318
match &self.aead {
309-
AEAD::AES_128_GCM => {
319+
AEAD::AES_128_GCM | AEAD::AES_256_GCM => {
310320
let cipher = AesGcm::new(py, key_obj)?;
311321
cipher.encrypt(py, nonce_buf, plaintext, aad)
312322
}
@@ -328,7 +338,7 @@ impl Suite {
328338
let key_obj = key.clone().unbind().into_any();
329339
let nonce_buf = CffiBuf::from_bytes(py, nonce.as_bytes());
330340
match &self.aead {
331-
AEAD::AES_128_GCM => {
341+
AEAD::AES_128_GCM | AEAD::AES_256_GCM => {
332342
let cipher = AesGcm::new(py, key_obj)?;
333343
cipher.decrypt(py, nonce_buf, CffiBuf::from_bytes(py, ciphertext), aad)
334344
}

tests/hazmat/primitives/test_hpke.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
33
# for complete details.
44

5+
import itertools
56
import json
67
import os
78

@@ -21,11 +22,13 @@
2122

2223
X25519_ENC_LENGTH = 32
2324

24-
SUPPORTED_SUITES = [
25-
(KEM.X25519, KDF.HKDF_SHA256, AEAD.AES_128_GCM),
26-
(KEM.X25519, KDF.HKDF_SHA256, AEAD.CHACHA20_POLY1305),
27-
(KEM.X25519, KDF.HKDF_SHA512, AEAD.AES_128_GCM),
28-
]
25+
SUPPORTED_SUITES = list(
26+
itertools.product(
27+
[KEM.X25519],
28+
[KDF.HKDF_SHA256, KDF.HKDF_SHA512],
29+
[AEAD.AES_128_GCM, AEAD.AES_256_GCM, AEAD.CHACHA20_POLY1305],
30+
)
31+
)
2932

3033

3134
@pytest.mark.supported(
@@ -232,6 +235,7 @@ def test_vector_decryption(self, subtests):
232235
}
233236
aead_map = {
234237
0x0001: AEAD.AES_128_GCM,
238+
0x0002: AEAD.AES_256_GCM,
235239
0x0003: AEAD.CHACHA20_POLY1305,
236240
}
237241

0 commit comments

Comments
 (0)