Botan 3.11.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
326 const SymmetricKey secret_key = m_ka.derive_secret(m_eph_public_key_bin, m_other_point.value());
327
328 // encryption
329
330 m_cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen()));
331 if(m_iv.empty() && !m_cipher->valid_nonce_length(m_iv.size())) {
332 throw Invalid_Argument("ECIES with " + m_cipher->name() + " requires an IV be set");
333 }
334
335 m_cipher->start(m_iv.bits_of());
336
337 secure_vector<uint8_t> encrypted_data(data, data + length);
338 m_cipher->finish(encrypted_data);
339
340 // compute the MAC
341 m_mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen());
342 m_mac->update(encrypted_data);
343 if(!m_label.empty()) {
344 m_mac->update(m_label);
345 }
346 const auto mac = m_mac->final();
347
348 // concat elements
349 return concat(m_eph_public_key_bin, encrypted_data, mac);
350}
351
353 const ECIES_System_Params& ecies_params,
355 m_ka(key, ecies_params, false, rng), m_params(ecies_params) {
356 /*
357 ISO 18033: "If v > 1 and CheckMode = 0, then we must have gcd(u, v) = 1." (v = index, u= order)
358
359 We skip this check because even if CheckMode = 0 we actually do check that
360 the point is valid. In addition the check from ISO 18033 is pretty odd; u is
361 the _prime_ order subgroup, and v is the cofactor. For gcd(u, v) > 1 to occur
362 the cofactor would have to be a multiple of the group order, implying that
363 the overall group was at least the square of the group order. Such a curve
364 would also break our assumption that one can check for membership in the
365 prime order subgroup by multiplying by the group order and checking for the
366 identity.
367 */
368
369 m_mac = m_params.create_mac();
370 m_cipher = m_params.create_cipher(Cipher_Dir::Decryption);
371}
372
373namespace {
374
375size_t compute_point_size(const EC_Group& group, EC_Point_Format format) {
376 const size_t fe_bytes = group.get_p_bytes();
377 if(format == EC_Point_Format::Compressed) {
378 return 1 + fe_bytes;
379 } else {
380 return 1 + 2 * fe_bytes;
381 }
382}
383
384} // namespace
385
386size_t ECIES_Decryptor::plaintext_length(size_t ctext_len) const {
387 const size_t point_size = compute_point_size(m_params.group(), m_params.point_format());
388 const size_t overhead = point_size + m_mac->output_length();
389
390 if(ctext_len < overhead) {
391 return 0;
392 }
393
394 return m_cipher->output_length(ctext_len - overhead);
395}
396
397/**
398* ECIES Decryption according to ISO 18033-2
399*/
400secure_vector<uint8_t> ECIES_Decryptor::do_decrypt(uint8_t& valid_mask, const uint8_t in[], size_t in_len) const {
401 const size_t point_size = compute_point_size(m_params.group(), m_params.point_format());
402
403 if(in_len < point_size + m_mac->output_length()) {
404 throw Decoding_Error("ECIES decryption: ciphertext is too short");
405 }
406
407 // extract data
408 const std::vector<uint8_t> other_public_key_bin(in, in + point_size); // the received (ephemeral) public key
409 const std::vector<uint8_t> encrypted_data(in + point_size, in + in_len - m_mac->output_length());
410 const std::vector<uint8_t> mac_data(in + in_len - m_mac->output_length(), in + in_len);
411
412 // ISO 18033: step a
413 auto other_public_key = EC_AffinePoint(m_params.group(), other_public_key_bin);
414
415 // ISO 18033: step b would check if other_public_key is on the curve iff check_mode is on
416 // but we ignore this and always check if the point is on the curve
417
418 // ISO 18033: step e (and step f because get_affine_x (called by ECDH_KA_Operation::raw_agree)
419 // throws Illegal_Transformation if the point is zero)
420 const SymmetricKey secret_key = m_ka.derive_secret(other_public_key_bin, other_public_key);
421
422 // validate mac
423 m_mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen());
424 m_mac->update(encrypted_data);
425 if(!m_label.empty()) {
426 m_mac->update(m_label);
427 }
428 const secure_vector<uint8_t> calculated_mac = m_mac->final();
429 valid_mask = CT::is_equal<uint8_t>(mac_data, calculated_mac).value();
430
431 if(valid_mask == 0xFF) {
432 // decrypt data
433
434 m_cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen()));
435 if(m_iv.empty() && !m_cipher->valid_nonce_length(m_iv.size())) {
436 throw Invalid_Argument("ECIES with " + m_cipher->name() + " requires an IV be set");
437 }
438 m_cipher->start(m_iv.bits_of());
439
440 try {
441 // the decryption can fail:
442 // e.g. Invalid_Authentication_Tag is thrown if GCM is used and the message does not have a valid tag
443 secure_vector<uint8_t> decrypted_data(encrypted_data.begin(), encrypted_data.end());
444 m_cipher->finish(decrypted_data);
445 return decrypted_data;
446 } catch(...) {
447 valid_mask = 0;
448 }
449 }
450 return secure_vector<uint8_t>();
451}
452
453} // namespace Botan
#define BOTAN_DIAGNOSTIC_POP
Definition api.h:122
#define BOTAN_DIAGNOSTIC_PUSH
Definition api.h:119
#define BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE
Definition api.h:121
#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:352
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:132
bool old_cofactor_mode() const
Definition ecies.h:129
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:126
bool single_hash_mode() const
Definition ecies.h:123
const EC_Group & group() const
Definition ecies.h:119
const std::string & kdf() const
Definition ecies.h:136
EC_Point_Format point_format() const
Definition ecies.h:134
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:669
bool has_cofactor() const
Definition ec_group.cpp:673
size_t get_p_bytes() const
Definition ec_group.cpp:597
bool is_zero() const
Definition ec_point.h:163
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:69
static std::unique_ptr< KDF > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition kdf.cpp:204
static std::unique_ptr< MessageAuthenticationCode > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition mac.cpp:147
const uint8_t * end() const
Definition symkey.h:46
const uint8_t * begin() const
Definition symkey.h:41
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:140
ECIES_Flags
Definition ecies.h:44
@ SingleHashMode
if set: 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:68