October 23, 2024
Chicago 12, Melborne City, USA
C#

OpenSSL EVP_PKEY_fromdata Importing Parameters Incorrectly


EDIT: I realized the EVP_PKEY_print_private function is actually the same as the RFC p, they just have differing endian-ness. I would still appreciate critiques of my code, and pointers to helpful documentation (I’ve found the newer OpenSSL docs to be difficult to read and lacking examples).

I am writing a simple ssh client using Diffie Hellman group 14. I’m using the OpenSSL library to handle the specifics of the DH exchange. The issue that I’m having is that the prime number (p) for group 14 that I import into a PKEY doesn’t seem to be imported correctly. The p that I import and the p that gets printed out do not match.

The p that RFC 3526 specifies for group 14:

  FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
  29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
  EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
  E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
  EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
  C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
  83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
  670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
  E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
  DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
  15728E5A 8AACAA68 FFFFFFFF FFFFFFFF

The output of EVP_PKEY_print_private():

DH Private-Key: (2048 bit)
private-key:
    52:d0:07:31:2d:a0:5a:01:17:9b:39:27:f8:1d:59:
    df:0c:b6:39:e2:7e:8b:c7:c3:b2:9c:86:a9:4d:bf:
    1b:99:b0:85:5d:17:b9:34:13:ca:86:26:ba:11:c4:
    9d:68:13:b3:a3:bb:6b:8d:11:3a:84:6e:de:40:15:
    da:02:25:c2:c9:72:bf:17:37:27:4e:cb:81:0c:0b:
    e8:73:3f:91:76:28:82:c4:7f:ef:00:f9:95:14:64:
    c4:b9:b0:bd:c6:a5:47:77:96:bd:e1:38:81:be:e9:
    d8:9d:c2:7b:aa:ef:c3:51:95:c8:43:95:73:9f:d4:
    8e:62:f3:52:83:34:88:f0:63:99:59:b2:2b:c7:9f:
    f4:ab:14:8b:11:ee:6f:cd:3a:76:9f:81:7d:de:5c:
    aa:44:a7:92:fc:89:3a:b8:7b:48:b5:ba:03:bd:c2:
    7c:67:b1:ed:59:49:15:f3:05:b0:60:bd:47:b7:df:
    52:31:2f:31:10:f4:a3:61:53:dd:10:9a:9f:07:e7:
    eb:c3:d6:ab:67:a9:2b:8d:eb:45:ea:9f:f0:be:b5:
    21:e4:76:74:0f:42:ea:80:8e:ec:50:c2:4f:03:ac:
    40:2e:7d:44:9f:d8:fa:47:43:29:4c:4e:bd:20:00:
    71:70:cf:a3:60:f1:a7:52:50:dc:9e:2a:12:f6:94:
    b8
public-key:
    00:94:9b:ca:79:4d:9a:cc:ed:84:90:8e:5c:52:46:
    e6:b8:bf:a7:75:74:a6:ec:6a:95:fc:12:47:75:5d:
    91:9c:fb:63:13:81:e5:5b:e0:64:c8:40:39:73:67:
    96:b9:98:6c:72:f0:d7:d1:fa:93:26:9c:75:d4:c0:
    d2:b2:53:32:25:b4:07:a9:26:5d:c8:c9:c3:fd:ab:
    5d:ea:10:10:4f:f7:ad:f1:64:bf:80:88:b3:35:52:
    48:23:b7:82:65:d4:cc:28:cf:64:a4:9d:e2:22:83:
    1a:7d:14:1f:07:7f:b1:dd:20:20:21:7c:da:35:93:
    be:b4:48:6d:ab:43:c0:ef:1e:fc:4e:55:7d:87:9f:
    63:4a:67:bb:cf:eb:af:b7:a1:81:23:71:c2:c2:18:
    a9:5f:a2:2c:61:03:c8:b5:82:ab:df:14:1a:03:04:
    fb:60:f7:61:c8:6e:35:56:95:fb:a2:c6:a3:d1:b5:
    df:b0:45:41:71:2c:54:2c:c7:cd:c4:67:09:ce:ab:
    0e:17:f6:b4:45:89:53:18:c2:cb:7d:10:41:65:0a:
    c9:d5:75:29:fa:64:6a:c7:02:fe:b9:3a:f0:2d:f3:
    ce:34:07:94:f4:db:0d:5e:85:2a:04:2b:cd:70:4a:
    04:d1:83:cc:6d:54:09:49:67:d6:69:fd:82:6f:14:
    e8:d6
