ECDSA

Sign and Verify a Message with Openssl ECDSA Library

This article wants to show how to sign and verify a message using an Elliptic Curve Digital Signature Algorithm. In particular, I am going to use secp256k1 class of curves used in Bitcoin. To understand almost all the OpenSSL data structure you can read this quote from OpenSSL wiki: An EC_GROUP structure is used to represent the definition of an elliptic curve. Points on a curve are stored using an EC_POINT structure. An EC_KEY is used to hold a private/public key pair, where a private key is simply a BIGNUM and a public key is a point on a curve (represented by an EC_POINT). I propose here 2 version of the code: the “no checks” version it is done without checking the return values of the functions. the “with checks” version it is done checking the return values of the functions. (TODO) C (no checks) C (with checks) // filename: main_no_checks.cpp #include <string.h> #include "openssl/ec.h" #include "openssl/ecdsa.h" #include "openssl/bn.h" #include "openssl/obj_mac.h" #include "openssl/sha.h" int main() { EC_KEY *key_pair_obj = nullptr; int ret_error; BIGNUM *priv_key; EC_POINT *pub_key; EC_GROUP *secp256k1_group; char *pub_key_char, *priv_key_char; const char *message = "message to sign"; unsigned char buffer_digest[SHA256_DIGEST_LENGTH]; uint8_t *digest; uint8_t *signature; uint32_t signature_len; int verification; BIGNUM *bn; EC_KEY *imported_key_pair = nullptr; EC_GROUP *curve_group; EC_POINT *public_point; int char_read; // Generate secp256k1 key pair key_pair_obj = EC_KEY_new_by_curve_name(NID_secp256k1); ret_error = EC_KEY_generate_key(key_pair_obj); // Get private key priv_key = (BIGNUM *)EC_KEY_get0_private_key(key_pair_obj); priv_key_char = BN_bn2hex(priv_key); // Get public key pub_key = (EC_POINT *)EC_KEY_get0_public_key(key_pair_obj); secp256k1_group = EC_GROUP_new_by_curve_name(NID_secp256k1); pub_key_char = EC_POINT_point2hex(secp256k1_group, pub_key, POINT_CONVERSION_COMPRESSED, nullptr); EC_GROUP_free(secp256k1_group); printf("Pivate key: %s\n", priv_key_char); printf("Public key: %s\n", pub_key_char); // Sign message signature_len = ECDSA_size(key_pair_obj); // the signature size depends on the key signature = (uint8_t *) OPENSSL_malloc(signature_len); digest = SHA256((const unsigned char *)message, strlen(message), buffer_digest); ret_error = ECDSA_sign(0, (const uint8_t *)digest, SHA256_DIGEST_LENGTH, signature, &signature_len, key_pair_obj); printf("Message SHA256: ");for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) printf("%02x", digest [i]); printf("\n"); printf("Signature : "); for(uint32_t i = 0; i < signature_len ; i++) printf("%02x", signature[i]); printf("\n"); // Verify the signature verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, key_pair_obj); if (verification == 1) printf("Verification successful\n"); else printf("Verification NOT successful\n"); EC_KEY_free(key_pair_obj); // Double check process for correctness imported_key_pair = EC_KEY_new_by_curve_name(NID_secp256k1); curve_group = EC_GROUP_new_by_curve_name(NID_secp256k1); public_point = EC_POINT_new(curve_group); public_point = EC_POINT_hex2point(curve_group, pub_key_char, public_point, nullptr); ret_error = EC_KEY_set_public_key(imported_key_pair, public_point); EC_GROUP_free(curve_group); EC_POINT_free(public_point); free(pub_key_char); bn = BN_new(); char_read = BN_hex2bn(&bn, priv_key_char); ret_error = EC_KEY_set_private_key(imported_key_pair, bn); BN_clear_free(bn); free(priv_key_char); verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, imported_key_pair); if (verification == 1) printf("Re-Verification successful\n"); else printf("Re-Verification NOT successful\n"); EC_KEY_free(imported_key_pair); OPENSSL_free(signature); return 0; } // TO DO ! ! ! // filename: main_with_checks.cpp #include <string.h> #include "openssl/ec.h" #include "openssl/ecdsa.h" #include "openssl/bn.h" #include "openssl/obj_mac.h" #include "openssl/sha.h" int main() { EC_KEY *key_pair_obj = nullptr; int ret_error; BIGNUM *priv_key; EC_POINT *pub_key; EC_GROUP *secp256k1_group; char *pub_key_char, *priv_key_char; const char *message = "message to sign"; unsigned char buffer_digest[SHA256_DIGEST_LENGTH]; uint8_t *digest; uint8_t *signature; uint32_t signature_len; int verification; BIGNUM *bn; EC_KEY *imported_key_pair = nullptr; EC_GROUP *curve_group; EC_POINT *public_point; int char_read; // Generate secp256k1 key pair key_pair_obj = EC_KEY_new_by_curve_name(NID_secp256k1); ret_error = EC_KEY_generate_key(key_pair_obj); // Get private key priv_key = (BIGNUM *)EC_KEY_get0_private_key(key_pair_obj); priv_key_char = BN_bn2hex(priv_key); // Get public key pub_key = (EC_POINT *)EC_KEY_get0_public_key(key_pair_obj); secp256k1_group = EC_GROUP_new_by_curve_name(NID_secp256k1); pub_key_char = EC_POINT_point2hex(secp256k1_group, pub_key, POINT_CONVERSION_COMPRESSED, nullptr); EC_GROUP_free(secp256k1_group); printf("Pivate key: %s\n", priv_key_char); printf("Public key: %s\n", pub_key_char); // Sign message signature_len = ECDSA_size(key_pair_obj); // the signature size depends on the key signature = (uint8_t *) OPENSSL_malloc(signature_len); digest = SHA256((const unsigned char *)message, strlen(message), buffer_digest); ret_error = ECDSA_sign(0, (const uint8_t *)digest, SHA256_DIGEST_LENGTH, signature, &signature_len, key_pair_obj); printf("Message SHA256: ");for (uint32_t i = 0; i < SHA256_DIGEST_LENGTH; i++) printf("%02x", digest [i]); printf("\n"); printf("Signature : "); for(uint32_t i = 0; i < signature_len ; i++) printf("%02x", signature[i]); printf("\n"); // Verify the signature verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, key_pair_obj); if (verification == 1) printf("Verification successful\n"); else printf("Verification NOT successful\n"); EC_KEY_free(key_pair_obj); // Double check process for correctness imported_key_pair = EC_KEY_new_by_curve_name(NID_secp256k1); curve_group = EC_GROUP_new_by_curve_name(NID_secp256k1); public_point = EC_POINT_new(curve_group); public_point = EC_POINT_hex2point(curve_group, pub_key_char, public_point, nullptr); ret_error = EC_KEY_set_public_key(imported_key_pair, public_point); EC_GROUP_free(curve_group); EC_POINT_free(public_point); free(pub_key_char); bn = BN_new(); char_read = BN_hex2bn(&bn, priv_key_char); ret_error = EC_KEY_set_private_key(imported_key_pair, bn); BN_clear_free(bn); free(priv_key_char); verification = ECDSA_verify(0, digest, SHA256_DIGEST_LENGTH, signature, signature_len, imported_key_pair); if (verification == 1) printf("Re-Verification successful\n"); else printf("Re-Verification NOT successful\n"); EC_KEY_free(imported_key_pair); OPENSSL_free(signature); return 0; } The following explanation of the code follows the “no check” version of the code. In line 32, an EC_KEY curve object is created but it is empty at the moment. In line 33, the private and public key parts are created. In lines 36-37, the private key is extracted from the key object. The private key is represented as big-number (BIGNUM) thus it is encoded in hex values. In lines 40-42, the public key is extracted from the key object. The public key is a EC_POINT on the curve thus to be extracted the function EC_POINT_point2hex needs to know which curve it is. In lines 49-52, the SHA256 of the message is computed and the signature is computed after it. The signature length depends on the dimension of the key (line 47). In line 58, the signature is verified. To double check that the signature is correct, I am going to import the public and private key, as hexadecimal strings, into a key object and I am going to use it to verify the signature. In line 66-70, I am importing the public key (the point of the curve) from its hexadecimal string representation. In line 75-77, I am importing the private key (the random big-number) from its hexadecimal string representation. In line 81, the signature is verified again. To compile the code use: g++ -o main_no_checks main_no_checks.cpp -lcrypto It is also good practice to check for memory leak with Valgrind. In order to use it, we need to compile the program with debug information and disable the compiler optimizations: g++ -g -O0 -o main_no_checks main_no_checks.cpp -lcrypto valgrind --leak-check=yes ./main_no_checks Here you can find the function prototypes along with the library where it is declared: #include "openssl/ec.h" EC_KEY *EC_KEY_new_by_curve_name (int nid); int EC_KEY_generate_key (EC_KEY *key); const BIGNUM *EC_KEY_get0_private_key (const EC_KEY *key); const EC_POINT *EC_KEY_get0_public_key (const EC_KEY *key); int EC_KEY_set_private_key (EC_KEY *key, const BIGNUM *prv); int EC_KEY_set_public_key (EC_KEY *key, const EC_POINT *pub); EC_GROUP *EC_GROUP_new_by_curve_name(int nid); char *EC_POINT_point2hex (const EC_GROUP *group, const EC_POINT *p, point_conversion_form_t form, BN_CTX *ctx); EC_POINT *EC_POINT_hex2point (const EC_GROUP *group, const char *hex, EC_POINT *p, BN_CTX *ctx); void EC_KEY_free (EC_KEY *key); void EC_GROUP_free (EC_GROUP *group); void EC_POINT_free (EC_POINT *point); #include "openssl/ecdsa.h" int ECDSA_size (const EC_KEY *eckey) int ECDSA_sign (int type, const unsigned char *dgst, int dgstlen, unsigned char *sig, unsigned int *siglen, EC_KEY *eckey); "type" --> is ignored for now int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, const unsigned char *sig, int siglen, EC_KEY *eckey); "type" --> is ignored for now #include "openssl/bn.h" char *BN_bn2hex (const BIGNUM *a); int BN_hex2bn (BIGNUM **a, const char *str); void BN_clear_free(BIGNUM *a); #include "openssl/obj_mac.h" NID_secp256k1 #include "openssl/sha.h" SHA256_DIGEST_LENGTH unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); More to read: https://en.wikibooks.org/wiki/OpenSSL/EC http://valgrind.org