Differences

This shows you the differences between two versions of the page.

Link to this comparison view

ac:laboratoare:11 [2025/11/06 09:54]
marios.choudary
ac:laboratoare:11 [2025/11/06 10:36] (current)
marios.choudary
Line 80: Line 80:
 ==== 3. PQC ready? (4p) ==== ==== 3. PQC ready? (4p) ====
  
-Now try to replace ​the key exchange ​used above with post-quantum cryptography. For this you can use different libraries.+Now let's update ​the key exchange ​to use post-quantum cryptography.
  
-One option is using [[https://​github.com/​open-quantum-safe/​liboqs |liboqs]]. To install it on Ubuntu, use the command below:+For this you can either modify the work you did above to use a post-quantum key exchange method such as ML-KEM in Python. 
 +To do this in Python you can use a library such as [[https://​pypi.org/​project/​mlkem/​|ml-kem]]. 
 +You can install this as follows: 
 +<​code>​ 
 +pip install ml-kem 
 +</​code>​ 
 + 
 +Below is an example of how to use this library in a simple client-server scenario. Note: this example is provided by Gemini AI, so use it with caution and double-check it: 
 +<​code>​ 
 +# Install the library first: pip install ml-kem 
 + 
 +from mlkem.ml_kem import ML_KEM 
 +from mlkem.parameter_set import ML_KEM_768 # Recommended security level 
 +import secrets 
 + 
 +# --- Server (Alice) Side --- 
 + 
 +def server_keygen():​ 
 +    """​Alice generates her ML-KEM key pair."""​ 
 +    # Initialize ML-KEM with the desired security level 
 +    ml_kem = ML_KEM(parameters=ML_KEM_768,​ randomness=secrets.token_bytes) 
 +     
 +    # Generate the encapsulation key (ek, public) and decapsulation key (dk, private) 
 +    ek, dk = ml_kem.key_gen() 
 +     
 +    print("​Alice:​ Generated Public Key (ek) and Private Key (dk)."​) 
 +    return ek, dk, ml_kem 
 + 
 +def server_decapsulate(dk,​ c, ml_kem): 
 +    """​Alice decapsulates the ciphertext to get the shared secret."""​ 
 +    try: 
 +        K_prime = ml_kem.decaps(dk,​ c) 
 +        print("​Alice:​ Successfully decapsulated the Shared Secret (K'​)."​) 
 +        return K_prime 
 +    except ValueError as e: 
 +        print(f"​Alice:​ Decapsulation failed! {e}"​) 
 +        return None 
 + 
 +# --- Client (Bob) Side --- 
 + 
 +def client_encapsulate(ek):​ 
 +    """​Bob encapsulates a shared secret using Alice'​s public key."""​ 
 +    ml_kem = ML_KEM(parameters=ML_KEM_768,​ randomness=secrets.token_bytes) 
 +     
 +    # Encapsulate to get the shared secret (K) and the ciphertext (c) 
 +    K, c = ml_kem.encaps(ek) 
 +     
 +    print("​Bob:​ Encapsulated a Shared Secret (K) and created Ciphertext (c)."​) 
 +    return K, c 
 + 
 +# --- Communication Flow Simulation --- 
 + 
 +# 1. Server (Alice) Key Generation 
 +ek_server, dk_server, ml_kem_instance = server_keygen() 
 + 
 +# 2. Public Key Transmission (ek_server is sent to the client) 
 +print("​\n--- Network Transmission:​ ek sent to Bob ---"​) 
 + 
 +# 3. Client (Bob) Encapsulation 
 +K_client, c_client = client_encapsulate(ek_server) 
 + 
 +# 4. Ciphertext Transmission (c_client is sent back to the server) 
 +print("​\n--- Network Transmission:​ c sent to Alice ---"​) 
 + 
 +# 5. Server (Alice) Decapsulation 
 +K_server = server_decapsulate(dk_server,​ c_client, ml_kem_instance) 
 + 
 +# 6. Verification 
 +print("​\n--- Verification ---"​) 
 +if K_client is not None and K_server is not None: 
 +    if K_client == K_server: 
 +        print("​Success! Alice'​s and Bob's shared secrets match."​) 
 +        # The shared secret can now be used as an AES key, e.g., K_client 
 +        # The shared secret is bytes: 
 +        # print(f"​Shared Secret: {K_client.hex()}"​) 
 +    else: 
 +        print("​Failure! Shared secrets do not match."​) 
 +else: 
 +    print("​Failure in key exchange process."​) 
 +</​code>​ 
 + 
 + 
 +Otherwise, you can start from the Diffie-Hellman key exchange lab we did in OpenSSL/C. You may start from {{:​ac:​laboratoare:​lab_dhe_solved.zip|this}} code, that provides a working solution for the Diffie-Hellman lab in OpenSSL. 
 + 
 +If you go for the C implementation in OpenSSL, one option ​to include post-quantum encryption ​is using [[https://​github.com/​open-quantum-safe/​liboqs |liboqs]]. To install it on Ubuntu, use the command below:
  
 <​code>​ <​code>​
Line 92: Line 176:
 Alternatively,​ you can also use OpenSSL directly (starting with version 3.5). You can find some documentation for ML-KEM [[https://​docs.openssl.org/​master/​man7/​EVP_KEM-ML-KEM/​ |here]] and [[https://​docs.openssl.org/​3.5/​man7/​EVP_PKEY-ML-KEM/​ |here]]. Alternatively,​ you can also use OpenSSL directly (starting with version 3.5). You can find some documentation for ML-KEM [[https://​docs.openssl.org/​master/​man7/​EVP_KEM-ML-KEM/​ |here]] and [[https://​docs.openssl.org/​3.5/​man7/​EVP_PKEY-ML-KEM/​ |here]].
  
-Below is an example code obtained through Gemini AI (not fully tested, but perhaps a good starting point -- you may do something similar to get a starting point for the server):+Below is an example code obtained through Gemini AI (not fully tested, but perhaps a good starting point):
 <​code>​ <​code>​
 #include <​stdio.h>​ #include <​stdio.h>​
Line 217: Line 301:
 </​code>​ </​code>​
  
 +Some instructions to use this code (also from Gemini AI):
 +  * Save the code as mlkem_client.c.
 +  * Generate a real ML-KEM-768 public key (if you didn't do this in the previous step) by running the following commands:
 +<​code>​
 +openssl genpkey -algorithm ML-KEM-768 -out server_private.pem
 +openssl pkey -in server_private.pem -pubout -out server_public.pem
 +</​code>​
 +  * Extract the raw public key bytes to replace the placeholder in the C code. A common way to get the raw bytes is to convert the PEM file to a DER format and extract the key component. For ML-KEM, the raw key is often found within the ASN.1 structure.
 +<​note>​
 +See the DH lab for information about this process. But see also below more details for this process, as provided by Gemini AI.
 +</​note>​
 +  * Compile the code (adjusting paths to your OpenSSL 3.5 installation),​ using a command like this (check again also the DH lab for more details):
 +<​code>​
 +gcc -o mlkem_client mlkem_client.c -I/​path/​to/​openssl/​include -L/​path/​to/​openssl/​lib -lssl -lcrypto
 +</​code>​
 +
 +The following GeminiAI-generated scripts (might have some bugs, please double-check,​ they are only given as reference/​helper tools) generate an ML-KEM-768 key pair and, importantly,​ forces OpenSSL to save the private key in the FIPS 203 '​dk'​ format (the one the C code expects for decapsulation) rather than the default '​seed'​ format, which is not what the raw C code imports:
 +<​code>​
 +#!/bin/bash
 +KEY_ALG="​ML-KEM-768"​
 +PRIV_FILE="​server_private_dk.pem"​
 +PUB_FILE="​server_public_ek.pem"​
 +RAW_PRIV_FILE="​server_private_raw.bin"​
 +RAW_PUB_FILE="​server_public_raw.bin"​
 +
 +echo "--- 1. Generating $KEY_ALG Key Pair (forcing dk-only private key) ---"
 +
 +# Generate the private key, using '​-provparam ml-kem.retain_seed=no'​
 +# to ensure it saves the full FIPS 203 '​dk'​ decapsulation key,
 +# NOT just the seed.
 +openssl genpkey -algorithm "​$KEY_ALG"​ \
 +    -provparam ml-kem.retain_seed=no \
 +    -out "​$PRIV_FILE"​
 +
 +# Extract the public key from the private key file
 +openssl pkey -in "​$PRIV_FILE"​ -pubout -out "​$PUB_FILE"​
 +
 +echo "Keys generated: $PRIV_FILE and $PUB_FILE"​
 +echo "​----------------------------------------------------------------"​
 +</​code>​
 +
 +The ML-KEM public key is in the SubjectPublicKeyInfo structure in the PEM file. We use openssl pkey to output the key in DER format and then remove the ASN.1 header/​wrapper to get the raw bytes:
 +<​code>​
 +echo "--- 2. Extracting RAW Public Key (608 bytes) ---"
 +
 +# The ML-KEM public key is 608 bytes long.
 +# The '​pubout'​ option converts to DER, and '​tail'​ removes the ASN.1 wrapper.
 +# NOTE: The ASN.1 wrapper size may vary by OpenSSL version/​config. We must
 +# determine the exact offset to trim off the SubjectPublicKeyInfo header.
 +# For standard ML-KEM-768 PKCS#8 output, the header is typically 24 bytes.
 +HEADER_BYTES=24 # Common offset for ML-KEM-768 PKCS#8 SubjectPublicKeyInfo header
 +
 +openssl pkey -in "​$PUB_FILE"​ -pubin -outform DER -out "​$RAW_PUB_FILE"​
 +# Trim the ASN.1 header to get the raw 608-byte key
 +# (Skip first $HEADER_BYTES,​ save the next 608 bytes)
 +dd if="​$RAW_PUB_FILE"​ of="​client_public_key_raw_final.bin"​ bs=1 skip=$HEADER_BYTES count=608 status=none
 +
 +echo "Raw public key saved to client_public_key_raw_final.bin"​
 +# Print the hex array for C code
 +echo "​Public Key Hex Array:"​
 +xxd -p "​client_public_key_raw_final.bin"​ | tr -d '​\n'​ | sed '​s/​../&,​ 0x/g' | sed 's/^, 0x/​0x/'​ | sed 's/, $//'
 +echo
 +echo "​----------------------------------------------------------------"​
 +</​code>​
 +
 +Similarly, the private key (the FIPS 203 '​dk'​ component) is inside the PKCS#8 private key structure. We again convert to DER and trim the ASN.1 wrappers:
 +<​code>​
 +echo "--- 3. Extracting RAW Private Key (1184 bytes) ---"
 +
 +# The ML-KEM-768 private key (dk) is 1184 bytes long.
 +# We convert to DER and again skip the ASN.1 header bytes.
 +# For PKCS#8 '​dk'​ private key, the header is typically 50 bytes.
 +HEADER_BYTES=50 # Common offset for ML-KEM-768 PKCS#8 PrivateKeyInfo header
 +
 +openssl pkey -in "​$PRIV_FILE"​ -outform DER -out "​$RAW_PRIV_FILE"​
 +# Trim the ASN.1 header to get the raw 1184-byte key
 +# (Skip first $HEADER_BYTES,​ save the next 1184 bytes)
 +dd if="​$RAW_PRIV_FILE"​ of="​server_private_key_raw_final.bin"​ bs=1 skip=$HEADER_BYTES count=1184 status=none
 +
 +echo "Raw private key saved to server_private_key_raw_final.bin"​
 +# Print the hex array for C code
 +echo "​Private Key Hex Array:"​
 +xxd -p "​server_private_key_raw_final.bin"​ | tr -d '​\n'​ | sed '​s/​../&,​ 0x/g' | sed 's/^, 0x/​0x/'​ | sed 's/, $//'
 +echo
 +echo "​----------------------------------------------------------------"​
 +</​code>​
 +
 +You can now use the hex output from these scripts to reliably replace the placeholder arrays in your C client and server code.
 +See also [[https://​www.youtube.com/​watch?​v=FzLzH5W-mi8|this]] video about Post-Quantum Cryptography support in OpenSSL.
 +
 +
 +Below is also an example code from Gemini AI as starting point for the server, to pair with the code above for the client.
 +Note that these examples use hardcoded values for keys and do not imply any communication. Therefore, make sure you use them for a proper key exchange that uses communication between parties to exchange the public keys and ciphertexts,​ in a similar manner to what you did for the DH lab.
 +
 +<​code>​
 +#include <​stdio.h>​
 +#include <​stdlib.h>​
 +#include <​string.h>​
 +#include <​openssl/​evp.h>​
 +#include <​openssl/​err.h>​
 +#include <​openssl/​provider.h>​
 +
 +// --- HARDCODED ML-KEM-768 Private Key (Replace with a real key) ---
 +// A real ML-KEM-768 private key is 1184 bytes long.
 +unsigned char server_private_key_bytes[] = {
 +    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
 +    // ... 1168 more bytes of a real ML-KEM-768 private key would go here ...
 +    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
 +};
 +size_t private_key_len = 1184; // ML-KEM-768 Private Key size
 +
 +// --- HARDCODED Ciphertext (Generated by the Client - Replace with real data) ---
 +// A real ML-KEM-768 ciphertext is 1088 bytes long.
 +unsigned char client_ciphertext_bytes[] = {
 +    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
 +    // ... 1072 more bytes of a real ML-KEM-768 ciphertext would go here ...
 +    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
 +};
 +size_t ciphertext_len = 1088; // ML-KEM-768 Ciphertext size
 +
 +// --- Hardcoded Shared Secret Size (32 bytes for ML-KEM-768) ---
 +size_t shared_secret_len = 32;
 +
 +// Error handling macro
 +#define HANDLE_ERROR(msg) \
 +    { \
 +        fprintf(stderr,​ "%s failed.\n",​ msg); \
 +        ERR_print_errors_fp(stderr);​ \
 +        goto cleanup; \
 +    }
 +
 +int main(void) {
 +    EVP_PKEY_CTX *kctx = NULL;
 +    EVP_PKEY *server_priv_key = NULL;
 +    OSSL_PARAM params[2];
 +    unsigned char *recovered_secret = NULL;
 +    int ret = 0;
 +
 +    printf("​Starting ML-KEM Server Key Decapsulation (ML-KEM-768).\n"​);​
 +
 +    // 1. Load the OpenSSL default provider
 +    if (!OSSL_PROVIDER_load(NULL,​ "​default"​)) {
 +        HANDLE_ERROR("​Failed to load default provider"​);​
 +    }
 +
 +    // 2. Import the raw private key bytes into an EVP_PKEY structure
 +    printf("​2. Importing ML-KEM-768 Private Key...\n"​);​
 +
 +    // a. Create parameter array to describe the key components
 +    // The ML-KEM private key is referred to as "​priv"​ (or '​dk'​ in FIPS 203 terms)
 +    params[0] = OSSL_PARAM_construct_octet_string("​priv",​ server_private_key_bytes,​ private_key_len);​
 +    params[1] = OSSL_PARAM_construct_end();​
 +
 +    // b. Import the key.
 +    server_priv_key = EVP_PKEY_new_from_params(NULL,​ params, "​ML-KEM-768"​);​
 +    if (!server_priv_key) {
 +        HANDLE_ERROR("​EVP_PKEY_new_from_params failed (Private Key Import)"​);​
 +    }
 +
 +    // 3. Initialize the Key Decapsulation Context
 +    kctx = EVP_PKEY_CTX_new_from_pkey(NULL,​ server_priv_key,​ NULL);
 +    if (!kctx) {
 +        HANDLE_ERROR("​EVP_PKEY_CTX_new_from_pkey failed"​);​
 +    }
 +
 +    if (EVP_PKEY_decapsulate_init(kctx) <= 0) {
 +        HANDLE_ERROR("​EVP_PKEY_decapsulate_init failed"​);​
 +    }
 +
 +    // 4. Allocate buffer for the shared secret
 +    recovered_secret = OPENSSL_malloc(shared_secret_len);​
 +    if (!recovered_secret) {
 +        HANDLE_ERROR("​Memory allocation failed"​);​
 +    }
 +
 +    // 5. Perform the Decapsulation (Server'​s Key Exchange Step)
 +    printf("​5. Performing Key Decapsulation...\n"​);​
 +
 +    // The ciphertext is passed as the input buffer (client_ciphertext_bytes).
 +    // The recovered shared secret is written to the output buffer (recovered_secret).
 +    if (EVP_PKEY_decapsulate(kctx,​ recovered_secret,​ &​shared_secret_len,​ client_ciphertext_bytes,​ ciphertext_len) <= 0) {
 +        HANDLE_ERROR("​EVP_PKEY_decapsulate failed"​);​
 +    }
 +
 +    printf("​✅ Decapsulation successful!\n"​);​
 +    printf(" ​  The server has recovered the **Shared Secret**.\n"​);​
 +    ​
 +    printf(" ​  - Recovered Shared Secret (First 16 bytes): ");
 +    for (size_t i = 0; i < (shared_secret_len > 16 ? 16 : shared_secret_len);​ i++) {
 +        printf("​%02x",​ recovered_secret[i]);​
 +    }
 +    printf("​...\n"​);​
 +
 +    printf("​\n**NOTE:​ For a successful key exchange, the hash of this secret must match the hash of the client'​s secret.**\n"​);​
 +
 +    ret = 0; // Success
 +    ​
 +cleanup:
 +    // Free all allocated resources
 +    EVP_PKEY_CTX_free(kctx);​
 +    EVP_PKEY_free(server_priv_key);​
 +    OPENSSL_free(recovered_secret);​
 +    OSSL_PROVIDER_unload(OSSL_PROVIDER_load(NULL,​ "​default"​));​ // Unload provider
 +
 +    if (ret != 0) {
 +        return EXIT_FAILURE;​
 +    }
 +    return EXIT_SUCCESS;​
 +}
 +</​code>​
  
  
ac/laboratoare/11.1762415688.txt.gz · Last modified: 2025/11/06 09:54 by marios.choudary
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0