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