diff options
-rw-r--r-- | doc/credits.rst | 2 | ||||
-rw-r--r-- | src/lib/pubkey/ecies/ecies.cpp | 385 | ||||
-rw-r--r-- | src/lib/pubkey/ecies/ecies.h | 293 | ||||
-rw-r--r-- | src/lib/pubkey/ecies/info.txt | 9 | ||||
-rw-r--r-- | src/tests/data/pubkey/ecies-18033.vec | 66 | ||||
-rw-r--r-- | src/tests/data/pubkey/ecies.vec | 126 | ||||
-rw-r--r-- | src/tests/test_ecies.cpp | 246 |
7 files changed, 1126 insertions, 1 deletions
diff --git a/doc/credits.rst b/doc/credits.rst index 1b97ac3fc..d113fc7de 100644 --- a/doc/credits.rst +++ b/doc/credits.rst @@ -108,5 +108,5 @@ snail-mail address (S), and Bitcoin address (B). N: Philipp Weber W: https://sirrix.com/ - D: KDF1-18033 + D: KDF1-18033, ECIES S: Saarland, Germany diff --git a/src/lib/pubkey/ecies/ecies.cpp b/src/lib/pubkey/ecies/ecies.cpp new file mode 100644 index 000000000..51ba3d172 --- /dev/null +++ b/src/lib/pubkey/ecies/ecies.cpp @@ -0,0 +1,385 @@ +/* +* ECIES +* (C) 2016 Philipp Weber +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/ecies.h> +#include <botan/cipher_mode.h> +#include <botan/pipe.h> + +#include <botan/internal/ct_utils.h> +#include <botan/internal/pk_utils.h> + +namespace Botan { + +namespace { + +/** +* Private key type for ECIES_ECDH_KA_Operation +*/ +class ECIES_PrivateKey : public EC_PrivateKey, public PK_Key_Agreement_Key + { + public: + explicit ECIES_PrivateKey(const ECDH_PrivateKey& private_key) : + EC_PublicKey(private_key), + EC_PrivateKey(private_key), + PK_Key_Agreement_Key(), + m_key(private_key) + { + } + + std::vector<byte> public_value() const override + { + return m_key.public_value(); + } + + std::string algo_name() const override + { + return "ECIES"; + } + + size_t max_input_bits() const override + { + return m_key.max_input_bits(); + } + + private: + ECDH_PrivateKey m_key; + }; + +/** +* Implements ECDH key agreement without using the cofactor mode +*/ +class ECIES_ECDH_KA_Operation : public PK_Ops::Key_Agreement_with_KDF + { + public: + typedef ECIES_PrivateKey Key_Type; + + ECIES_ECDH_KA_Operation(const ECIES_PrivateKey& private_key, const std::string&) : + PK_Ops::Key_Agreement_with_KDF("Raw"), + m_key(private_key) + { + } + + secure_vector<byte> raw_agree(const byte w[], size_t w_len) override + { + const CurveGFp& curve = m_key.domain().get_curve(); + PointGFp point = OS2ECP(w, w_len, curve); + PointGFp S = point * m_key.private_value(); + BOTAN_ASSERT(S.on_the_curve(), "ECDH agreed value was on the curve"); + return BigInt::encode_1363(S.get_affine_x(), curve.get_p().bytes()); + } + + private: + ECIES_PrivateKey m_key; + }; + +/** +* Creates a PK_Key_Agreement instance for the given key and ecies_params +* Returns either ECIES_ECDH_KA_Operation or the default implementation for the given key, +* depending on the key and ecies_params +* @param private_key the private key used for the key agreement +* @param ecies_params settings for ecies +* @param for_encryption disable cofactor mode if the secret will be used for encryption +* (according to ISO 18033 cofactor mode is only used during decryption) +*/ +PK_Key_Agreement create_key_agreement(const PK_Key_Agreement_Key& private_key, const ECIES_KA_Params& ecies_params, + bool for_encryption) + { + const ECDH_PrivateKey* ecdh_key = dynamic_cast<const ECDH_PrivateKey*>(&private_key); + + if(ecdh_key == nullptr && (ecies_params.cofactor_mode() || ecies_params.old_cofactor_mode() + || ecies_params.check_mode())) + { + // assume we have a private key from an external provider (e.g. pkcs#11): + // there is no way to determine or control whether the provider uses cofactor mode or not. + // ISO 18033 does not allow cofactor mode in combination with old cofactor mode or check mode + // => disable cofactor mode, old cofactor mode and check mode for unknown keys/providers (as a precaution). + throw Invalid_Argument("ECIES: cofactor, old cofactor and check mode are only supported for ECDH_PrivateKey"); + } + + if(ecdh_key && (for_encryption || !ecies_params.cofactor_mode())) + { + // ECDH_KA_Operation uses cofactor mode: use own key agreement method if cofactor should not be used. + return PK_Key_Agreement(ECIES_PrivateKey(*ecdh_key), "Raw"); + } + + return PK_Key_Agreement(private_key, "Raw"); // use default implementation + } +} + +BOTAN_REGISTER_PK_KEY_AGREE_OP("ECIES", ECIES_ECDH_KA_Operation); + +ECIES_KA_Operation::ECIES_KA_Operation(const PK_Key_Agreement_Key& private_key, const ECIES_KA_Params& ecies_params, + bool for_encryption) : + m_ka(create_key_agreement(private_key, ecies_params, for_encryption)), + m_params(ecies_params) + { + } + +/** +* ECIES secret derivation according to ISO 18033-2 +*/ +SymmetricKey ECIES_KA_Operation::derive_secret(const std::vector<byte>& eph_public_key_bin, + const PointGFp& other_public_key_point) const + { + if(other_public_key_point.is_zero()) + { + throw Invalid_Argument("ECIES: other public key point is zero"); + } + + std::unique_ptr<KDF> kdf = m_params.create_kdf(); + BOTAN_ASSERT(kdf != nullptr, "KDF is found"); + + PointGFp other_point = other_public_key_point; + + // ISO 18033: step b + if(m_params.old_cofactor_mode()) + { + other_point *= m_params.domain().get_cofactor(); + } + + secure_vector<byte> derivation_input; + + // ISO 18033: encryption step e / decryption step g + if(!m_params.single_hash_mode()) + { + derivation_input += eph_public_key_bin; + } + + // ISO 18033: encryption step f / decryption step h + secure_vector<byte> other_public_key_bin = EC2OSP(other_point, static_cast<byte>(m_params.compression_type())); + // Note: the argument `m_params.secret_length()` passed for `key_len` will only be used by providers because + // "Raw" is passed to the `PK_Key_Agreement` if the implementation of botan is used. + const SymmetricKey peh = m_ka.derive_key(m_params.domain().get_order().bytes(), other_public_key_bin.data(), other_public_key_bin.size()); + derivation_input.insert(derivation_input.end(), peh.begin(), peh.end()); + + // ISO 18033: encryption step g / decryption step i + return kdf->derive_key(m_params.secret_length(), derivation_input); + } + + +ECIES_KA_Params::ECIES_KA_Params(const EC_Group& domain, const std::string& kdf_spec, size_t length, + PointGFp::Compression_Type compression_type, ECIES_Flags flags) : + m_domain(domain), + m_kdf_spec(kdf_spec), + m_length(length), + m_compression_mode(compression_type), + m_flags(flags) + { + } + +std::unique_ptr<KDF> ECIES_KA_Params::create_kdf() const + { + std::unique_ptr<KDF> kdf = Botan::KDF::create(m_kdf_spec); + if(kdf == nullptr) + { + throw Algorithm_Not_Found(m_kdf_spec); + } + return kdf; + } + + +ECIES_System_Params::ECIES_System_Params(const EC_Group& domain, const std::string& kdf_spec, + const std::string& dem_algo_spec, size_t dem_key_len, + const std::string& mac_spec, size_t mac_key_len, + PointGFp::Compression_Type compression_type, ECIES_Flags flags) : + ECIES_KA_Params(domain, kdf_spec, dem_key_len + mac_key_len, compression_type, flags), + m_dem_spec(dem_algo_spec), + m_dem_keylen(dem_key_len), + m_mac_spec(mac_spec), + m_mac_keylen(mac_key_len) + { + // ISO 18033: "At most one of CofactorMode, OldCofactorMode, and CheckMode may be 1." + if(cofactor_mode() + old_cofactor_mode() + check_mode() > 1) + { + throw Invalid_Argument("ECIES: only one of cofactor_mode, old_cofactor_mode and check_mode can be set"); + } + } + +ECIES_System_Params::ECIES_System_Params(const EC_Group& domain, const std::string& kdf_spec, + const std::string& dem_algo_spec, size_t dem_key_len, + const std::string& mac_spec, size_t mac_key_len) : + ECIES_System_Params(domain, kdf_spec, dem_algo_spec, dem_key_len, mac_spec, mac_key_len, PointGFp::UNCOMPRESSED, + ECIES_Flags::NONE) + { + } + +std::unique_ptr<MessageAuthenticationCode> ECIES_System_Params::create_mac() const + { + std::unique_ptr<MessageAuthenticationCode> mac = Botan::MessageAuthenticationCode::create(m_mac_spec); + if(mac == nullptr) + { + throw Algorithm_Not_Found(m_mac_spec); + } + return mac; + } + +std::unique_ptr<Keyed_Filter> ECIES_System_Params::create_cipher(Botan::Cipher_Dir direction) const + { + Keyed_Filter* cipher = get_cipher(m_dem_spec, direction); + if(cipher == nullptr) + { + throw Algorithm_Not_Found(m_dem_spec); + } + return std::unique_ptr<Keyed_Filter>(cipher); + } + + +/* +* ECIES_Encryptor Constructor +*/ +ECIES_Encryptor::ECIES_Encryptor(const PK_Key_Agreement_Key& private_key, const ECIES_System_Params& ecies_params) : + m_ka(private_key, ecies_params, true), + m_params(ecies_params), + m_eph_public_key_bin(private_key.public_value()), // returns the uncompressed public key, see conversion below + m_iv(), + m_other_point(), + m_label() + { + if(ecies_params.compression_type() != PointGFp::UNCOMPRESSED) + { + // ISO 18033: step d + // convert only if necessary; m_eph_public_key_bin has been initialized with the uncompressed format + m_eph_public_key_bin = unlock(EC2OSP(OS2ECP(m_eph_public_key_bin, m_params.domain().get_curve()), + static_cast<byte>(ecies_params.compression_type()))); + } + } + +/* +* ECIES_Encryptor Constructor +*/ +ECIES_Encryptor::ECIES_Encryptor(RandomNumberGenerator& rng, const ECIES_System_Params& ecies_params) : + ECIES_Encryptor(ECDH_PrivateKey(rng, ecies_params.domain()), ecies_params) + { + } + + +/* +* ECIES Encryption according to ISO 18033-2 +*/ +std::vector<byte> ECIES_Encryptor::enc(const byte data[], size_t length, RandomNumberGenerator&) const + { + if(m_other_point.is_zero()) + { + throw Invalid_State("ECIES: the other key is zero"); + } + + const SymmetricKey secret_key = m_ka.derive_secret(m_eph_public_key_bin, m_other_point); + + // encryption + std::unique_ptr<Keyed_Filter> cipher = m_params.create_cipher(ENCRYPTION); + BOTAN_ASSERT(cipher != nullptr, "Cipher is found"); + + cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen())); + if(m_iv.size() != 0) + { + cipher->set_iv(m_iv); + } + Pipe pipe(cipher.release()); + pipe.process_msg(data, length); + const secure_vector<byte> encrypted_data = pipe.read_all(0); + + // concat elements + std::unique_ptr<MessageAuthenticationCode> mac = m_params.create_mac(); + BOTAN_ASSERT(mac != nullptr, "MAC is found"); + + secure_vector<byte> out(m_eph_public_key_bin.size() + encrypted_data.size() + mac->output_length()); + buffer_insert(out, 0, m_eph_public_key_bin); + buffer_insert(out, m_eph_public_key_bin.size(), encrypted_data); + + // mac + mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen()); + mac->update(encrypted_data); + if(!m_label.empty()) + { + mac->update(m_label); + } + mac->final(out.data() + m_eph_public_key_bin.size() + encrypted_data.size()); + + return unlock(out); + } + + +ECIES_Decryptor::ECIES_Decryptor(const PK_Key_Agreement_Key& key, const ECIES_System_Params& ecies_params) : + m_ka(key, ecies_params, false), + m_params(ecies_params), + m_iv(), + m_label() + { + // ISO 18033: "If v > 1 and CheckMode = 0, then we must have gcd(�, v) = 1." (v = index, � = order) + if(!ecies_params.check_mode()) + { + Botan::BigInt cofactor = m_params.domain().get_cofactor(); + if(cofactor > 1 && Botan::gcd(cofactor, m_params.domain().get_order()) != 1) + { + throw Invalid_Argument("ECIES: gcd of cofactor and order must be 1 if check_mode is 0"); + } + } + } + +/** +* ECIES Decryption according to ISO 18033-2 +*/ +secure_vector<byte> ECIES_Decryptor::do_decrypt(byte& valid_mask, const byte in[], size_t in_len) const + { + size_t point_size = m_params.domain().get_curve().get_p().bytes(); + if(m_params.compression_type() != PointGFp::COMPRESSED) + { + point_size *= 2; // uncompressed and hybrid contains x AND y + } + point_size += 1; // format byte + + std::unique_ptr<MessageAuthenticationCode> mac = m_params.create_mac(); + BOTAN_ASSERT(mac != nullptr, "MAC is found"); + + if(in_len < point_size + mac->output_length()) + { + throw Decoding_Error("ECIES decryption: ciphertext is too short"); + } + + // extract data + const std::vector<byte> other_public_key_bin(in, in + point_size); // the received (ephemeral) public key + const std::vector<byte> encrypted_data(in + point_size, in + in_len - mac->output_length()); + const std::vector<byte> mac_data(in + in_len - mac->output_length(), in + in_len); + + // ISO 18033: step a + PointGFp other_public_key = OS2ECP(other_public_key_bin, m_params.domain().get_curve()); + + // ISO 18033: step b + if(m_params.check_mode() && !other_public_key.on_the_curve()) + { + throw Decoding_Error("ECIES decryption: received public key is not on the curve"); + } + + // ISO 18033: step e (and step f because get_affine_x (called by ECDH_KA_Operation::raw_agree) + // throws Illegal_Transformation if the point is zero) + const SymmetricKey secret_key = m_ka.derive_secret(other_public_key_bin, other_public_key); + + // validate mac + mac->set_key(secret_key.begin() + m_params.dem_keylen(), m_params.mac_keylen()); + mac->update(encrypted_data); + if(!m_label.empty()) + { + mac->update(m_label); + } + const secure_vector<byte> calculated_mac = mac->final(); + valid_mask = CT::expand_mask<byte>(same_mem(mac_data.data(), calculated_mac.data(), mac_data.size())); + + // decrypt data + std::unique_ptr<Keyed_Filter> cipher = m_params.create_cipher(DECRYPTION); + BOTAN_ASSERT(cipher != nullptr, "Cipher is found"); + + cipher->set_key(SymmetricKey(secret_key.begin(), m_params.dem_keylen())); + if(m_iv.size() != 0) + { + cipher->set_iv(m_iv); + } + Pipe pipe(cipher.release()); + pipe.process_msg(encrypted_data); + return pipe.read_all(0); + } + +} diff --git a/src/lib/pubkey/ecies/ecies.h b/src/lib/pubkey/ecies/ecies.h new file mode 100644 index 000000000..07937556c --- /dev/null +++ b/src/lib/pubkey/ecies/ecies.h @@ -0,0 +1,293 @@ +/* +* ECIES +* (C) 2016 Philipp Weber +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_ECIES_H__ +#define BOTAN_ECIES_H__ + +#include <botan/ecdh.h> +#include <botan/ec_group.h> +#include <botan/kdf.h> +#include <botan/key_filt.h> +#include <botan/mac.h> +#include <botan/point_gfp.h> +#include <botan/pubkey.h> +#include <botan/secmem.h> +#include <botan/symkey.h> +#include <memory> +#include <string> +#include <vector> + +namespace Botan { + +class RandomNumberGenerator; + +enum class ECIES_Flags : uint32_t + { + NONE = 0, + + /// if set: prefix the input of the (ecdh) key agreement with the encoded (ephemeral) public key + SINGLE_HASH_MODE = 1, + + /// (decryption only) if set: use cofactor multiplication during (ecdh) key agreement + COFACTOR_MODE = 2, + + /// if set: use ecdhc instead of ecdh + OLD_COFACTOR_MODE = 4, + + /// (decryption only) if set: test if the (ephemeral) public key is on the curve + CHECK_MODE = 8 + }; + +inline ECIES_Flags operator |(ECIES_Flags a, ECIES_Flags b) + { + return static_cast<ECIES_Flags>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b)); + } + +inline ECIES_Flags operator &(ECIES_Flags a, ECIES_Flags b) + { + return static_cast<ECIES_Flags>(static_cast<uint32_t>(a) & static_cast<uint32_t>(b)); + } + +/** +* Parameters for ecies secret derivation +*/ +class BOTAN_DLL ECIES_KA_Params + { + public: + /** + * @param domain ec domain parameters of the involved ec keys + * @param kdf_spec name of the key derivation function + * @param length length of the secret to be derived + * @param compression_type format of encoded keys (affects the secret derivation if single_hash_mode is used) + * @param flags options, see documentation of ECIES_Flags + */ + ECIES_KA_Params(const EC_Group& domain, const std::string& kdf_spec, size_t length, + PointGFp::Compression_Type compression_type, ECIES_Flags flags); + + virtual ~ECIES_KA_Params() = default; + + std::unique_ptr<KDF> create_kdf() const; + + inline const EC_Group& domain() const + { + return m_domain; + } + + inline size_t secret_length() const + { + return m_length; + } + + inline bool single_hash_mode() const + { + return (m_flags & ECIES_Flags::SINGLE_HASH_MODE) == ECIES_Flags::SINGLE_HASH_MODE; + } + + inline bool cofactor_mode() const + { + return (m_flags & ECIES_Flags::COFACTOR_MODE) == ECIES_Flags::COFACTOR_MODE; + } + + inline bool old_cofactor_mode() const + { + return (m_flags & ECIES_Flags::OLD_COFACTOR_MODE) == ECIES_Flags::OLD_COFACTOR_MODE; + } + + inline bool check_mode() const + { + return (m_flags & ECIES_Flags::CHECK_MODE) == ECIES_Flags::CHECK_MODE; + } + + inline PointGFp::Compression_Type compression_type() const + { + return m_compression_mode; + } + + private: + const EC_Group m_domain; + const std::string m_kdf_spec; + const size_t m_length; + const PointGFp::Compression_Type m_compression_mode; + const ECIES_Flags m_flags; + }; + + +class BOTAN_DLL ECIES_System_Params : public ECIES_KA_Params + { + public: + /** + * @param domain ec domain parameters of the involved ec keys + * @param kdf_spec name of the key derivation function + * @param dem_algo_spec name of the data encryption method + * @param dem_key_len length of the key used for the data encryption method + * @param mac_spec name of the message authentication code + * @param mac_key_len length of the key used for the message authentication code + * @param compression_type format of encoded keys (affects the secret derivation if single_hash_mode is used) + * @param flags options, see documentation of ECIES_Flags + */ + ECIES_System_Params(const EC_Group& domain, const std::string& kdf_spec, const std::string& dem_algo_spec, + size_t dem_key_len, const std::string& mac_spec, size_t mac_key_len); + + /** + * @param domain ec domain parameters of the involved ec keys + * @param kdf_spec name of the key derivation function + * @param dem_algo_spec name of the data encryption method + * @param dem_key_len length of the key used for the data encryption method + * @param mac_spec name of the message authentication code + * @param mac_key_len length of the key used for the message authentication code + */ + ECIES_System_Params(const EC_Group& domain, const std::string& kdf_spec, const std::string& dem_algo_spec, + size_t dem_key_len, const std::string& mac_spec, size_t mac_key_len, + PointGFp::Compression_Type compression_type, ECIES_Flags flags); + + virtual ~ECIES_System_Params() = default; + + /// creates an instance of the message authentication code + std::unique_ptr<MessageAuthenticationCode> create_mac() const; + + /// creates an instance of the data encryption method + std::unique_ptr<Keyed_Filter> create_cipher(Botan::Cipher_Dir direction) const; + + /// returns the length of the key used by the data encryption method + inline size_t dem_keylen() const + { + return m_dem_keylen; + } + + /// returns the length of the key used by the message authentication code + inline size_t mac_keylen() const + { + return m_mac_keylen; + } + + private: + const std::string m_dem_spec; + const size_t m_dem_keylen; + const std::string m_mac_spec; + const size_t m_mac_keylen; + }; + + +/** +* ECIES secret derivation according to ISO 18033-2 +*/ +class BOTAN_DLL ECIES_KA_Operation + { + public: + /** + * @param private_key the (ephemeral) private key which is used to derive the secret + * @param ecies_params settings for ecies + * @param for_encryption disable cofactor mode if the secret will be used for encryption + * (according to ISO 18033 cofactor mode is only used during decryption) + */ + ECIES_KA_Operation(const PK_Key_Agreement_Key& private_key, const ECIES_KA_Params& ecies_params, + bool for_encryption); + + /** + * Performs a key agreement with the provided keys and derives the secret from the result + * @param eph_public_key_bin the encoded (ephemeral) public key which belongs to the used (ephemeral) private key + * @param other_public_key_point public key point of the other party + */ + SymmetricKey derive_secret(const std::vector<byte>& eph_public_key_bin, + const PointGFp& other_public_key_point) const; + + private: + const PK_Key_Agreement m_ka; + const ECIES_KA_Params m_params; + }; + + +/** +* ECIES Encryption according to ISO 18033-2 +*/ +class BOTAN_DLL ECIES_Encryptor : public PK_Encryptor + { + public: + /** + * @param private_key the (ephemeral) private key which is used for the key agreement + * @param ecies_params settings for ecies + */ + ECIES_Encryptor(const PK_Key_Agreement_Key& private_key, const ECIES_System_Params& ecies_params); + + /** + * Creates an ephemeral private key which is used for the key agreement + * @param rng random generator used during private key generation + * @param ecies_params settings for ecies + */ + ECIES_Encryptor(RandomNumberGenerator& rng, const ECIES_System_Params& ecies_params); + + /// Set the public key of the other party + inline void set_other_key(const Botan::PointGFp& public_point) + { + m_other_point = public_point; + } + + /// Set the initialization vector for the data encryption method + inline void set_initialization_vector(const InitializationVector& iv) + { + m_iv = iv; + } + + /// Set the label which is appended to the input for the message authentication code + inline void set_label(const std::string& label) + { + m_label = std::vector<byte>(label.begin(), label.end()); + } + + private: + std::vector<byte> enc(const byte data[], size_t length, RandomNumberGenerator&) const override; + + inline size_t maximum_input_size() const override + { + return std::numeric_limits<size_t>::max(); + } + + const ECIES_KA_Operation m_ka; + const ECIES_System_Params m_params; + std::vector<byte> m_eph_public_key_bin; + InitializationVector m_iv; + PointGFp m_other_point; + std::vector<byte> m_label; + }; + + +/** +* ECIES Decryption according to ISO 18033-2 +*/ +class BOTAN_DLL ECIES_Decryptor : public PK_Decryptor + { + public: + /** + * @param private_key the private key which is used for the key agreement + * @param ecies_params settings for ecies + */ + ECIES_Decryptor(const PK_Key_Agreement_Key& private_key, const ECIES_System_Params& ecies_params); + + /// Set the initialization vector for the data encryption method + inline void set_initialization_vector(const InitializationVector& iv) + { + m_iv = iv; + } + + /// Set the label which is appended to the input for the message authentication code + inline void set_label(const std::string& label) + { + m_label = std::vector<byte>(label.begin(), label.end()); + } + + private: + secure_vector<byte> do_decrypt(byte& valid_mask, const byte in[], size_t in_len) const; + + const ECIES_KA_Operation m_ka; + const ECIES_System_Params m_params; + InitializationVector m_iv; + std::vector<byte> m_label; + }; + +} + +#endif diff --git a/src/lib/pubkey/ecies/info.txt b/src/lib/pubkey/ecies/info.txt new file mode 100644 index 000000000..dacefc88a --- /dev/null +++ b/src/lib/pubkey/ecies/info.txt @@ -0,0 +1,9 @@ +define ECIES 20160128 + +<requires> +kdf +mac +ecdh +modes +filters +</requires>
\ No newline at end of file diff --git a/src/tests/data/pubkey/ecies-18033.vec b/src/tests/data/pubkey/ecies-18033.vec new file mode 100644 index 000000000..8937abd2c --- /dev/null +++ b/src/tests/data/pubkey/ecies-18033.vec @@ -0,0 +1,66 @@ +# ISO/IEC 18033-2 2006 +# ECIES-KEM test vectors for ECModp-Group + +# ---------------------------------------------------------------------------------------------------- + +# C.2.2 +# Kdf=Kdf1(Hash=Sha1()) +# Keylen=128 +# CofactorMode=0 +# OldCofactorMode=0 +# CheckMode=0 +# SingleHashMode=0 + +format = uncompressed + +p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff +a = 0xfffffffffffffffffffffffffffffffefffffffffffffffc +b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 +mu = 0xffffffffffffffffffffffff99def836146bc9b1b4d22831 +nu = 0x01 +gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 +gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + +# Public Key +hx = 0x1cbc74a41b4e84a1509f935e2328a0bb06104d8dbb8d2130 +hy = 0x7b2ab1f10d76fde1ea046a4ad5fb903734190151bb30cec2 + +# Private Key +x = 0xb67048c28d2d26a73f713d5ebb994ac92588464e7fe7d3f3 + +# Encoding format = uncompressed_fmt +r = 0x083d4ac64f1960a9836a84f91ca211a185814fa43a2c8f21 +C0 = 04ccc9ea07b8b71d25646b22b0e251362a3fa9e993042315df047b2e07dd2ffb89359945f3d22ca8757874be2536e0f924 +K = 9a709adeb6c7590ccfc7d594670dd2d74fcdda3f8622f2dbcf0f0c02966d5d9002db578c989bf4a5cc896d2a11d74e0c51efc1f8ee784897ab9b865a7232b5661b7cac87cf4150bdf23b015d7b525b797cf6d533e9f6ad49a4c6de5e7089724c9cadf0adf13ee51b41be6713653fc1cb2c95a1d1b771cc7429189861d7a829f3 + +# ---------------------------------------------------------------------------------------------------- + +# C.2.3 +# Kdf=Kdf1(Hash=Sha1()) +# Keylen=128 +# CofactorMode=0 +# OldCofactorMode=0 +# CheckMode=0 +# SingleHashMode=0 + +format = compressed + +p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff +a = 0xfffffffffffffffffffffffffffffffefffffffffffffffc +b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 +mu = 0xffffffffffffffffffffffff99def836146bc9b1b4d22831 +nu = 0x01 +gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 +gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + +# Public Key +hx = 0x1cbc74a41b4e84a1509f935e2328a0bb06104d8dbb8d2130 +hy = 0x7b2ab1f10d76fde1ea046a4ad5fb903734190151bb30cec2 + +# Private Key +x = 0xb67048c28d2d26a73f713d5ebb994ac92588464e7fe7d3f3 + +# Encoding format = compressed_fmt +r = 0x083d4ac64f1960a9836a84f91ca211a185814fa43a2c8f21 +C0 = 02ccc9ea07b8b71d25646b22b0e251362a3fa9e993042315df +K = 8fbe0903fac2fa05df02278fe162708fb432f3cbf9bb14138d22be1d279f74bfb94f0843a153b708fcc8d9446c76f00e4ccabef85228195f732f4aedc5e48efcf2968c3a46f2df6f2afcbdf5ef79c958f233c6d208f3a7496e08f505d1c792b314b45ff647237b0aa186d0cdbab47a00fb4065d62cfc18f8a8d12c78ecbee3fd diff --git a/src/tests/data/pubkey/ecies.vec b/src/tests/data/pubkey/ecies.vec new file mode 100644 index 000000000..fcf0baf62 --- /dev/null +++ b/src/tests/data/pubkey/ecies.vec @@ -0,0 +1,126 @@ +# random keys created by botan + +# ciphertext created with bouncycastle 1.54. example: +# public static void main( String[] args ) +# throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidCipherTextException +# { +# X9ECParameters spec = SECNamedCurves.getByName( "secp160r1" ); +# ECDomainParameters ecDomain = new ECDomainParameters( spec.getCurve(), spec.getG(), spec.getN() ); +# +# ECPrivateKeyParameters alice = new ECPrivateKeyParameters( new BigInteger( "1239488582848888730519239446720775754920686817364", 10 ), ecDomain ); +# ECPrivateKeyParameters bob = new ECPrivateKeyParameters( new BigInteger( "1255825134563225934367124570783723166851629196761", 10 ), ecDomain ); +# ECPublicKeyParameters alicePublicKey = new ECPublicKeyParameters( alice.getParameters().getG().multiply( alice.getD() ), alice.getParameters() ); +# ECPublicKeyParameters bobPublicKey = new ECPublicKeyParameters( bob.getParameters().getG().multiply( bob.getD() ), bob.getParameters() ); +# +# byte[] d = new byte[ 0 ]; +# byte[] e = new byte[ 0 ]; +# byte[] iv = new byte[ 16 ]; +# CipherParameters p = new ParametersWithIV( new IESWithCipherParameters( d, e, 160, 256 ), iv ); +# +# IESEngine ecies = +# new IESEngine( new ECDHBasicAgreement(), new KDF2BytesGenerator( new SHA1Digest() ), new HMac( new SHA256Digest() ), new PaddedBufferedBlockCipher( new CBCBlockCipher( +# new AESEngine() ) ) ); +# ecies.init( true, alice, bobPublicKey, p ); +# +# byte[] message = Hex.decode( "00" ); +# byte[] result = ecies.processBlock( message, 0, message.length ); +# +# byte[] ephPublicKey = alicePublicKey.getQ().getEncoded( true ); +# byte[] out = Arrays.concatenate( ephPublicKey, result ); +# +# System.out.println( Hex.toHexString( out ) ); +# } + +Curve = secp160r1 +PrivateKey = 1239488582848888730519239446720775754920686817364 +OtherPrivateKey = 1255825134563225934367124570783723166851629196761 +Kdf = KDF2(SHA-1) +Dem = AES-256/CBC +DemKeyLen = 32 +Iv = 00000000000000000000000000000000 +Mac = HMAC(SHA-256) +MacKeyLen = 20 +Format = compressed +CofactorMode = 0 +OldCofactorMode = 0 +CheckMode = 0 +SingleHashMode = 1 +Label = +Plaintext = 00 +Ciphertext = 02b26eafa6b51a39790c32a75c2f10b3e8e89d698a6da2667af153734225c8922800db5e10b73975848cceac0fc78cef589b2e93a81cc204dbc7b9b901cbaa4509e61141d7 + +Curve = secp521r1 +PrivateKey = 4050298667054381376040649773970530311598264897556821662677634075002761777100287880684822948852132235484464537021197213998300006547176718172344447619746779823 +OtherPrivateKey = 2294226772740614508941417891614236736606752960073669253551166842586609531509032791476032516821966982891507407145617606630445744825404691681749451640151380153 +Kdf = KDF2(SHA-1) +Dem = Camellia-128/CBC +DemKeyLen = 16 +Iv = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +Mac = HMAC(SHA-256) +MacKeyLen = 16 +Format = uncompressed +CofactorMode = 0 +OldCofactorMode = 0 +CheckMode = 0 +SingleHashMode = 1 +Label = Test +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 0401519eaa0489ff9d51e98e4c22349463e2001cd06f8ce47d81d4007a79acf98e92c814686477cea666efc277dc84e15fc95e38aff8e16d478a44cd5c5f1517f8b1f300000591317f261c3d04a7207f01eae3ec70f23600f82c53cc0b85be7ac9f6ce79ef2ab416e5934d61ba9d346385d7545c57f77c7ea7c58e18c70cbfb0a24ae1b994eda8dbc666713558717077dde021d9252b7f68eef0bc369086f6a6cb991fcc2fbcac3671a122ba18541790974cef7420cb53e7d6f30d1b808dddd58a63413f7b + +# use secp112r2 - curve with cofactor != 1 +Curve = -----BEGIN EC PARAMETERS-----MHMCAQEwGgYHKoZIzj0BAQIPANt8Kr9i415mgHa+rSCLMCAEDmEnwkwF84oKqvZcDvAsBA5R3vGBXbXtdPzDTIXXCQQdBEujCrXokrThZJ3QkoZDrc1G9YguN0fe826VbpcCDjbfCq/YuNdZfKEFINBLAgEE-----END EC PARAMETERS----- +PrivateKey = 656008468895526658474428975817604 +OtherPrivateKey = 563449446384594847151017584539074 +Kdf = KDF2(SHA-1) +Dem = Camellia-128/CBC +DemKeyLen = 16 +Iv = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +Mac = HMAC(SHA-256) +MacKeyLen = 16 +Format = uncompressed +CofactorMode = 0 +OldCofactorMode = 1 +CheckMode = 0 +SingleHashMode = 1 +Label = Test +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 048c40bda0986dadeb651178b4a8e64b7735fb02f43e621151849ea761a0f79fbb500b76e4eb9cd65281b804406536d04059b60689ed286490afcbf8f7f32dfefff8d37d29d335cb11aef3cc5d65f87571e3c8799974038f9d377a2683 + +# use secp112r2 - curve with cofactor != 1 +Curve = -----BEGIN EC PARAMETERS-----MHMCAQEwGgYHKoZIzj0BAQIPANt8Kr9i415mgHa+rSCLMCAEDmEnwkwF84oKqvZcDvAsBA5R3vGBXbXtdPzDTIXXCQQdBEujCrXokrThZJ3QkoZDrc1G9YguN0fe826VbpcCDjbfCq/YuNdZfKEFINBLAgEE-----END EC PARAMETERS----- +PrivateKey = 656008468895526658474428975817604 +OtherPrivateKey = 563449446384594847151017584539074 +Kdf = KDF2(SHA-1) +Dem = Camellia-128/CBC +DemKeyLen = 16 +Iv = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +Mac = HMAC(SHA-256) +MacKeyLen = 16 +Format = uncompressed +CofactorMode = 1 +OldCofactorMode = 0 +CheckMode = 0 +SingleHashMode = 1 +Label = Test +Plaintext = 000102030405060708090A0B0C0D0E0F +Ciphertext = 048c40bda0986dadeb651178b4a8e64b7735fb02f43e621151849ea761230f2bddf1ffa3262673bcb3f468dd8b92c31a32e23935cfd27dfcc123928a18bbc82bdcada733be6d42119d3fb968ac4b77fff9a47d336fa025bfad3ee54286 + +# bouncycastle does not support aead ciphers with IESEngine -> empty ciphertext; the test suite asserts that the plaintext can be encrypted and decrypted properly + +Curve = brainpool512r1 +PrivateKey = 7978796978847894400103470063598909318992754342406974939475470191530421638356103244921001321651015274653183103561457607601257178840534133802655904526250737 +OtherPrivateKey = 2308129338363763325603164530220543667351108423592731601992535938718831256964324847657313285466745344259451280420400800014583532495130674675477133156417282 +Kdf = KDF2(SHA-1) +Dem = Twofish/GCM +DemKeyLen = 32 +Iv = 00000000000000000000000000000000 +Mac = HMAC(SHA-512) +MacKeyLen = 64 +Format = compressed +CofactorMode = 0 +OldCofactorMode = 0 +CheckMode = 0 +SingleHashMode = 0 +Label = Test +Plaintext = 00 +Ciphertext = diff --git a/src/tests/test_ecies.cpp b/src/tests/test_ecies.cpp new file mode 100644 index 000000000..2fba6c93f --- /dev/null +++ b/src/tests/test_ecies.cpp @@ -0,0 +1,246 @@ +/* +* (C) 2016 Philipp Weber +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + + +#if defined(BOTAN_HAS_ECIES) + #include "test_pubkey.h" + #include <botan/ecies.h> + #include <botan/ecdh.h> + #include <botan/auto_rng.h> +#endif + +namespace Botan_Tests { + +namespace { + +#if defined(BOTAN_HAS_ECIES) + +using byte = Botan::byte; +using Flags = Botan::ECIES_Flags; + +Botan::PointGFp::Compression_Type get_compression_type(const std::string& format) + { + if(format == "uncompressed") + { + return Botan::PointGFp::UNCOMPRESSED; + } + else if(format == "compressed") + { + return Botan::PointGFp::COMPRESSED; + } + else if(format == "hybrid") + { + return Botan::PointGFp::HYBRID; + } + throw Botan::Invalid_Argument("invalid compression format"); + } + +Flags ecies_flags(bool cofactor_mode, bool old_cofactor_mode, bool check_mode, bool single_hash_mode) + { + return (cofactor_mode ? Flags::COFACTOR_MODE : Flags::NONE) + | (single_hash_mode ? Flags::SINGLE_HASH_MODE : Flags::NONE) + | (old_cofactor_mode ? Flags::OLD_COFACTOR_MODE : Flags::NONE) + | (check_mode ? Flags::CHECK_MODE : Flags::NONE); + } + +void check_encrypt_decrypt(Test::Result& result, const Botan::ECDH_PrivateKey& private_key, + const Botan::ECDH_PrivateKey& other_private_key, + const Botan::ECIES_System_Params& ecies_params, + const Botan::InitializationVector& iv, const std::string& label, + const std::vector<byte>& plaintext, const std::vector<byte>& ciphertext) + { + Botan::ECIES_Encryptor ecies_enc(private_key, ecies_params); + ecies_enc.set_other_key(other_private_key.public_point()); + Botan::ECIES_Decryptor ecies_dec(other_private_key, ecies_params); + if(!iv.bits_of().empty()) + { + ecies_enc.set_initialization_vector(iv); + ecies_dec.set_initialization_vector(iv); + } + if(!label.empty()) + { + ecies_enc.set_label(label); + ecies_dec.set_label(label); + } + + try + { + const std::vector<byte> encrypted = ecies_enc.encrypt(plaintext, Test::rng()); + if(!ciphertext.empty()) + { + result.test_eq("encrypted data", encrypted, ciphertext); + } + const Botan::secure_vector<byte> decrypted = ecies_dec.decrypt(encrypted); + result.test_eq("decrypted data equals plaintext", decrypted, plaintext); + } + catch(Botan::Lookup_Error& e) + { + result.test_note(std::string("Test not executed: ") + e.what()); + } + } + +void check_encrypt_decrypt(Test::Result& result, const Botan::ECDH_PrivateKey& private_key, + const Botan::ECDH_PrivateKey& other_private_key, + const Botan::ECIES_System_Params& ecies_params, size_t iv_length = 0) + { + static std::vector<byte> Plaintext { 1, 2, 3 }; + check_encrypt_decrypt(result, private_key, other_private_key, ecies_params, std::vector<byte>(iv_length, 0), "", + Plaintext, std::vector<byte>()); + } + +class ECIES_ISO_Tests : public Text_Based_Test + { + public: + ECIES_ISO_Tests() : Text_Based_Test( + "pubkey/ecies-18033.vec", + { "format", "p", "a", "b", "mu", "nu", "gx", "gy", "hx", "hy", "x", "r", "C0", "K" }) + { + } + + Test::Result run_one_test(const std::string&, const VarMap& vars) override + { + Test::Result result("ECIES-ISO"); + + // get test vectors defined by ISO 18033 + const Botan::PointGFp::Compression_Type compression_type = get_compression_type(get_req_str(vars, "format")); + const Botan::BigInt p = get_req_bn(vars, "p"); + const Botan::BigInt a = get_req_bn(vars, "a"); + const Botan::BigInt b = get_req_bn(vars, "b"); + const Botan::BigInt mu = get_req_bn(vars, "mu"); // order + const Botan::BigInt nu = get_req_bn(vars, "nu"); // cofactor + const Botan::BigInt gx = get_req_bn(vars, "gx"); // base point x + const Botan::BigInt gy = get_req_bn(vars, "gy"); // base point y + const Botan::BigInt hx = get_req_bn(vars, "hx"); // x of public point of bob + const Botan::BigInt hy = get_req_bn(vars, "hy"); // y of public point of bob + const Botan::BigInt x = get_req_bn(vars, "x"); // private key of bob + const Botan::BigInt r = get_req_bn(vars, "r"); // (ephemeral) private key of alice + const std::vector<byte> c0 = get_req_bin(vars, "C0"); // expected encoded (ephemeral) public key + const std::vector<byte> k = get_req_bin(vars, "K"); // expected derived secret + + const Botan::CurveGFp curve(p, a, b); + const Botan::EC_Group domain(curve, Botan::PointGFp(curve, gx, gy), mu, nu); + + // keys of bob + const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, x); + const Botan::PointGFp other_public_key_point(curve, hx, hy); + const Botan::ECDH_PublicKey other_public_key(domain, other_public_key_point); + + // (ephemeral) keys of alice + const Botan::ECDH_PrivateKey eph_private_key(Test::rng(), domain, r); + const Botan::PointGFp eph_public_key_point = eph_private_key.public_point(); + const std::vector<byte> eph_public_key_bin = Botan::unlock( + Botan::EC2OSP(eph_public_key_point, compression_type)); + result.test_eq("encoded (ephemeral) public key", eph_public_key_bin, c0); + + // test secret derivation: ISO 18033 test vectors use KDF1 from ISO 18033 + // no cofactor-/oldcofactor-/singlehash-/check-mode and 128 byte secret length + Botan::ECIES_KA_Params ka_params(eph_private_key.domain(), "KDF1-18033(SHA-1)", 128, compression_type, + Flags::NONE); + const Botan::ECIES_KA_Operation ka(eph_private_key, ka_params, true); + const Botan::SymmetricKey secret_key = ka.derive_secret(eph_public_key_bin, other_public_key_point); + result.test_eq("derived secret key", secret_key.bits_of(), k); + + // test encryption / decryption + for(int i_cofactor_mode = 0; i_cofactor_mode < 2; ++i_cofactor_mode) + { + for(int i_single_hash_mode = 0; i_single_hash_mode < 2; ++i_single_hash_mode) + { + for(int i_old_cofactor_mode = 0; i_old_cofactor_mode < 2; ++i_old_cofactor_mode) + { + for(int i_check_mode = 0; i_check_mode < 2; ++i_check_mode) + { + for(int i_compression_type = 0; i_compression_type < 3; ++i_compression_type) + { + const bool cofactor_mode = i_cofactor_mode != 0; + const bool single_hash_mode = i_single_hash_mode != 0; + const bool old_cofactor_mode = i_old_cofactor_mode != 0; + const bool check_mode = i_check_mode != 0; + const Botan::PointGFp::Compression_Type compression_type = + static_cast<Botan::PointGFp::Compression_Type>(i_compression_type); + + Flags flags = ecies_flags(cofactor_mode, old_cofactor_mode, check_mode, single_hash_mode); + + if(cofactor_mode + check_mode + old_cofactor_mode > 1) + { + result.test_throws("throw on invalid ECIES_Flags", [&] + { + Botan::ECIES_System_Params(eph_private_key.domain(), "KDF2(SHA-1)", "AES-256/CBC", + 32, "HMAC(SHA-1)", 20, compression_type, flags); + }); + continue; + } + + Botan::ECIES_System_Params ecies_params(eph_private_key.domain(), "KDF2(SHA-1)", "AES-256/CBC", + 32, "HMAC(SHA-1)", 20, compression_type, flags); + check_encrypt_decrypt(result, eph_private_key, other_private_key, ecies_params, 16); + } + } + } + } + } + + return result; + } + }; + +BOTAN_REGISTER_TEST("ecies-iso", ECIES_ISO_Tests); + + +class ECIES_Tests : public Text_Based_Test + { + public: + ECIES_Tests() : Text_Based_Test( + "pubkey/ecies.vec", + { "Curve", "PrivateKey", "OtherPrivateKey", "Kdf", "Dem", "DemKeyLen", "Iv", "Mac", "MacKeyLen", "Format", + "CofactorMode", "OldCofactorMode", "CheckMode", "SingleHashMode", "Label", "Plaintext", "Ciphertext" }) + { + } + + Test::Result run_one_test(const std::string&, const VarMap& vars) override + { + Test::Result result("ECIES"); + + const std::string curve = get_req_str(vars, "Curve"); + const Botan::BigInt private_key_value = get_req_bn(vars, "PrivateKey"); + const Botan::BigInt other_private_key_value = get_req_bn(vars, "OtherPrivateKey"); + const std::string kdf = get_req_str(vars, "Kdf"); + const std::string dem = get_req_str(vars, "Dem"); + const size_t dem_key_len = get_req_sz(vars, "DemKeyLen"); + const std::vector<byte> iv = get_req_bin(vars, "Iv"); + const std::string mac = get_req_str(vars, "Mac"); + const size_t mac_key_len = get_req_sz(vars, "MacKeyLen"); + const Botan::PointGFp::Compression_Type compression_type = get_compression_type(get_req_str(vars, "Format")); + const bool cofactor_mode = get_req_sz(vars, "CofactorMode") != 0; + const bool old_cofactor_mode = get_req_sz(vars, "OldCofactorMode") != 0; + const bool check_mode = get_req_sz(vars, "CheckMode") != 0; + const bool single_hash_mode = get_req_sz(vars, "SingleHashMode") != 0; + const std::string label = get_req_str(vars, "Label"); + const std::vector<byte> plaintext = get_req_bin(vars, "Plaintext"); + const std::vector<byte> ciphertext = get_req_bin(vars, "Ciphertext"); + + const Flags flags = ecies_flags(cofactor_mode, old_cofactor_mode, check_mode, single_hash_mode); + const Botan::EC_Group domain(curve); + const Botan::ECDH_PrivateKey private_key(Test::rng(), domain, private_key_value); + const Botan::ECDH_PrivateKey other_private_key(Test::rng(), domain, other_private_key_value); + + const Botan::ECIES_System_Params ecies_params(private_key.domain(), kdf, dem, dem_key_len, mac, mac_key_len, + compression_type, flags); + check_encrypt_decrypt(result, private_key, other_private_key, ecies_params, iv, label, plaintext, ciphertext); + + return result; + } + + }; + +BOTAN_REGISTER_TEST("ecies", ECIES_Tests); + +#endif + +} + +} |