Search code examples
cencryptionecdhlibgcryptgcrypt

ECDH Curve 25519 Key Generation c implementation in Libgcrypt


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);
}

Solution

  • 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;
    }