Botan 3.13.0
Crypto and TLS for C&
ecies.cpp
Go to the documentation of this file.
1/*
2* ECIES
3* (C) 2016 Philipp Weber
4* 2016 Daniel Neus, Rohde & Schwarz Cybersecurity
5* 2025 Jack Lloyd
6*
7* Botan is released under the Simplified BSD License (see license.txt)
8*/
9
10#include <botan/ecies.h>
11
12#include <botan/cipher_mode.h>
13#include <botan/ecdh.h>
14#include <botan/kdf.h>
15#include <botan/mac.h>
16#include <botan/rng.h>
17#include <botan/internal/concat_util.h>
18#include <botan/internal/ct_utils.h>
19#include <botan/internal/pk_ops_impl.h>
20
21namespace Botan {
22
23namespace {
24
25/**
26* Private key type for ECIES_ECDH_KA_Operation
27*
28* TODO(Botan4) this can be removed once cofactor support is removed from ECDH
29*/
30
33
34class ECIES_PrivateKey final : public virtual EC_PrivateKey,
35 public virtual PK_Key_Agreement_Key {
36 public:
37 explicit ECIES_PrivateKey(const ECDH_PrivateKey& private_key) :
38 // NOLINTNEXTLINE(*-slicing)
39 EC_PublicKey(private_key), EC_PrivateKey(private_key), PK_Key_Agreement_Key(), m_key(private_key) {}
40
41 std::vector<uint8_t> public_value() const override { return m_key.public_value(); }
42
43 std::string algo_name() const override { return "ECIES"; }
44
45 std::unique_ptr<Public_Key> public_key() const override { return m_key.public_key(); }
46
47 bool supports_operation(PublicKeyOperation op) const override { return (op == PublicKeyOperation::KeyAgreement); }
48
49 std::unique_ptr<Private_Key> generate_another(RandomNumberGenerator& rng) const override {
50 return m_key.generate_another(rng);
51 }
52
53 std::unique_ptr<PK_Ops::Key_Agreement> create_key_agreement_op(RandomNumberGenerator& rng,
54 std::string_view params,
55 std::string_view provider) const override;
56
57 private:
58 ECDH_PrivateKey m_key;
59};
60
62
63/**
64* Implements ECDH key agreement without using the cofactor mode
65*
66* TODO(Botan4) this can be removed once cofactor support is removed from ECDH
67*/
68class ECIES_ECDH_KA_Operation final : public PK_Ops::Key_Agreement_with_KDF {
69 public:
70 ECIES_ECDH_KA_Operation(const ECIES_PrivateKey& private_key, RandomNumberGenerator& rng) :
71 PK_Ops::Key_Agreement_with_KDF("Raw"), m_key(private_key), m_rng(rng) {}
72
73 size_t agreed_value_size() const override { return m_key.domain().get_p_bytes(); }
74
75 secure_vector<uint8_t> raw_agree(const uint8_t w[], size_t w_len) override {
76 const EC_Group& group = m_key.domain();
77 if(auto input_point = EC_AffinePoint::deserialize(group, {w, w_len})) {
78 return input_point->mul(m_key._private_key(), m_rng).x_bytes();
79 } else {
80 throw Decoding_Error("ECIES - Invalid elliptic curve point");
81 }
82 }
83
84 private:
85 ECIES_PrivateKey m_key;
86 RandomNumberGenerator& m_rng;
87};
88
89std::unique_ptr<PK_Ops::Key_Agreement> ECIES_PrivateKey::create_key_agreement_op(RandomNumberGenerator& rng,
90 std::string_view /*params*/,
91 std::string_view /*provider*/) const {
92 return std::make_unique<ECIES_ECDH_KA_Operation>(*this, rng);
93}
94
95/**
96* Creates a PK_Key_Agreement instance for the given key and ecies_params
97* Returns either ECIES_ECDH_KA_Operation or the default implementation for the given key,
98* depending on the key and ecies_params
99* @param private_key the private key used for the key agreement
100* @param ecies_params settings for ecies
101* @param for_encryption disable cofactor mode if the secret will be used for encryption
102* (according to ISO 18033 cofactor mode is only used during decryption)
103*
104* TODO(Botan4) this entire function can be removed once cofactor support is gone
105*/
106PK_Key_Agreement create_key_agreement(const PK_Key_Agreement_Key& private_key,
107 const ECIES_KA_Params& ecies_params,
108 bool for_encryption,
109 RandomNumberGenerator& rng) {
110 const ECDH_PrivateKey* ecdh_key = dynamic_cast<const ECDH_PrivateKey*>(&private_key);
111
112 if(ecdh_key == nullptr &&
113 (ecies_params.cofactor_mode() || ecies_params.old_cofactor_mode() || ecies_params.check_mode())) {
114 // assume we have a private key from an external provider (e.g. pkcs#11):
115 // there is no way to determine or control whether the provider uses cofactor mode or not.
116 // ISO 18033 does not allow cofactor mode in combination with old cofactor mode or check mode
117 // => disable cofactor mode, old cofactor mode and check mode for unknown keys/providers (as a precaution).
118 throw Invalid_Argument("ECIES: cofactor, old cofactor and check mode are only supported for ECDH_PrivateKey");
119 }
120
121 if(ecdh_key != nullptr && (for_encryption || !ecies_params.cofactor_mode())) {
122 // ECDH_KA_Operation uses cofactor mode: use own key agreement method if cofactor should not be used.
123 return PK_Key_Agreement(ECIES_PrivateKey(*ecdh_key), rng, "Raw");
124 }
125
126 return PK_Key_Agreement(private_key, rng, "Raw"); // use default implementation
127}
128} // namespace
129
131 const ECIES_KA_Params& ecies_params,
132 bool for_encryption,
134 m_ka(create_key_agreement(private_key, ecies_params, for_encryption, rng)), m_params(ecies_params) {}
135
136#if defined(BOTAN_HAS_LEGACY_EC_POINT)
137/**
138* ECIES secret derivation according to ISO 18033-2
139*/
140SymmetricKey ECIES_KA_Operation::derive_secret(const std::vector<uint8_t>& eph_public_key_bin,
141 const EC_Point& other_public_key_point) const {
142 if(other_public_key_point.is_zero()) {
143 throw Invalid_Argument("ECIES: other public key point is zero");
144 }
145
146 auto kdf = KDF::create_or_throw(m_params.kdf());
147
148 EC_Point other_point = other_public_key_point;
149
150 // ISO 18033: step b
151 // TODO(Botan4) remove when cofactor support is removed
152 if(m_params.old_cofactor_mode() && m_params.group().has_cofactor()) {
153 other_point *= m_params.group().get_cofactor();
154 }
155
156 secure_vector<uint8_t> derivation_input;
157
158 // ISO 18033: encryption step e / decryption step g
159 if(!m_params.single_hash_mode()) {
160 derivation_input += eph_public_key_bin;
161 }
162
163 // ISO 18033: encryption step f / decryption step h
164 std::vector<uint8_t> other_public_key_bin = other_point.encode(m_params.point_format());
165 // Note: the argument `m_params.secret_length()` passed for `key_len` will only be used by providers because
166 // "Raw" is passed to the `PK_Key_Agreement` if the implementation of botan is used.
167 const SymmetricKey peh =
168 m_ka.derive_key(m_params.group().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size());
169 derivation_input.insert(derivation_input.end(), peh.begin(), peh.end());
170
171 // ISO 18033: encryption step g / decryption step i
172 return SymmetricKey(kdf->derive_key(m_params.secret_length(), derivation_input));
173}
174#endif
175
176/**
177* ECIES secret derivation according to ISO 18033-2
178*/
179SymmetricKey ECIES_KA_Operation::derive_secret(std::span<const uint8_t> eph_public_key_bin,
180 const EC_AffinePoint& other_public_key_point) const {
181 BOTAN_ARG_CHECK(!other_public_key_point.is_identity(), "ECIES: peer public key point is the identity element");
182
183 auto kdf = KDF::create_or_throw(m_params.kdf());
184
185 auto other_point = other_public_key_point;
186
187 const auto& group = m_params.group();
188
189 // ISO 18033: step b
190 // TODO(Botan4) remove when cofactor support is removed
191 if(m_params.old_cofactor_mode() && group.has_cofactor()) {
192 Null_RNG null_rng;
193 auto cofactor = EC_Scalar::from_bigint(group, group.get_cofactor());
194 other_point = other_point.mul(cofactor, null_rng);
195 }
196
197 secure_vector<uint8_t> derivation_input;
198
199 // ISO 18033: encryption step e / decryption step g
200 if(!m_params.single_hash_mode()) {
201 derivation_input.assign(eph_public_key_bin.begin(), eph_public_key_bin.end());
202 }
203
204 // ISO 18033: encryption step f / decryption step h
205 std::vector<uint8_t> other_public_key_bin = other_point.serialize(m_params.point_format());
206 // Note: the argument `m_params.secret_length()` passed for `key_len` will only be used by providers because
207 // "Raw" is passed to the `PK_Key_Agreement` if the implementation of botan is used.
208 const SymmetricKey peh =
209 m_ka.derive_key(m_params.group().get_order_bytes(), other_public_key_bin.data(), other_public_key_bin.size());
210 derivation_input.insert(derivation_input.end(), peh.begin(), peh.end());
211
212 // ISO 18033: encryption step g / decryption step i
213 return SymmetricKey(kdf->derive_key(m_params.secret_length(), derivation_input));
214}
215
217 const EC_Group& group, std::string_view kdf, size_t length, EC_Point_Format point_format, ECIES_Flags flags) :
218 m_group(group),
219 m_kdf(kdf),
220 m_length(length),
221 m_point_format(point_format),
222 m_single_hash_mode((flags & ECIES_Flags::SingleHashMode) == ECIES_Flags::SingleHashMode),
223 m_check_mode((flags & ECIES_Flags::CheckMode) == ECIES_Flags::CheckMode),
224 m_cofactor_mode((flags & ECIES_Flags::CofactorMode) == ECIES_Flags::CofactorMode),
225 m_old_cofactor_mode((flags & ECIES_Flags::OldCofactorMode) == ECIES_Flags::OldCofactorMode) {}
226
228 const EC_Group& group, std::string_view kdf, size_t length, EC_Point_Format point_format, bool single_hash_mode) :
229 m_group(group),
230 m_kdf(kdf),
231 m_length(length),
232 m_point_format(point_format),
233 m_single_hash_mode(single_hash_mode),
234 m_check_mode(true),
235 m_cofactor_mode(false),
236 m_old_cofactor_mode(false) {}
237
239 std::string_view kdf,
240 std::string_view dem_algo_spec,
241 size_t dem_key_len,
242 std::string_view mac_spec,
243 size_t mac_key_len,
245 ECIES_Flags flags) :
246 ECIES_KA_Params(group, kdf, dem_key_len + mac_key_len, point_format, flags),
247 m_dem_spec(dem_algo_spec),
248 m_dem_keylen(dem_key_len),
249 m_mac_spec(mac_spec),
250 m_mac_keylen(mac_key_len) {
251 // ISO 18033: "At most one of CofactorMode, OldCofactorMode, and CheckMode may be 1."
252 if(size_t(cofactor_mode()) + size_t(old_cofactor_mode()) + size_t(check_mode()) > 1) {
253 throw Invalid_Argument("ECIES: only one of cofactor_mode, old_cofactor_mode and check_mode can be set");
254 }
255}
256
258 std::string_view kdf,
259 std::string_view dem_algo_spec,
260 size_t dem_key_len,
261 std::string_view mac_spec,
262 size_t mac_key_len,
264 bool single_hash_mode) :
265 ECIES_KA_Params(group, kdf, dem_key_len + mac_key_len, point_format, single_hash_mode),
266 m_dem_spec(dem_algo_spec),
267 m_dem_keylen(dem_key_len),
268 m_mac_spec(mac_spec),
269 m_mac_keylen(mac_key_len) {}
270
271std::unique_ptr<MessageAuthenticationCode> ECIES_System_Params::create_mac() const {
273}
274
275std::unique_ptr<Cipher_Mode> ECIES_System_Params::create_cipher(Cipher_Dir direction) const {
276 return Cipher_Mode::create_or_throw(m_dem_spec, direction);
277}
278
279/*
280* ECIES_Encryptor Constructor
281*/
283 const ECIES_System_Params& ecies_params,
285 m_ka(private_key, ecies_params, true, rng),
286 m_params(ecies_params),
287 m_eph_public_key_bin(private_key.public_value()) {
288 if(ecies_params.point_format() != EC_Point_Format::Uncompressed) {
289 // ISO 18033: step d
290 // convert only if necessary; m_eph_public_key_bin has been initialized with the uncompressed format
291 m_eph_public_key_bin =
292 EC_AffinePoint(m_params.group(), m_eph_public_key_bin).serialize(ecies_params.point_format());
293 }
294 m_mac = m_params.create_mac();
295 m_cipher = m_params.create_cipher(Cipher_Dir::Encryption);
296}
297
298/*
299* ECIES_Encryptor Constructor
300*/
302 ECIES_Encryptor(ECDH_PrivateKey(rng, ecies_params.group()), ecies_params, rng) {}
303
305 /*
306 ECIES should just be used for key transport so this (arbitrary) limit
307 seems sufficient
308 */
309 return 64;
310}
311
312size_t ECIES_Encryptor::ciphertext_length(size_t ptext_len) const {
313 return m_eph_public_key_bin.size() + m_mac->output_length() + m_cipher->output_length(ptext_len);
314}
315
316/*
317* ECIES Encryption according to ISO 18033-2
318*/
319std::vector<uint8_t> ECIES_Encryptor::enc(const uint8_t data[],
320 size_t length,
321 RandomNumberGenerator& /*unused*/) const {
322 if(!m_other_point.has_value()) {
323 throw Invalid_State("ECIES_Encryptor: peer key invalid or not set");
324 }
325 if(!m_iv.has_value()) {
326 throw Invalid_State("ECIES requires a fresh IV be provided for each message");
327 }
328 if(m_iv->empty() && !m_cipher->valid_nonce_length(m_iv->size())) {
329 throw Invalid_Argument("ECIES with " + m_cipher->name() + " requires an IV be set");
330 }
331
332 const SymmetricKey secret_key = m_ka.derive_secret(m_eph_public_key_bin, m_other_point.value());
333
334 BOTAN_ASSERT_NOMSG(secret_key.size() == m_params.dem_keylen() + m_params.mac_keylen());
335
336 // encryption
337
338 m_cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen()));
339
340 m_cipher->start(m_iv->bits_of());
341 m_iv.reset();
342
343 secure_vector<uint8_t> encrypted_data(data, data + length);
344 m_cipher->finish(encrypted_data);
345
346 // compute the MAC
347 m_mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen());
348 m_mac->update(encrypted_data);
349 if(!m_label.empty()) {
350 m_mac->update(m_label);
351 }
352 const auto mac = m_mac->final();
353
354 // concat elements
355 return concat(m_eph_public_key_bin, encrypted_data, mac);
356}
357
359 const ECIES_System_Params& ecies_params,
361 m_ka(key, ecies_params, false, rng), m_params(ecies_params) {
362 /*
363 ISO 18033: "If v > 1 and CheckMode = 0, then we must have gcd(u, v) = 1." (v = index, u= order)
364
365 We skip this check because even if CheckMode = 0 we actually do check that
366 the point is valid. In addition the check from ISO 18033 is pretty odd; u is
367 the _prime_ order subgroup, and v is the cofactor. For gcd(u, v) > 1 to occur
368 the cofactor would have to be a multiple of the group order, implying that
369 the overall group was at least the square of the group order. Such a curve
370 would also break our assumption that one can check for membership in the
371 prime order subgroup by multiplying by the group order and checking for the
372 identity.
373 */
374
375 m_mac = m_params.create_mac();
376 m_cipher = m_params.create_cipher(Cipher_Dir::Decryption);
377}
378
379namespace {
380
381size_t compute_point_size(const EC_Group& group, EC_Point_Format format) {
382 const size_t fe_bytes = group.get_p_bytes();
383 if(format == EC_Point_Format::Compressed) {
384 return 1 + fe_bytes;
385 } else {
386 return 1 + 2 * fe_bytes;
387 }
388}
389
390} // namespace
391
392size_t ECIES_Decryptor::plaintext_length(size_t ctext_len) const {
393 const size_t point_size = compute_point_size(m_params.group(), m_params.point_format());
394 const size_t overhead = point_size + m_mac->output_length();
395
396 if(ctext_len < overhead) {
397 return 0;
398 }
399
400 return m_cipher->output_length(ctext_len - overhead);
401}
402
403size_t ECIES_Decryptor::ciphertext_length(size_t ptext_len) const {
404 // We need the encryption direction to estimate the ciphertext length given
405 // an expected plaintext length.
406 auto cipher = m_params.create_cipher(Cipher_Dir::Encryption);
407
408 const size_t point_size = compute_point_size(m_params.group(), m_params.point_format());
409 const size_t mac_size = m_mac->output_length();
410 const size_t ct_size_bound = cipher->output_length(ptext_len);
411 return point_size + mac_size + ct_size_bound;
412}
413
414/**
415* ECIES Decryption according to ISO 18033-2
416*/
417secure_vector<uint8_t> ECIES_Decryptor::do_decrypt(uint8_t& valid_mask, const uint8_t in[], size_t in_len) const {
418 const size_t point_size = compute_point_size(m_params.group(), m_params.point_format());
419
420 if(in_len < point_size + m_mac->output_length()) {
421 throw Decoding_Error("ECIES decryption: ciphertext is too short");
422 }
423
424 if(!m_iv.has_value()) {
425 throw Invalid_State("ECIES requires a fresh IV be provided for each message");
426 }
427 if(m_iv->empty() && !m_cipher->valid_nonce_length(m_iv->size())) {
428 throw Invalid_Argument("ECIES with " + m_cipher->name() + " requires an IV be set");
429 }
430
431 // extract data
432 const std::vector<uint8_t> other_public_key_bin(in, in + point_size); // the received (ephemeral) public key
433 const std::vector<uint8_t> encrypted_data(in + point_size, in + in_len - m_mac->output_length());
434 const std::vector<uint8_t> mac_data(in + in_len - m_mac->output_length(), in + in_len);
435
436 // ISO 18033: step a
437 auto other_public_key = EC_AffinePoint(m_params.group(), other_public_key_bin);
438
439 // ISO 18033: step b would check if other_public_key is on the curve iff check_mode is on
440 // but we ignore this and always check if the point is on the curve
441
442 // ISO 18033: step e (and step f because get_affine_x (called by ECDH_KA_Operation::raw_agree)
443 // throws Illegal_Transformation if the point is zero)
444 const SymmetricKey secret_key = m_ka.derive_secret(other_public_key_bin, other_public_key);
445
446 BOTAN_ASSERT_NOMSG(secret_key.size() == m_params.dem_keylen() + m_params.mac_keylen());
447
448 // validate mac
449 m_mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen());
450 m_mac->update(encrypted_data);
451 if(!m_label.empty()) {
452 m_mac->update(m_label);
453 }
454 const secure_vector<uint8_t> calculated_mac = m_mac->final();
455 valid_mask = CT::is_equal<uint8_t>(mac_data, calculated_mac).value();
456
457 // Consume the IV state even if the message is invalid
458 auto iv = m_iv->bits_of();
459 m_iv.reset();
460
461 if(valid_mask == 0xFF) {
462 // decrypt data
463
464 m_cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen()));
465 m_cipher->start(iv);
466
467 try {
468 // the decryption can fail:
469 // e.g. Invalid_Authentication_Tag is thrown if GCM is used and the message does not have a valid tag
470 secure_vector<uint8_t> decrypted_data(encrypted_data.begin(), encrypted_data.end());
471 m_cipher->finish(decrypted_data);
472 return decrypted_data;
473 } catch(...) {
474 valid_mask = 0;
475 }
476 }
477 return secure_vector<uint8_t>();
478}
479
480} // namespace Botan
#define BOTAN_DIAGNOSTIC_POP
Definition api.h:128
#define BOTAN_DIAGNOSTIC_PUSH
Definition api.h:125
#define BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE
Definition api.h:127
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static std::unique_ptr< Cipher_Mode > create_or_throw(std::string_view algo, Cipher_Dir direction, std::string_view provider="")
ECIES_Decryptor(const PK_Key_Agreement_Key &private_key, const ECIES_System_Params &ecies_params, RandomNumberGenerator &rng)
Definition ecies.cpp:358
ECIES_Encryptor(const PK_Key_Agreement_Key &private_key, const ECIES_System_Params &ecies_params, RandomNumberGenerator &rng)
Definition ecies.cpp:282
ECIES_KA_Operation(const PK_Key_Agreement_Key &private_key, const ECIES_KA_Params &ecies_params, bool for_encryption, RandomNumberGenerator &rng)
Definition ecies.cpp:130
SymmetricKey derive_secret(std::span< const uint8_t > eph_public_key_bin, const EC_AffinePoint &other_public_key_point) const
Definition ecies.cpp:179
bool check_mode() const
Definition ecies.h:136
bool old_cofactor_mode() const
Definition ecies.h:133
ECIES_KA_Params(const EC_Group &group, std::string_view kdf_spec, size_t length, EC_Point_Format point_format=EC_Point_Format::Uncompressed, bool single_hash_mode=true)
Definition ecies.cpp:227
bool cofactor_mode() const
Definition ecies.h:130
bool single_hash_mode() const
Definition ecies.h:127
const EC_Group & group() const
Definition ecies.h:123
const std::string & kdf() const
Definition ecies.h:140
EC_Point_Format point_format() const
Definition ecies.h:138
std::unique_ptr< Cipher_Mode > create_cipher(Cipher_Dir direction) const
creates an instance of the data encryption method
Definition ecies.cpp:275
std::unique_ptr< MessageAuthenticationCode > create_mac() const
creates an instance of the message authentication code
Definition ecies.cpp:271
ECIES_System_Params(const EC_Group &group, std::string_view kdf_spec, std::string_view dem_algo_spec, size_t dem_key_len, std::string_view mac_spec, size_t mac_key_len, EC_Point_Format point_format=EC_Point_Format::Uncompressed, bool single_hash_mode=false)
Definition ecies.cpp:257
bool is_identity() const
Return true if this point is the identity element.
static std::optional< EC_AffinePoint > deserialize(const EC_Group &group, std::span< const uint8_t > bytes)
const BigInt & get_cofactor() const
Definition ec_group.cpp:730
bool has_cofactor() const
Definition ec_group.cpp:734
size_t get_p_bytes() const
Definition ec_group.cpp:658
bool is_zero() const
Definition ec_point.h:170
std::vector< uint8_t > encode(EC_Point_Format format) const
Definition ec_point.cpp:813
static EC_Scalar from_bigint(const EC_Group &group, const BigInt &bn)
Definition ec_scalar.cpp:72
static std::unique_ptr< KDF > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition kdf.cpp:208
static std::unique_ptr< MessageAuthenticationCode > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition mac.cpp:149
const uint8_t * end() const
Definition symkey.h:58
const uint8_t * begin() const
Definition symkey.h:52
virtual size_t maximum_input_size() const =0
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
OctetString SymmetricKey
Definition symkey.h:153
ECIES_Flags
Definition ecies.h:44
@ SingleHashMode
if set: do NOT prefix the input of the (ecdh) key agreement with the encoded (ephemeral) public key
Definition ecies.h:47
PublicKeyOperation
Definition pk_keys.h:46
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128