P:   
    00:ff:ff:ff:ff:ff:ff:ff:ff:68:aa:ac:8a:5a:8e:
    72:15:10:05:fa:98:18:26:d2:15:e5:6a:95:ea:7c:
    49:95:39:18:17:58:95:f6:cb:2b:de:c9:52:4c:6f:
    f0:5d:c5:b5:8f:a2:07:ec:a2:83:27:9b:03:86:0e:
    18:2c:77:9e:e3:3b:ce:36:2e:46:5e:90:32:7c:21:
    18:ca:08:6c:74:f1:04:98:bc:4a:4e:35:0c:67:6d:
    96:96:70:07:29:d5:9e:bb:52:85:20:56:f3:62:1c:
    96:ad:a3:dc:23:5d:65:83:5f:cf:24:fd:a8:3f:16:
    69:9a:d3:55:1c:36:48:da:98:05:bf:63:a1:b8:7c:
    00:c2:3d:5b:e4:ec:51:66:28:49:e6:1f:4b:7c:11:
    24:9f:ae:a5:9f:89:5a:fb:6b:38:ee:ed:b7:06:f4:
    b6:5c:ff:0b:6b:ed:37:a6:e9:42:4c:f4:c6:7e:5e:
    62:76:b5:85:e4:45:c2:51:6d:6d:35:e1:4f:37:14:
    5f:f2:6d:0a:2b:30:1b:43:3a:cd:b3:19:95:ef:dd:
    04:34:8e:79:08:4a:51:22:9b:13:3b:a6:be:0b:02:
    74:cc:67:8a:08:4e:02:29:d1:1c:dc:80:8b:62:c6:
    c4:34:c2:68:21:a2:da:0f:c9:ff:ff:ff:ff:ff:ff:
    ff:ff
G:    2 (0x2)

From print statements, I know that the value of p is correct before calling EVP_PKEY_fromdata() and passing in the DH parameters (group 14 p and g), but when I print out the contents of the PKEY, the value of p is incorrect (as shown in images). The same issue happens after I receive the server’s public key (f). I have verified that the code receives f correctly from the server, but after I import it into a PKEY and print the PKEY, the value of f is incorrect.

I’ve been struggling to find documentation/examples of how to cleanly create a PKEY, so any advice there would also be appreciated. Here’s the code:

