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