I am trying to implement ECDH in libgcrypt. I have found examples online for how to compute a shared secret and I know how to do symmetric encryption I just can't figure out how to generate the keypairs.
I want the keys to be stored as unsigned char *'s . This is how I am currently trying to do it but it always segfaults on the line gcry_mpi_ec_mul(mpi_public_key, mpi_private_key, NULL, ctx);
Here is my code: `
void generate_curve25519_keypair(unsigned char *public_key, unsigned char *private_key) {
gcry_error_t error;
gcry_mpi_t mpi_private_key;
gcry_mpi_point_t mpi_public_key;
gcry_ctx_t ctx;
// Generate a random private key
gcry_randomize(private_key, CURVE25519_KEY_SIZE, GCRY_STRONG_RANDOM);
// Clamp the private key for Curve25519
private_key[0] &= 248;
private_key[31] &= 127;
private_key[31] |= 64;
// Initialize the context for Curve25519
error = gcry_mpi_ec_new(&ctx, NULL, "Curve25519");
if (error) {
fprintf(stderr, "Failed to create context: %s\n", gpg_strerror(error));
return;
}
// Convert the private key to an MPI
error = gcry_mpi_scan(&mpi_private_key, GCRYMPI_FMT_USG, private_key, CURVE25519_KEY_SIZE, NULL);
if (error) {
fprintf(stderr, "Failed to convert private key to MPI: %s\n", gpg_strerror(error));
gcry_ctx_release(ctx);
return;
}
// Allocate memory for the resulting public key point
mpi_public_key = gcry_mpi_point_new(0);
// Multiply the base point with the private key MPI to get the public key point
gcry_mpi_ec_mul(mpi_public_key, mpi_private_key, NULL, ctx); // Use NULL for the default base point G
if (error) {
fprintf(stderr, "Failed to multiply with base point: %s\n", gpg_strerror(error));
gcry_mpi_release(mpi_private_key);
gcry_mpi_point_release(mpi_public_key);
gcry_ctx_release(ctx);
return;
}
// Extract the x-coordinate of the public key point and convert it to a byte array
gcry_mpi_t mpi_x = gcry_mpi_new(0);
gcry_mpi_point_get(mpi_x, NULL, NULL, mpi_public_key);
error = gcry_mpi_print(GCRYMPI_FMT_USG, public_key, CURVE25519_KEY_SIZE, NULL, mpi_x);
if (error) {
fprintf(stderr, "Failed to export public key: %s\n", gpg_strerror(error));
gcry_mpi_release(mpi_x);
gcry_mpi_release(mpi_private_key);
gcry_mpi_point_release(mpi_public_key);
gcry_ctx_release(ctx);
return;
}
// Release resources
gcry_mpi_release(mpi_x);
gcry_mpi_release(mpi_private_key);
gcry_mpi_point_release(mpi_public_key);
gcry_ctx_release(ctx);
}
Here is a working implementation of ECDH in libgcrypt in C including individual methods for ECC key generation and Shared key generation. Keys are stored as strings and symmetric encryption is accomplished in main with ECDH.
static gpg_error_t compute_master_secret (unsigned char *master, size_t masterlen,
const unsigned char *sk_a, size_t sk_a_len,
const unsigned char *pk_b, size_t pk_b_len)
{
gpg_error_t err;
gcry_sexp_t s_sk_a = NULL;
gcry_sexp_t s_pk_b = NULL;
gcry_sexp_t s_shared = NULL;
gcry_sexp_t s_tmp;
const char *s;
size_t n;
gcry_mpi_t pk_mpi, privk_mpi;
//Convert public key string to MPI
err = gcry_mpi_scan(&pk_mpi,GCRYMPI_FMT_USG,pk_b,pk_b_len, NULL);
if(err){
printf("pk_mpi failed\n");
}
//Convert private key string to MPI
err = gcry_mpi_scan(&privk_mpi,GCRYMPI_FMT_USG,sk_a,sk_a_len, NULL);
if(err){
printf("privk_mpi failed\n");
}
//build a private key s expression
err = gcry_sexp_build (&s_sk_a, NULL, "%m", privk_mpi);
if (!err)
//build a public key s expression
err = gcry_sexp_build (&s_pk_b, NULL,
"(public-key(ecdh(curve Curve25519)"
"(q%m)))", pk_mpi);
if (err){
printf ("error building S-expression: %s\n", gpg_strerror (err));
goto leave;
}
// show_sexp("ecdh secret key ",s_sk_a);
// show_sexp("ecdh pub key ",s_pk_b);
//Encrypt secret k with public key, this does ECDH under the hood
//Ultimately uses ecc.encrypt_raw() in cipher/ecc.c
err = gcry_pk_encrypt (&s_shared, s_sk_a, s_pk_b);
if (err){
printf ("error computing DH: %s\n", gpg_strerror (err));
goto leave;
}
//temp s expression to just get shared key element
s_tmp = gcry_sexp_find_token (s_shared, "s", 0);
//s must be prefixed with 0x40
if (!s_tmp || !(s = gcry_sexp_nth_data (s_tmp, 1, &n))
|| n != 33 || s[0] != 0x40)
{
err = gpg_error (GPG_ERR_INTERNAL);
printf ("error computing DH: %s\n", gpg_strerror (err));
goto leave;
}
//plus one because the first byte is always 0x40 which denotes that it is a compressed point
memcpy (master, s+1, 32);
leave:
gcry_sexp_release (s_sk_a);
gcry_sexp_release (s_pk_b);
gcry_sexp_release (s_shared);
return err;
}
void crypto_box_keypair(char *public_key, char *private_key){
gcry_error_t error;
gcry_sexp_t key_param_sexp, key_pair_sexp;
size_t q_len, d_len;
char *public_key_str, *private_key_str;
gcry_mpi_t q_mpi, d_mpi;
unsigned char raw_public_key[crypto_box_PUBLICKEYBYTES * 2];
unsigned char raw_private_key[crypto_box_SECRETKEYBYTES * 2];
// Initialize libgcrypt
if (!gcry_check_version(GCRYPT_VERSION)) {
fprintf(stderr, "libgcrypt version mismatch\n");
exit(EXIT_FAILURE);
}
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
//build parameters s expression
error = gcry_sexp_build(&key_param_sexp, NULL, "(genkey (ecc (curve Curve25519)))");
if (error) {
fprintf(stderr, "Failed to build S-expression for key generation: %s\n", gcry_strerror(error));
exit(EXIT_FAILURE);
}
//generate ecc keys
error = gcry_pk_genkey(&key_pair_sexp, key_param_sexp);
if (error) {
fprintf(stderr, "Failed to generate key pair: %s\n", gcry_strerror(error));
gcry_sexp_release(key_param_sexp);
exit(EXIT_FAILURE);
}
//get sub s expression for q and d (public and private)
gcry_sexp_t q = gcry_sexp_find_token(key_pair_sexp, "q", 0);
gcry_sexp_t d = gcry_sexp_find_token(key_pair_sexp, "d", 0);
//convert s expression q and d to MPIs
q_mpi = gcry_sexp_nth_mpi(q, 1, GCRYMPI_FMT_USG);
d_mpi = gcry_sexp_nth_mpi(d, 1, GCRYMPI_FMT_USG);
if (!q_mpi || !d_mpi) {
fprintf(stderr, "Failed to extract MPIs from S-expression\n");
gcry_sexp_release(key_pair_sexp);
exit(EXIT_FAILURE);
}
// Convert the MPIs to binary format
error = gcry_mpi_print(GCRYMPI_FMT_USG, raw_public_key, sizeof(raw_public_key), &q_len, q_mpi);
if (error) {
fprintf(stderr, "Failed to convert public key MPI to binary format: %s\n", gcry_strerror(error));
}
error = gcry_mpi_print(GCRYMPI_FMT_USG, raw_private_key, sizeof(raw_private_key), &d_len, d_mpi);
if (error) {
fprintf(stderr, "Failed to convert private key MPI to binary format: %s\n", gcry_strerror(error));
}
// Remove the 0x40 prefix from the public key, if present
if (q_len == crypto_box_PUBLICKEYBYTES + 1 && raw_public_key[0] == 0x40) {
printf("public key is 33 in length\n");
memcpy(public_key, raw_public_key + 1, crypto_box_PUBLICKEYBYTES);
} else {
printf("public key is 32 in length\n");
memcpy(public_key, raw_public_key, crypto_box_PUBLICKEYBYTES);
}
memcpy(private_key, raw_private_key, crypto_box_SECRETKEYBYTES);
}
int main(){
unsigned char alice_publickey[crypto_box_PUBLICKEYBYTES];
unsigned char alice_privatekey[crypto_box_SECRETKEYBYTES];
unsigned char bob_publickey[crypto_box_PUBLICKEYBYTES];
unsigned char bob_privatekey[crypto_box_SECRETKEYBYTES];
char *msg = "test";
unsigned long long msg_len = 4;
unsigned char nonce[crypto_secretbox_NONCEBYTES];
randombytes_buf(nonce, sizeof nonce);
unsigned char ciphertext[msg_len + crypto_secretbox_MACBYTES];
unsigned char decrypted[msg_len];
crypto_box_keypair(alice_publickey, alice_privatekey);
crypto_box_keypair(bob_publickey, bob_privatekey);
//symmetric encryption function
if (crypto_box_easy(ciphertext, msg, msg_len, nonce,
bob_publickey, alice_privatekey) != 0) {
/* error */
}
//asymmetric encryption function
if(crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce, alice_publickey, bob_privatekey) != 0){
}
printf("Decrypted = %s\n", decrypted);
return 0;
}