int sendDiffieHellmanExchange(int sock) {
    EVP_PKEY_CTX *pctx = NULL, *kctx = NULL;
    EVP_PKEY *params = NULL, *dhkey = NULL;
    BIO *out = NULL;
    BIGNUM *p = NULL, *g = NULL;
    unsigned char *p_bin = NULL, *g_bin = NULL;
    OSSL_PARAM dh_params[3];
    int p_size = 0, g_size = 0;

    // Group 14 parameters
    const char* group14_p_hex = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF";
    const int group14_g = 2;

    // Convert Group 14 prime to BIGNUM
    p = BN_new();
    BN_hex2bn(&p, group14_p_hex);

    // Create BIGNUM for generator
    g = BN_new();
    BN_set_word(g, group14_g);

    // Get sizes for p and g
    p_size = BN_num_bytes(p);
    g_size = BN_num_bytes(g);

    // Allocate memory for p_bin and g_bin
    p_bin = (unsigned char*)OPENSSL_malloc(p_size);
    g_bin = (unsigned char*)OPENSSL_malloc(g_size);

    // Convert p and g into binary form 
    // need to pad out binary to ensure standard size
    BN_bn2binpad(p, p_bin, p_size);
    BN_bn2binpad(g, g_bin, g_size);

    // Initialize the parameter context for DH key generation 
    pctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);

    // Prepare the DH parameters (p and g) using OSSL_PARAM array 
    dh_params[0] = OSSL_PARAM_construct_BN("p", p_bin, p_size);
    dh_params[1] = OSSL_PARAM_construct_BN("g", g_bin, g_size);
    dh_params[2] = OSSL_PARAM_construct_end();

    // Use EVP_PKEY_fromdata to create an EVP_PKEY using the DH parameters 
    EVP_PKEY_fromdata_init(pctx);

    EVP_PKEY_fromdata(pctx, &params, EVP_PKEY_KEY_PARAMETERS, dh_params);

    // Create key generation context
    kctx = EVP_PKEY_CTX_new(params, NULL);

    // Generate a new DH key
    EVP_PKEY_keygen_init(kctx);

    EVP_PKEY_keygen(kctx, &dhkey);

    // Create a buffer for the encoded public key
    unsigned char *pub_key_encoded = NULL;
    EVP_PKEY_get1_encoded_public_key(dhkey, &pub_key_encoded);
    
    int eLen = EVP_PKEY_bits(dhkey)/8;
    // encodeMpint adds a leading 0 if necessary to ensure that e is positive
    RawByteArray *e = encodeMpint(pub_key_encoded, eLen);

    unsigned char *buffer = malloc(e -> size + 1 + 4);
    buffer[0] = SSH_MSG_KEXDH_INIT;
    uint32_t mpint_len_network_order = htonl(e->size);
    memcpy(buffer + 1, &mpint_len_network_order, sizeof(uint32_t));
    memcpy(buffer + 5, e -> data, e -> size);
    free(e -> data);

    /* Optional: Print the private key */
    out = BIO_new_fp(stdout, BIO_NOCLOSE);
    if (out && dhkey) {
        EVP_PKEY_print_private(out, dhkey, 0, NULL);
    }
    
    // RawByteArray is a struct that contains a unsigned char * for data, and a size_t for the        size of that data
    RawByteArray *payload = malloc(sizeof(RawByteArray));
    assert(payload != NULL);

    payload -> data = buffer;
    payload -> size = e -> size + 1 + 4; // +1 for message code, +4 for mpint len
    free(e);

    RawByteArray *packet = constructPacket(payload);
    free(payload);

    int sentBytes = send(sock, packet -> data, packet -> size, 0);
    free(buffer);
    // still need to free packet data even though we malloced data
    free(packet -> data);
    free(packet);

    if (sentBytes != -1) {
        printf("Successful DH init send! Number of bytes sent: %i\n", sentBytes);
    } else {
        printf("Send did not complete successfully.\n");
    }
    
    unsigned char serverResponse[BUFFER_SIZE];
    memset(serverResponse, 0, BUFFER_SIZE);  // Clear the buffer    
    ssize_t bytesReceived = recv(sock, serverResponse, BUFFER_SIZE, 0);
    
    if (bytesReceived > 0) {
        // printf("server DH init response:\n");
        // for (int i = 0; i < bytesReceived; i++) {
        //     printf("%02x ", (unsigned char)serverResponse[i]); 
        // }
        // printf("\n");
    } else {
        printf("No server DH response recieved :(\n");
    }

    // extractServerResponse returns a struct that contains all items that the server sent in step 2 of DH and the size of each of those items
    ServerDHResponse *dhResponse = extractServerDHResponse(serverResponse);

    // BELOW HERE IS TRYING TO IMPORT F INTO A PKEY

    // Convert f to a BIGNUM
    BIGNUM *f_bn = BN_new();
    BN_bin2bn(dhResponse -> f, dhResponse -> fLen, f_bn);

    EVP_PKEY_CTX *peer_ctx = NULL;
    EVP_PKEY *peerkey = NULL;
    OSSL_PARAM peer_params[4];  // Add space for the peer's public key (f)
    peer_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL);

    // Prepare the parameters for peer's public key
    peer_params[0] = OSSL_PARAM_construct_BN("p", p_bin, p_size);  // Using the same `p_bin` and `p_size` as you used for generating your own DH key
    peer_params[1] = OSSL_PARAM_construct_BN("g", g_bin, g_size);  // Using the same `g_bin` and `g_size`
    peer_params[2] = OSSL_PARAM_construct_BN("pub", dhResponse -> f, dhResponse -> fLen); // Server's public key `f`
    peer_params[3] = OSSL_PARAM_construct_end();  // End the array

    EVP_PKEY_fromdata_init(peer_ctx);
    EVP_PKEY_fromdata(peer_ctx, &peerkey, EVP_PKEY_PUBLIC_KEY, peer_params);
    
    printf("F:\n");
    for (int i = 0; i < dhResponse -> fLen; i++) {
        printf("%02x ", dhResponse -> f[i]);
    }
    printf("\n");

    // PRINTING TO DEBUG
    EVP_PKEY_print_public(out, peerkey, 0, NULL);

    /* Cleanup */
    cleanupServerDHResponse(dhResponse);
    EVP_PKEY_CTX_free(pctx);
    EVP_PKEY_CTX_free(kctx);
    EVP_PKEY_free(params);
    EVP_PKEY_free(dhkey);
    BIO_free(out);
    BN_free(p);
    BN_free(g);
    OPENSSL_free(p_bin);
    OPENSSL_free(g_bin);
    OPENSSL_free(pub_key_encoded);
    // cleaning up importing f into a pkey struct
    EVP_PKEY_CTX_free(peer_ctx);
    BN_free(f_bn);
    EVP_PKEY_free(peerkey);
    
    return 0;
}

What am I doing wrong when creating PKEYs? If anyone would like the entire file, please let me know.

I have tried a few different approaches to creating a PKEY with the specific group 14 parameters, but I have had issues finding any real human examples/documentation using the OpenSSL library so I’ve turned to ChatGPT which clearly isn’t the most effective way to write code that works.



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video