Botan 3.13.0
Crypto and TLS for C&
gcm_siv.cpp
Go to the documentation of this file.
1/*
2* GCM-SIV Mode (RFC 8452)
3* (C) 2026 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/internal/gcm_siv.h>
9
10#include <botan/exceptn.h>
11#include <botan/mem_ops.h>
12#include <botan/internal/ct_utils.h>
13#include <botan/internal/fmt.h>
14#include <botan/internal/int_utils.h>
15#include <botan/internal/loadstor.h>
16
17namespace Botan {
18
19namespace {
20
21Key_Length_Specification gcm_siv_key_spec(const BlockCipher& cipher) {
22 if(cipher.block_size() != 16) {
23 throw Invalid_Argument("GCM-SIV requires a 128 bit block cipher");
24 }
25
26 // The key-generating key is either 128 or 256 bits, and the derived
27 // message-encryption key is of the same length
28 const bool k16 = cipher.valid_keylength(16);
29 const bool k32 = cipher.valid_keylength(32);
30
31 if(k16 && k32) {
32 return Key_Length_Specification(16, 32, 16);
33 } else if(k16) {
34 return Key_Length_Specification(16);
35 } else if(k32) {
36 return Key_Length_Specification(32);
37 } else {
38 throw Invalid_Argument("GCM-SIV requires a cipher supporting 128 or 256 bit keys");
39 }
40}
41
42} // namespace
43
44GCM_SIV_Mode::GCM_SIV_Mode(std::unique_ptr<BlockCipher> cipher) :
45 m_cipher_name(cipher->name()),
46 m_key_spec(gcm_siv_key_spec(*cipher)),
47 m_cipher(std::move(cipher)),
48 m_msg_cipher(m_cipher->new_object()) {}
49
51
53 m_cipher->clear();
54 m_kgk_len = 0;
55 zap(m_ad);
56 reset();
57}
58
60 // The derived keys are per-message; start_msg rekeys both
61 m_msg_cipher->clear();
62 m_polyval.clear();
63 secure_scrub_memory(m_nonce);
64 m_msg_buf.clear();
65 m_in_msg = false;
66}
67
68std::string GCM_SIV_Mode::name() const {
69 return fmt("{}/GCM-SIV", m_cipher_name);
70}
71
72std::string GCM_SIV_Mode::provider() const {
73 return m_polyval.provider();
74}
75
77 return 1;
78}
79
81 return BS * std::max<size_t>(2, BlockCipher::ParallelismMult);
82}
83
84bool GCM_SIV_Mode::valid_nonce_length(size_t len) const {
85 return len == 12;
86}
87
89 return m_key_spec;
90}
91
93 return m_cipher->has_keying_material();
94}
95
96void GCM_SIV_Mode::key_schedule(std::span<const uint8_t> key) {
97 m_cipher->set_key(key);
98 m_kgk_len = key.size();
99 reset();
100}
101
102void GCM_SIV_Mode::set_associated_data_n(size_t idx, std::span<const uint8_t> ad) {
103 BOTAN_ARG_CHECK(idx == 0, "GCM-SIV: cannot handle non-zero index in set_associated_data_n");
104 BOTAN_STATE_CHECK(!m_in_msg);
105 BOTAN_ARG_CHECK(static_cast<uint64_t>(ad.size()) <= MAX_INPUT_LEN, "GCM-SIV AD too large");
106 m_ad.assign(ad.begin(), ad.end());
107}
108
109void GCM_SIV_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) {
111 BOTAN_STATE_CHECK(!m_in_msg);
112
113 if(!valid_nonce_length(nonce_len)) {
114 throw Invalid_IV_Length(name(), nonce_len);
115 }
116
117 copy_mem(m_nonce, std::span{nonce, nonce_len});
118
119 /*
120 Derive the per-nonce keys, RFC 8452 section 4:
121
122 "These keys are generated by encrypting a series of plaintext blocks
123 that contain a 32-bit, little-endian counter followed by the nonce,
124 and then discarding the second half of the resulting ciphertext."
125 */
126 const size_t blocks = (m_kgk_len == 16) ? 4 : 6;
127
128 std::array<uint8_t, 6 * BS> kb{};
129 for(size_t i = 0; i != blocks; ++i) {
130 store_le(static_cast<uint32_t>(i), &kb[BS * i]);
131 copy_mem(&kb[BS * i + 4], m_nonce.data(), m_nonce.size());
132 }
133 m_cipher->encrypt_n(kb.data(), kb.data(), blocks);
134
135 std::array<uint8_t, 16> auth_key{};
136 secure_vector<uint8_t> enc_key(m_kgk_len);
137
138 for(size_t i = 0; i != 2; ++i) {
139 copy_mem(&auth_key[8 * i], &kb[BS * i], 8);
140 }
141 for(size_t i = 0; i != blocks - 2; ++i) {
142 copy_mem(&enc_key[8 * i], &kb[BS * (i + 2)], 8);
143 }
144
145 m_polyval.set_key(auth_key);
146 m_msg_cipher->set_key(enc_key);
147
149 secure_scrub_memory(auth_key);
150
151 m_msg_buf.clear();
152 m_in_msg = true;
153}
154
155size_t GCM_SIV_Mode::process_msg(uint8_t buf[], size_t sz) {
156 BOTAN_STATE_CHECK(m_in_msg);
157
158 // Early rejection to bound buffering; the exact (per-direction) limits
159 // are checked in finish. The tag is included to allow decryption inputs.
160 if(sz > MAX_INPUT_LEN + BS - m_msg_buf.size()) {
161 throw Invalid_State("GCM-SIV message length limit exceeded");
162 }
163
164 // All input is buffered until finish
165 m_msg_buf.insert(m_msg_buf.end(), buf, buf + sz);
166 return 0;
167}
168
169std::array<uint8_t, GCM_SIV_Mode::BS> GCM_SIV_Mode::compute_tag(std::span<const uint8_t> ptext) {
170 m_polyval.update(m_ad);
171 m_polyval.zero_pad();
172 m_polyval.update(ptext);
173 m_polyval.zero_pad();
174
175 const uint64_t ad_bits = 8 * static_cast<uint64_t>(m_ad.size());
176 const uint64_t pt_bits = 8 * static_cast<uint64_t>(ptext.size());
177 m_polyval.update(store_le(ad_bits, pt_bits));
178
179 std::array<uint8_t, BS> S{};
180 m_polyval.final(S);
181
182 /*
183 RFC 8452 section 4: "XOR the first twelve bytes of S_s with the nonce
184 and clear the most significant bit of the last byte. Encrypt the
185 result with AES using the message-encryption key to produce the tag."
186 */
187 xor_buf(S.data(), m_nonce.data(), m_nonce.size());
188 S[15] &= 0x7f;
189 m_msg_cipher->encrypt(S);
190
191 return S;
192}
193
194void GCM_SIV_Mode::ctr_xor(std::span<const uint8_t, BS> tag, uint8_t buf[], size_t len) {
195 /*
196 RFC 8452 section 4: "The initial counter block is the tag with the
197 most significant bit of the last byte set to one. The counter
198 advances by incrementing the first 32 bits interpreted as an
199 unsigned, little-endian integer, wrapping at 2^32."
200 */
201 std::array<uint8_t, BS> ctr_block{};
202 copy_mem(ctr_block, tag);
203 ctr_block[15] |= 0x80;
204
205 uint32_t ctr32 = load_le<uint32_t>(ctr_block.data(), 0);
206
207 secure_vector<uint8_t> ks(m_msg_cipher->parallel_bytes());
208
209 while(len > 0) {
210 const size_t blocks = std::min((len + BS - 1) / BS, ks.size() / BS);
211
212 for(size_t i = 0; i != blocks; ++i) {
213 copy_mem(&ks[BS * i], ctr_block.data(), BS);
214 store_le(ctr32, &ks[BS * i]);
215 ctr32 += 1;
216 }
217
218 m_msg_cipher->encrypt_n(ks.data(), ks.data(), blocks);
219
220 const size_t todo = std::min(len, blocks * BS);
221 xor_buf(buf, ks.data(), todo);
222 buf += todo;
223 len -= todo;
224 }
225}
226
227size_t GCM_SIV_Encryption::output_length(size_t input_length) const {
228 return add_or_throw(input_length, tag_size(), "GCM-SIV input too large");
229}
230
231void GCM_SIV_Encryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
233 BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset");
234
235 buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end());
236 msg_buf().clear();
237
238 const size_t ptext_len = buffer.size() - offset;
239 BOTAN_ARG_CHECK(static_cast<uint64_t>(ptext_len) <= MAX_INPUT_LEN, "GCM-SIV plaintext too large");
240 uint8_t* buf = buffer.data() + offset;
241
242 const auto tag = compute_tag({buf, ptext_len});
243 ctr_xor(tag, buf, ptext_len);
244
245 buffer += std::make_pair(tag.data(), tag.size());
246 reset();
247}
248
249size_t GCM_SIV_Decryption::output_length(size_t input_length) const {
250 BOTAN_ARG_CHECK(input_length >= tag_size(), "Message too short to be valid");
251 return input_length - tag_size();
252}
253
254void GCM_SIV_Decryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) {
256 BOTAN_ARG_CHECK(offset <= buffer.size(), "Invalid offset");
257
258 if(!msg_buf().empty()) {
259 buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end());
260 msg_buf().clear();
261 }
262
263 const size_t sz = buffer.size() - offset;
264 BOTAN_ARG_CHECK(sz >= tag_size(), "input did not include the tag");
265
266 const size_t ctext_len = sz - tag_size();
267 BOTAN_ARG_CHECK(static_cast<uint64_t>(ctext_len) <= MAX_INPUT_LEN, "GCM-SIV ciphertext too large");
268 uint8_t* buf = buffer.data() + offset;
269
270 std::array<uint8_t, 16> included_tag{};
271 copy_mem(included_tag, std::span{buf + ctext_len, 16});
272
273 ctr_xor(included_tag, buf, ctext_len);
274
275 const auto expected_tag = compute_tag({buf, ctext_len});
276
277 reset();
278
279 if(!CT::is_equal(expected_tag.data(), included_tag.data(), included_tag.size()).as_bool()) {
280 clear_mem(std::span{buffer}.subspan(offset, ctext_len));
281 throw Invalid_Authentication_Tag("GCM-SIV tag check failed");
282 }
283
284 buffer.resize(offset + ctext_len);
285}
286
287} // namespace Botan
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static constexpr size_t ParallelismMult
size_t output_length(size_t input_length) const override
Definition gcm_siv.cpp:249
size_t output_length(size_t input_length) const override
Definition gcm_siv.cpp:227
~GCM_SIV_Mode() override
std::string name() const final
Definition gcm_siv.cpp:68
static constexpr size_t BS
Definition gcm_siv.h:55
static constexpr uint64_t MAX_INPUT_LEN
RFC 8452 limits both the plaintext and the AD to 2**36 bytes.
Definition gcm_siv.h:58
bool valid_nonce_length(size_t len) const final
Definition gcm_siv.cpp:84
bool in_msg() const
Definition gcm_siv.h:62
std::string provider() const final
Definition gcm_siv.cpp:72
size_t ideal_granularity() const final
Definition gcm_siv.cpp:80
void ctr_xor(std::span< const uint8_t, BS > tag, uint8_t buf[], size_t len)
XOR the buffer with the CTR keystream, starting from the tag-derived counter.
Definition gcm_siv.cpp:194
void clear() final
Definition gcm_siv.cpp:52
size_t tag_size() const final
Definition gcm_siv.h:35
Key_Length_Specification key_spec() const final
Definition gcm_siv.cpp:88
bool has_keying_material() const final
Definition gcm_siv.cpp:92
void reset() final
Definition gcm_siv.cpp:59
secure_vector< uint8_t > & msg_buf()
Definition gcm_siv.h:60
size_t update_granularity() const final
Definition gcm_siv.cpp:76
void set_associated_data_n(size_t idx, std::span< const uint8_t > ad) final
Definition gcm_siv.cpp:102
GCM_SIV_Mode(std::unique_ptr< BlockCipher > cipher)
Definition gcm_siv.cpp:44
std::array< uint8_t, BS > compute_tag(std::span< const uint8_t > ptext)
Compute the expected tag for the (unpadded) plaintext.
Definition gcm_siv.cpp:169
bool valid_keylength(size_t length) const
Definition sym_algo.h:44
void assert_key_material_set() const
Definition sym_algo.h:180
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
constexpr T add_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:66
void zap(std::vector< T, Alloc > &vec)
Definition secmem.h:261
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
void secure_scrub_memory(void *ptr, size_t n)
Definition mem_utils.cpp:25
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
constexpr auto load_le(ParamTs &&... params)
Definition loadstor.h:495
constexpr void xor_buf(ranges::contiguous_output_range< uint8_t > auto &&out, ranges::contiguous_range< uint8_t > auto &&in)
Definition mem_ops.h:403
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
constexpr void clear_mem(T *ptr, size_t n)
Definition mem_ops.h:118