The process of ECC is quite different from that of RSA. ECC involves many additional concepts, such as group. In addition, ECC includes two completely different mechanisms, namely, ECDSA and ecdh, these two sets of mechanisms are unified in ECC. In non-ECC algorithms, these two sets of mechanisms are implemented by two independent algorithms, for example, RSA and DSA are used for encryption, decryption, and signature/verification. DH is used for key negotiation, how to unify these two types of algorithms into a single structure is a problem that needs to be solved by frameworks such as OpenSSL. OpenSSL puts all the information about the "key" into a single structure, this struct is the evp_pkey. The information includes the key itself and operations on these keys. The most important of these operations is operations such as key generation and signature verification, since key operations are just some standards, various organizations can customize their own implementations, so the role of the engine is reflected, according to the OO point of view, it is a good design to encapsulate the engine into the key, because almost all encryption and decryption signature verification operations are The engine is centered around keys, so almost all engines are in key-related structures. In addition, the engine provides a mechanism for symmetric algorithms. For details, see the previous article.
Struct evp_pkey_st
{
Int type;
Int save_type;
Int references;
Union {
Char * PTR;
# Ifndef openssl_no_rsa
Struct rsa_st * RSA;/* RSA */
# Endif
...
# Ifndef openssl_no_ec
Struct ec_key_st * EC;/* ECC */
# Endif
} Pkey;
Int save_parameters;
Stack_of (x509_attribute) * attributes;/* [0] */
...
}
For RSA, the following rsa_st can accommodate all data and operations, including public keys, private keys, sign, verify, and so on:
Struct rsa_st
{
Int pad;
Long version;
Const rsa_method * meth; // method that can be overloaded. All RSA operations are in this meth.
/* Functional reference if 'meth' is engine-provided */
Engine * engine; // The current engine, which contains an rsa_methed internally. This methed can overload the above meth.
Bignum * N;
Bignum * E;
Bignum * D;
Bignum * P;
Bignum * q;
Bignum * DMP1;
Bignum * dmq1;
Bignum * iqmp;
...
}
But it is not that simple for ECC, although it also has an ec_key_st structure similar to rsa_st, obviously, this struct stores information about the key, the key itself, and key operations. However, this struct serves two mechanisms, this is the aforementioned key negotiation algorithm and public/private key algorithm (or signature algorithm, because ECC's ECDSA is almost not used for encryption/decryption and digital envelope ), A feasible but unreasonable way of organizing is to put all the operation functions in these two types of algorithms into a struct, such as ecc_methed_st, and then add the struct to the engine structure, although the practice similar to RSA seems simple, it is unreasonable because the relationship between the signature algorithm and the key negotiation algorithm is not very close, there are a lot of problems when they are forcibly bonded. For example, an engine Real-time uses its own hardware encryption card to implement an ECC signature algorithm, however, he does not care about key negotiation or other things, so he has to obtain e for the integrity of the ecc_methed_st structure. Cc's default implementation of key negotiation is then added to ecc_methed_st, which can be avoided through better design. So we separate the two and let them belong to two engines respectively. That is to say, different engines manage different algorithms, so we have the following struct:
Typedef struct ecdh_data_st {
/* Ec_key_meth_data part */
INT (* init) (ec_key *);
/* Method specific part */
Engine * engine;
Int flags;
Const ecdh_method * meth;
Crypto_ex_data ex_data;
} Ecdh_data;
Typedef struct ecdsa_data_st {
/* Ec_key_meth_data part */
INT (* init) (ec_key *);
/* Method (ECDSA) specific part */
Engine * engine;
Int flags;
Const ecdsa_method * meth;
Crypto_ex_data ex_data;
} Ecdsa_data;
It can be seen that these two struct structures are very similar to RSA. In order not to touch the excellent structure of evp_pkey, a well-designed ec_key_st struct must be required, this struct can dynamically decide whether to use ecdh_data or ecdsa_data, that is, the role of an engine switch. To understand ec_key_st at the design level, the content is actually the Public Information of ecdh and ECDSA. According to the principle of ECC, their public information is a public key and a private key. what is different is the operation and usage of the public/private key, these operations and purposes will be reflected in the engine, so the following struct is available:
Struct ec_key_st {
Int version;
Ec_group * group; // curve group, which is the core concept of ECC.
Ec_point * pub_key; // Public Key. In ECC, the bow is the "point" on a curve"
Bignum * priv_key; // Private Key. In ECC, the private key is a large number. The public key can be obtained by multiplying the large number and the "base point ".
Unsigned int enc_flag;
Point_conversion_form_t conv_form;
Int references;
Ec_extra_data * method_data; // This struct dynamically determines whether it is DH, DSA, or something else. Note 0
}/* Ec_key */;
Note 0: What else does it mean? In fact, the method_data field of ec_key_st is a linked list:
Typedef struct ec_extra_data_st {
Struct ec_extra_data_st * next;
Void * data; // This data is the above ecdsa_data or ecdh_data and other Methed
Void * (* dup_func) (void *);
Void (* free_func) (void *);
Void (* clear_free_func) (void *);
} Ec_extra_data;/* Used in ec_group */
Because method_data is dynamically determined, who decides what method is actually determined by the caller, because the execution process of the program knows what needs to be done at this time, then we can get the corresponding method_data struct, and then we can hand it over to the engine of the method to implement the function.
Next we will use a practical process to experience the execution process of OpenSSL for ECC Signature Verification:
1. The Verify parameter is added to the end of the OpenSSL command, followed by the CA certificate and user certificate;
2. Go to verify. C, extract the certificate, verify the certificate chain (see the previous section), extract the signature algorithm, and extract the public key;
3. x509_get_pubkey to get the evp_pkey structure. According to the X509 structure, the signature algorithm and the Public Key itself can be obtained, and then the type of the corresponding key can be obtained based on the oId of the signature algorithm. Then:
Else if (ret-> type = evp_pkey_ec ){
If (a-> parameter & (a-> parameter-> type = v_asn1_sequence )){
If (ret-> pkey. EC = ec_key_new () = NULL) // initialize ec_key_st
...
4. enter x509_verify to verify the signature. First, you need to re-digest the signed message and then verify it. The logic of the asn1_item_verify function is clear, in line with the principle of depth first, enter asn1_item_verify and have an in-depth understanding of it:
Int asn1_item_verify (const asn1_item * It, x509_algor * a, asn1_bit_string * signature, void * Asn, evp_pkey * pkey)
{
Evp_md_ctx CTX;
Const evp_md * type;
Evp_md_ctx_init (& CTX );
I = obj_obj2nid (a-> algorithm); // obtain the NID of the algorithm.
Type = evp_get_digestbyname (obj_nid2sn (I); // obtain the abstract processing function structure of the corresponding NID algorithm. Note 1
Evp_verifyinit_ex (& CTX, type, null );
INL = asn1_item_i2d (Asn, & buf_in, it );
Evp_verifyupdate (& CTX, (unsigned char *) buf_in, INL );
Evp_verifyfinal (& CTX, (unsigned char *) signature-> data,
(Unsigned INT) signature-> length, pkey); // implement verification
...
}
For Note 1, it is necessary to explain that evp_md is a very important data structure. In fact, it is also a structure that encapsulates data and operations. It implements a signature and signature verification logic framework, evp_md is a logic encapsulation over the engine. evp_md can be viewed as a logical encapsulation, while engine is an algorithm encapsulation that implements these logics:
Struct env_md_st {
Int type;
Int pkey_type;
Int md_size;
Unsigned long flags;
INT (* init) (evp_md_ctx * CTX); // Initialization
INT (* update) (evp_md_ctx * CTX, const void * data, size_t count); // operation
INT (* Final) (evp_md_ctx * CTX, unsigned char * MD); // operation ended
INT (* Copy) (evp_md_ctx * To, const evp_md_ctx * From );
INT (* cleanup) (evp_md_ctx * CTX );
INT (* sign) (INT type, const unsigned char * m, unsigned int m_length,
Unsigned char * sigret, unsigned int * siglen, void * Key );
INT (* Verify) (INT type, const unsigned char * m, unsigned int m_length,
Const unsigned char * sigbuf, unsigned int siglen,
Void * Key );
Int required_pkey_type [5];/* evp_pkey_xxx */
Int block_size;
Int ctx_size;
}/* Evp_md */;
Evp_md not only implements the Digest algorithm, but also includes the signature and verification process encapsulation functions. In fact, the so-called signature algorithm is a combination of the digest algorithm and asymmetric private key encryption algorithm, for example, there are many digest algorithms, such as MD5 and sha1, and there are also many asymmetric private key encryption algorithms, such as RSA, DSA, and ECDSA. Therefore, signature algorithms are their cartesian products. The init and updatefinal of evp_md are basically in the computing digest. In the more advanced logic, verify is often called to verify the signature or sign the signature after final is called, for example, evp_verifyfinal and evp_signfinal, EVP _ series functions are the upper-layer logic of OpenSSL cryptography implementation. Next, let's start with step 4.
5. call evp_verifyinit_ex to find the evp_md of the corresponding type from the default engine. If you have your own engine and implement the corresponding evp_md, you can load your engine to use your evp_md. By the way, the evp_md extraction process from the engine is the same as that from the engine by cipher.
6. Call evp_verifyupdate. This is done by calling the update callback function initialized in step 1;
7. Call evp_verifyfinal to complete the final step. In evp_verifyfinal, call evp_digestfinal_ex to complete the digest calculation, and then call evp_md's verify callback function:
Evp_digestfinal_ex (& tmp_ctx, & (M [0]), & m_len );
I = CTX-> digest-> verify (CTX-> digest-> type, M, m_len, sigbuf, siglen, pkey-> pkey. PTR );
8. For ECC, the callback function verify is ecdsa_verify. This function finally calls ecdsa_do_verify:
Int ecdsa_do_verify (const unsigned char * DGST, int dgst_len,
Const ecdsa_sig * Sig, ec_key * Eckey)
{// The last parameter of the callback function varies with the algorithm because the pkey member is a union.
Ecdsa_data * ECDSA = ecdsa_check (Eckey );
Return ECDSA-> meth-> ecdsa_do_verify (DGST, dgst_len, Sig, Eckey );
}
9. The final ecdsa_check determines that the data type in method_data in ec_key is ecdsa_data;
10. Next, call ecdsa_do_verify of ecdsa_method to complete the final operation.
The above 10 steps are the steps to execute the Verify command. For the X509 command to issue a certificate, the same is true, except that verify is changed to sign. If we compare the signature and verification process of RSA, we will find that ECC has an additional step, which is the ecdsa_check process.
By now, the implementation of ECC has been quite clear, but it is not enough to understand ECC from the programming perspective to find out what ECC is and what the ECC key is, next we will introduce the principle of ECC in sequence. If only programming is performed, it is at best an IT migrant worker. Therefore, we should not only know about a technology, but also what it means.