Differences

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

Link to this comparison view

ac:laboratoare:11 [2024/01/18 13:48]
marios.choudary
ac:laboratoare:11 [2025/11/06 10:36] (current)
marios.choudary
Line 1: Line 1:
-===== Lab 11: EMV crypto ​Verifying a DDA Signature ​=====+===== Lab 6b TOFU-based Authenticated Key Exchange ​=====
  
-==== Install python tools to work with your smartcard ====+In this lab, we will implement Trust On First Use in a manner similar ​to that of SSH. We will base our implementation around ''​%%PyCryptodome%%''​ - you can find the relevant documentation [[https://​www.pycryptodome.org/​src/​api |here]].
  
-Do the following on Linux (this is for Ubuntu/​Debian -- you might need root access): +==== 0. Init ==== 
-  * Install pcsclite-dev:​ + 
-<​code>​ +Use these commands to generate a private key and a Diffie-Hellman parameter file:
-sudo apt-get install libpcsclite-dev +
-</​code>​ +
-  * Then also install ​these packages: +
-<​code>​ +
-sudo apt-get install swig python3-dev libudev-dev python3-pip +
-</​code>​ +
-  * Get and install Pyscard using pip (install pip if needed) +
-<​code>​ +
-pip3 install pyscard +
-</​code>​ +
-  * Install Pyserial +
-<​code>​ +
-pip3 install pyserial +
-</​code>​ +
-If this doesn'​t work, then get pyserial from [[https://​pypi.python.org/​pypi/​pyserial#​downloads|here]] +
-  * Install pcsc related libs: +
-<​code>​ +
-sudo apt-get install libusb-dev libccid pcscd libpcsclite1 +
-</​code>​ +
-  * You might also want to install these additional card tools from here:+
 <​code>​ <​code>​
-sudo apt-get install libpcsc-perl pcsc-tools+openssl genrsa ​-out private.pem 2048 
 +openssl dhparam ​-out dhparam.pem 2048
 </​code>​ </​code>​
  
-See details ​[[http://support.gemalto.com/fileadmin/user_upload/IAM/​FAQ/​How_to_install_the_PC-Link_reader_on_Linux.pdf|here]].+The ''​%%dhparam%%''​ file will be used as a hardcoded ''​%%.pem%%''​ that contains the necessary Diffie-Hellman parameters to generate our DH keys. These values are generally decided upon by convention and are hardcoded - check [[https://datatracker.ietf.org/doc/html/rfc7919#​page-19 |RFC 7919]]. An example which can be found there is ''​%%ffdhe2048%%''​.
  
-For Windows drivers ​you can check [[https://​supportportal.gemalto.com/​csm/?​id=kb_article_view&​sys_kb_id=0adc96844f350700873b69d18110c76a&​sysparm_article=KB0016522|here]]. However, we recommend using Linux, as the instructions below apply for the linux installation.+To better understand its structure, ​you can use the following commands:
  
-==== Get information about your card ====+<code bash> 
 +openssl dhparam -in dhparam.pem -text -noout 
 +openssl asn1parse < dhparam.pem 
 +</​code>​
  
-Try this with your card in the smartcard reader+Create a Python environment or use an existing one, then install ​the required packages: 
-<​code>​ + 
-pcsc_scan+<​code ​bash
 +python3 -m venv create env 
 +source ./​env/​bin/​activate 
 +pip install --upgrade pip 
 +pip install pycryptodome pyasn1 pyasn1-modules
 </​code>​ </​code>​
  
-This should show you applications on the cardIf not, don't worry, we'll do it ourselves below.+We will use the ''​%%pyasn1%%''​ library to parse the ''​%%.pem%%'' ​file's binarized DER format more conveniently.
  
-==== Writing a terminal emulator to interact with your card ====+==== 1. Implement DH + RSA signature (3p) ==== 
  
-Start with files for accessing card data in {{:​ac:​laboratoare:​lab_emv_py3.zip|this}} zip file.  +Starting from {{:​ac:​laboratoare:​lab_tofu.zip |these files}}, solve the TODO 1 series in ''​%%dhe_server.py%%''​ and ''​%%dhe_client.py%%''​
-(for Python 2 you may use the code [[https://​ocw.cs.pub.ro/​courses/​_media/​ac/​laboratoare/​lab_emv.zip|here]],​ but is obsolete +
-and no longer mantained).+
  
-Create a file named terminal.txt that will be populated as mentioned below. +On the server side, you should:
-This file should end with a line containing ​the string '​0000000000'​.+
  
-After updating this file (see below), we can run the terminal in this manner+    * Send the RSA public key to the client. 
-<​code>​ +    * Generate a DH key pair (use the data hardcoded in ''​%%dhparam.pem%%''​and send the public key to the client. 
-python3 sclink.py --scterminal terminal.txt gg +    * Generate a signature over the RSA and DH public keys (concatenate and hash themthen sign using the RSA private key) and send it to the client. Use [[https://​pycryptodome.readthedocs.io/​en/​latest/​src/​signature/​pkcs1_v1_5.html |this]] as your guide. 
-</code>+    * Receive the client'​s DH public key and generate the shared DH secret. 
 +    * Derive a symmetric key from the shared secretUse [[https://​pycryptodome.readthedocs.io/en/​latest/​src/​protocol/​kdf.html#​hkdf |HKDF]] for this.
  
-==== Select finantial app ====+On the client side, you should:
  
-We shall now first select ​the main financial application on the card via the general `1PAY.SYS.DDF01' ​file +    * Receive ​the RSA public key from the server. 
-available on some EMV cards followed by selection of the Application IDSee EMV Book 1, sections 11.3 and 12 for details.+    * Receive ​the server'​s DH public key. 
 +    * Receive the server'​s signature over the RSA and DH public keys and [[https://​pycryptodome.readthedocs.io/​en/​latest/​src/​signature/​pkcs1_v1_5.html |verify]] it using the server's public key. 
 +    * Generate a DH key pair and send the public key to the server. 
 +    * Compute the shared DH secret. 
 +    * Derive a symmetric key from the shared secret.
  
-In summary, the main steps are these:+Generating a signature over the RSA and DH public keys is a way to authenticate the remote host. If the client successfully verifies this signature using the server'​s public keythen the server is authenticated unless the public key itself has been replaced by the attacker in a man in the middle attack. We will look at a way to (mostly) solve this issue in the next task.
  
-  - Send the first SELECT command with `1PAY.SYS.DDF01': 00A404000E315041592E5359532E4444463031 +The symmetric key derived from the shared secret will be used to encrypt the communication between the client and serverAlthough we're stopping ​the tasks herethis key would be the one that you would use to encrypt ​and decrypt ​the communication between ​the client and serverIf you wantyou can check the [[https://pycryptodome.readthedocs.io/​en/​latest/​src/​cipher/​aes.html ​|PyCryptodome documentation]] for more information ​on how to use AESTo see what ciphers SSH usesrun the following ​command:
-  - Decode ​the response using [[http://​www.emvlab.org|emvlab]]. Use the SFI response (e.g. 01concatenated with the record number encoded in the last 3 bits): ​ (SFI << 3) | REC_NUM. E.g. If SFI=01 ​and REC_NUM=1, we get the Reference Control parameter (P2) 0x0C for the READ RECORD command, leading to the READ RECORD command 00B2010C00. +
-  - Check the available apps by sending READ RECORD commands of the form  00B2010C0000B2020C00, etc. Check the responses by decoding them with [[http://www.emvlab.org|emvlab]] +
-  - Eventually select one of them using SELECT, e.g.  +
-  * select particular app: 00A4040007XXXXXXXXXXXXXX (replace the X values based on the Application ID response ​to the 00B2XXX command above). +
-    E.g. to get something like 00A4040007A0000000041010. (If the application has 7 bytes -- 14 hex characters for the Application ID). +
-  * 00A4040007A0000000041010 (this must be updated for your cardbased on the response to the 00B2XXX ​command ​above). +
-  * start transaction with GET PROCESSING OPTS80A80000028300+
  
-Now your terminal.txt file should look something like this +<​code ​bash
-(but again, replace the Application ID with the correct one and also use the correct READ RECORD commands -- from your trials). +ssh -Q cipher
-<​code ​- terminal.txt+
-00A404000E315041592E5359532E4444463031 +
-00B2010C00 +
-00A4040007A0000000041010 +
-80A80000028300 +
-0000000000+
 </​code>​ </​code>​
  
-As mentioned above, now run this terminal emulator with the following code: +==== 2Do you like TOFU? (3p) ==== 
-<​code>​ +
-python3 sclink.py --scterminal terminal.txt gg +
-</​code>​+
  
-==== Reading data from card ====+Now start solving the TODO 2 series by implementing Trust On First Use in ''​%%dhe_client.py%%''​. Do it as follows:
  
-Your next goal is to be able to read all the application files with READ RECORD commands (for each file).+    * Store the public key of the server in a file named ''​%%known_hosts%%''​ in the following format (the same way SSH does it):
  
-In order to find out the present files (which differ from card to card), you need to issue the GET PROCESSING OPTS command above (80A80000028300).+<code text> 
 +hostname1 public_key1 
 +hostname2 public_key2 
 +... 
 +</​code>​
  
-In responseafter you [[http://​www.emvlab.org/​tlvutils/​|TLV decode it]]you will find one or more groups of 4 bytes as follows: +    * If the client already has a public key for the given IP of the servercheck that the public key of the server matches the one that is stored (if it's a first connection - i.e., **First Use** - just store the public key and **Trust** the host). 
-  ​1st byte: SFI << 3 +    If it matches, print a "​connection established"​ message and proceed to use that key for verification of the signature over the DH share of the server. 
-  ​2nd byte: first record_number +    If it doesn'​t ​match, print a suggestive error message and exit.
-  3rd byte: last record_number +
-  4th byte: [you don'​t ​need it]+
  
-<​note>​ +This is very similar to what SSH does when connecting to server using a pair of public/​private keys and is known as Trust On First Use (TOFUauthentication.
-The response of the Get Processing Opts command can vary. Either it is a BER-TLV encoded value and you will see easily the AIP and AFL values, or it is a non TLV result ​(starting with tag 88where the AIP (2 bytes) and the list of AFLs (each of 4 bytes) are just concatenated,​ i.e. you have something like 88 <LEN> <AIP> <​AFL1>​ <​AFL2>​ ... <​AFLn>,​ where n>=1. Each AFL is encoded as mentioned above. +
-</​note>​+
  
-SFI is like a directory with multiple records that can be read.+==== 3PQC ready? (4p) ====
  
-To read a file, you need to issue a READ RECORD command which looks like this: +Now let's update the key exchange ​to use post-quantum cryptography.
-00B2 <​record_number>​ <SFI || 100>+
  
-The <​record_number>​ is a byte (you need to write READ RECORD command for each record_number)+For this you can either modify the work you did above to use post-quantum key exchange method such as ML-KEM in Python
-<SFI || 100> is byte which contains the SFI number in the first 5 bits and 100 in the last 3 bits. This is the same as SFI << 3 + 0x04+To do this in Python you can use library such as [[https://​pypi.org/​project/​mlkem/​|ml-kem]]
- +You can install ​this as follows:
-For example, if your AFL shows like "10 01 05 01", then you might want to read records between 01 and 05 using SFI 01 + 0x04, i.e. issuing READ RECORD commands like this:+
 <​code>​ <​code>​
-00 B2 01 14 00+pip install ml-kem
 </​code>​ </​code>​
  
-==== Read public key material ====+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
  
-Using the READ RECORD commands mentioned earlier and the [[https://​emvlab.org/​tlvutils/​|TLV decoder]], find the public keys in +from mlkem.ml_kem import ML_KEM 
-your card, in particular:+from mlkem.parameter_set import ML_KEM_768 # Recommended security level 
 +import secrets
  
-  * Issuer public key certificate +# --- Server (Alice) Side ---
-  * Issuer public key exponent +
-  * Issuer public key reminder +
-  * ICC public key certificate +
-  * ICC public key exponent +
-  * ICC public key reminder+
  
-<​note>​ +def server_keygen():​ 
-Depending on the application selected, you might have (or NOT) public ​keys available. If you don't find onesthen just select a different app at the beginning+    """​Alice generates her ML-KEM key pair."""​ 
-</​note>​+    # 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 (dkprivate) 
 +    ek, dk = ml_kem.key_gen() 
 +     
 +    print("​Alice:​ Generated Public Key (ek) and Private Key (dk)."​) 
 +    return ek, dk, ml_kem
  
-==== Get Dynamic signature from card ====+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
  
-After you get all the public key data, use an INTERNAL AUTHENTICATE command similar to this: 00880000043085C163. +# --- Client ​(BobSide ---
-See the file trace_emv.txt for an example of trace as model for the set of commands you might have to issue (i.e. to add to your terminal.txt file[[https://​ocw.cs.pub.ro/​courses/​_media/​ac/​laboratoare/​lab_emv.zip|here]].+
  
 +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
  
-As discussed in class (see also the [[https://​www.emvco.com/​specifications/​book-2-security-and-key-management-2/|EMV book 2]], section 6), modern EMV cards generally support dynamic signature generation (DDA). +--- Communication Flow Simulation ​---
-This works as follows: +
-  * The terminal issues the INTERNAL AUTHENTICATE command with some random data (typically 4 bytes) +
-  * The ICC makes a signature over some internal ICC data and the random bytes from the terminal +
-  * The ICC sends the signature (signed dynamic data) to the terminal in response to the INTERNAL AUTHENTICATE command +
-  * The terminal verifies the signature using a chain of certificates+
  
-An example of an INTERNAL AUTHENTICATE command similar is the following: 00880000043085C163+# 1Server (Alice) Key Generation 
-You can look at the file trace_emv.txt for an example of trace [[https://​ocw.cs.pub.ro/​courses/​_media/​ac/​laboratoare/​lab_emv.zip|here]]+ek_server, dk_server, ml_kem_instance = server_keygen()
  
-<​note>​+# 2. Public Key Transmission (ek_server is sent to the client) 
 +print("​\n--- Network Transmission:​ ek sent to Bob ---")
  
-If your card doesn'​t work with the standard Payment application ID (the one in terminal.txt), try using one from +# 3. Client ​(BobEncapsulation 
-[[https://​www.eftlab.com/​knowledge-base/​211-emv-aid-rid-pix/​|here]].+K_clientc_client = client_encapsulate(ek_server)
  
-A short list might be this one: +# 4Ciphertext Transmission ​(c_client is sent back to the server
-<​code>​ +print("\n--- Network Transmissionc sent to Alice ---")
-// EMV.AIDLIST:​ +
-EMV.AIDLIST = new Array(); +
-EMV.AIDLIST[0] = { aid : "A00000002501",​ partial : true, name : "AMEX" }; +
-EMV.AIDLIST[1] = { aid : "​A0000000031010",​ partial : false, name : "​VISA"​ }; +
-EMV.AIDLIST[2] = { aid : "​A0000000041010",​ partial : false, name : "​MC"​ }; +
-</​code>​+
  
-Check that you obtained a correct DDA signature and a successful "​9000"​ response.+# 5Server (Alice) Decapsulation 
 +K_server = server_decapsulate(dk_server,​ c_client, ml_kem_instance)
  
-To verify the DDA signature obtained earlier, the terminal must have access to the root CA public keys+# 6Verification 
-You may find some of these available +print("​\n--- Verification ---") 
-[[https://developer.elavon.com/​na/​docs/​viaconex/​1.0.0/​emv-integration-guide/​10_references/​emv_production_public_keys.md|here]]+if K_client is not None and K_server is not None: 
-[[https://​technologypartner.visa.com/​download.aspx?​id=34|here]],​ +    if K_client == K_server: 
-[[https://www.mastercard.us/​content/​dam/​public/​mastercardcom/​na/​us/​en/​documents/​mchip-payment-system-public-keys-12042018.pdf|here]],​ +        print("​Success! Alice'​s and Bob's shared secrets match.") 
-or +        # The shared secret can now be used as an AES key, e.g., K_client 
-[[https://​www.eftlab.com/knowledge-base/​243-ca-public-keys/​|here]].+        # 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>
  
-You will need to know the card type (AMEX, VISA, Mastercard, etc.) and CA public key index, which is given by the ICC (see tag 8F). 
  
 +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.
  
-</note>+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:
  
-The process to verify a DDA signature is as follows: +<​code>​ 
-  * The terminal verifies (RSA decrypts) the signed Issuer public key data (read from the ICC) using the CA public key, obtaining the Issuer public key +sudo apt install astyle cmake gcc ninja-build libssl-dev python3-pytest python3-pytest-xdist unzip xsltproc doxygen graphviz python3-yaml valgrind 
-  * The terminal verifies (RSA decrypts) the signed ICC public key data (read from the ICC) using the Issuer public key, obtaining the ICC public key +</​code>​
-  * The terminal verifies (RSA decrypts) the signed DDA data using the ICC public key (read from the ICC via the INTERNAL AUTHENTICATE command)+
  
-At each stepthe verification step includes decryption ​of the data and checking that the hash over the fields mentioned ​in Book 2 of EMV matches ​the hash in the decrypted data.+If using this libraryyou may use [[https://​github.com/​open-quantum-safe/​liboqs/​blob/​main/​tests/​example_kem.c |this example]] to implement your own version ​of key exchange ​and test it in the scenarios above (key exchange and key exchange + TOFU). You may use any resources or available KEMs.
  
-=== Verify the signature returned by your card ===+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]].
  
-In short, you need to do the following ​(see EMV Book 2sections 6, 6.1, 6.2, 6.3, 6.4 and 6.5) +Below is an example code obtained through Gemini AI (not fully testedbut perhaps a good starting point): 
-  * Decrypt Issuer public key from Issuer Certificate Public key using the root CA public key of your card scheme (section 6.3) +<​code>​ 
-  * Decrypt ICC public key from ICC Certificate Public Key using the Issuer public key (section 6.4) +#include <stdio.h> 
-  * Decrypt DDA signature returned by the card using the ICC public key (section 6.5) +#include <stdlib.h> 
-  * Verify the DDA signature (section 6.5)+#include <string.h> 
 +#include <​openssl/​evp.h> 
 +#include <​openssl/​err.h> 
 +#include <​openssl/​provider.h>
  
-To recover the data from the issuer public key certificate ​(same applies to the other signatures), you may also find useful the following notes (which are based on the EMV specs mentioned aboveplease refer to those).+// --- HARDCODED ML-KEM-768 Public Key (Replace with a real key--- 
 +// A real ML-KEM-768 public key is 608 bytes long. 
 +// This is a placeholder for demonstration. 
 +unsigned char server_public_key_bytes[] = { 
 +    0x000x010x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 
 +    // ... 592 more bytes of a real ML-KEM-768 public key would go here ... 
 +    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 
 +    // Note: This placeholder is NOT a valid ML-KEM-768 key and will likely fail. 
 +    // Replace with a 608-byte key generated via '​openssl genpkey'​ for real use. 
 +}; 
 +size_t public_key_len = 608; // ML-KEM-768 Public Key size
  
-  * First, generate a template ASN1 file as follows: +// Error handling macro 
-<file asn1 '​template.asn1'>​ +#define HANDLE_ERROR(msg) \ 
-# Start with a SEQUENCE +    { \ 
-asn1=SEQUENCE:​pubkeyinfo+        fprintf(stderr,​ "%s failed.\n", msg); \ 
 +        ​ERR_print_errors_fp(stderr);​ \ 
 +        goto cleanup; \ 
 +    }
  
-# pubkeyinfo contains an algorithm identifier and the public key wrapped +int main(void) { 
-# in a BIT STRING +    ​EVP_PKEY_CTX *kctx = NULL; 
-[pubkeyinfo+    ​EVP_PKEY *peer_pub_key = NULL; 
-algorithm=SEQUENCE:​rsa_alg +    OSSL_PARAM params[2]
-pubkey=BITWRAP,​SEQUENCE:​rsapubkey+    unsigned char *shared_secret = NULL; 
 +    unsigned char *ciphertext = NULL; 
 +    size_t shared_secret_len = 0; 
 +    ​size_t ciphertext_len ​0; 
 +    int ret 0;
  
-# algorithm ID for RSA is just an OID and a NULL +    printf("​Starting ML-KEM Client Key Encapsulation (ML-KEM-768).\n"​);​
-[rsa_alg] +
-algorithm=OID:​rsaEncryption +
-parameter=NULL+
  
-# Actual public key: modulus and exponent +    // 1. Load the OpenSSL default provider (ML-KEM is in the default provider since 3.5) 
-[rsapubkey] +    if (!OSSL_PROVIDER_load(NULL,​ "​default"​)) { 
-n=INTEGER:​0x%%MODULUS%%+        ​HANDLE_ERROR("​Failed to load default provider"​);​ 
 +    }
  
-e=INTEGER:​0x%%EXPONENT%% +    ​// 2. Import the raw public key bytes into an EVP_PKEY structure 
-</file>+    printf("​2. Importing ML-KEM-768 Public Key...\n"​);​
  
-  * Then use this template for all the keys you need to generate. For example, for the CA root key, use the template and replace the %%MODULUS%% and %%EXPONENT%% part by the modulus and exponent bytes given in the list of public CA root public keys for your card. Say the resulting file is named ca_pk.asn1. +    // a. Create parameter array to describe ​the key components 
-  * Then use openssl asn1 parser to obtain a public key in DER format as follows: +    ​params[0] = OSSL_PARAM_construct_octet_string("pub", server_public_key_bytes,​ public_key_len); 
-  <​code>​ +    ​params[1] = OSSL_PARAM_construct_end();
-  openssl asn1parse -genconf ca_pk.asn1 -out ca_pk.der -noout +
-  </code> +
-  * Now copy the Issuer Certificate Public Key bytes obtained from the card into file, say issuer_pk.bytes and then convert this to a binary file like this: +
-  <​code>​ +
-  cat issuer_pk.bytes | xxd -r -p > issuer_pk.bin +
-  </​code>​ +
-  * At this point you can verify/​decrypt the issuer certificate using openssl as follows: +
-  <​code>​ +
-  openssl rsautl -verify -in issuer_pk_cert.bin -inkey ca_pk.der -pubin -keyform DER -raw +
-  </​code>​ +
-  Although it might be more convenient to see the output in hexa, using something like this: +
-  <​code>​ +
-  openssl rsautl -verify -in issuer_pk_cert.bin -inkey ca_pk.der -pubin -keyform DER -raw | xxd -p +
-  </​code>​ +
-   +
-To understand the meaning of the decrypted bytes, please refer to the respective EMV documentation (in particular sections 6.2-6.5  +
-in book 2). For example, for the Issuer public ​key certificate,​ to obtain the actual issuer public key you need to ignore the first  +
-15 bytes (metadata) as well as the last 21 bytes (hash result and trailer value "BC"). The reminder bytes are the first part of the  +
-Issuer Public key. For the second part of the Issuer Public key (which you need to concatenate to the first part to get the full  +
-public key), please see the card response with tag 92 (Issuer Public Key reminder).+
  
-Apply the same/similar process to get the ICC public ​key and finally to decrypt/verify ​the DDA signature.+    ​// b. Import ​the key. ML-KEM-768 is named "​ML-KEM-768"​ 
 +    peer_pub_key = EVP_PKEY_new_from_params(NULL,​ params, "​ML-KEM-768"​);​ 
 +    if (!peer_pub_key) { 
 +        ​// NOTE: This will fail if the hardcoded bytes are not a valid key. 
 +        HANDLE_ERROR("​EVP_PKEY_new_from_params failed (Public Key Import)"​);​ 
 +    }
  
-<​note>​ +    // 3. Initialize the Key Encapsulation Context 
-Check the decrypted DDA response format in the EMV specs (book 2section 6.5). +    kctx = EVP_PKEY_CTX_new_from_pkey(NULLpeer_pub_key,​ NULL); 
-The response should follow this format ​(but please check it to confirm): +    ​if ​(!kctx{ 
-  * response length ​(1 byte-- say N +        ​HANDLE_ERROR("​EVP_PKEY_CTX_new_from_pkey failed"​); 
-  * signature (N - 21 bytes) +    }
-  * a SHA-1 hash over 20 bytes (20*8 = 160 bits)  +
-  * a trailer byte with value "​BC"​+
  
-The hash contained in the DDA reponse is computed over the signature bytes (N - 21 bytesconcatenated with the data sent for the DDA signature ​(typically the 4 random/​unpredictable bytes). Hence, if you recompute the hash over the N-21 signature bytes concatenated with the 4 random bytes and this matches the 20 bytes of the hash in the DDA response this should confirm that the 4 random bytes were correctly input into the signature generation. +    if (EVP_PKEY_encapsulate_init(kctx<= 0) { 
-</​note>​+        HANDLE_ERROR("​EVP_PKEY_encapsulate_init failed"​); 
 +    }
  
 +    // 4. Determine buffer sizes
 +    printf("​4. Determining buffer sizes...\n"​);​
 +    if (EVP_PKEY_encapsulate(kctx,​ NULL, &​shared_secret_len,​ NULL, &​ciphertext_len) <= 0) {
 +        HANDLE_ERROR("​EVP_PKEY_encapsulate (size determination) failed"​);​
 +    }
 +    ​
 +    printf(" ​  - Shared Secret Length: %zu bytes\n",​ shared_secret_len);​
 +    printf(" ​  - Ciphertext Length: %zu bytes\n",​ ciphertext_len);​
  
-=== Responses from a card ===+    // 5. Allocate buffers 
 +    shared_secret ​OPENSSL_malloc(shared_secret_len);​ 
 +    ciphertext ​OPENSSL_malloc(ciphertext_len);​
  
 +    if (!shared_secret || !ciphertext) {
 +        HANDLE_ERROR("​Memory allocation failed"​);​
 +    }
  
-=== In case of trouble use existing signature ===+    // 6. Perform the Encapsulation (Client'​s Key Exchange Step) 
 +    printf("​6. Performing Key Encapsulation...\n"​);​ 
 +    if (EVP_PKEY_encapsulate(kctx,​ shared_secret,​ &​shared_secret_len,​ ciphertext, &​ciphertext_len) <0) { 
 +        HANDLE_ERROR("​EVP_PKEY_encapsulate failed"​);​ 
 +    }
  
-If you don't manage to get signature from your card, use these responses from a card (decode them with TLV decode):+    printf("​✅ Encapsulation successful!\n"​);​ 
 +    printf(" ​  The client has generated ​**Shared Secret** and the **Ciphertext**.\n"​);​ 
 +     
 +    printf(" ​  - Shared Secret (First 16 bytes): "); 
 +    for (size_t i = 0; i < (shared_secret_len > 16 ? 16 : shared_secret_len);​ i++) { 
 +        printf("​%02x",​ shared_secret[i]);​ 
 +    } 
 +    printf("​...\n"​);​
  
 +    printf(" ​  - Ciphertext (First 16 bytes, to be sent to Server): ");
 +    for (size_t i = 0; i < (ciphertext_len > 16 ? 16 : ciphertext_len);​ i++) {
 +        printf("​%02x",​ ciphertext[i]);​
 +    }
 +    printf("​...\n"​);​
  
-Start from the following responses of a card (decode them with [[http://www.emvlab.org/tlvutils/|TLV decode]])+    ret = 0; // Success 
 +     
 +cleanup: 
 +    ​// Free all allocated resources 
 +    EVP_PKEY_CTX_free(kctx)
 +    EVP_PKEY_free(peer_pub_key);​ 
 +    OPENSSL_free(shared_secret);​ 
 +    OPENSSL_free(ciphertext);​ 
 +    OSSL_PROVIDER_unload(OSSL_PROVIDER_load(NULL,​ "​default"​));​ // Unload provider
  
-<​code>​ +    if (ret != 0) { 
-> 00 B2 01 14 8A +        ​return EXIT_FAILURE;​ 
-< 70 81 87 5F 25 03 08 02 01 5F 24 03 12 02 29 5A 08 54 00 49 51 48 65 15 96 5F 34 01 00 9F 07 02 FF 00 8E 14 00 00 00 00 00 00 00 00 42 01 44 03 41 03 42 03 5E 03 1F 03 8C 21 9F 02 06 9F 03 06 9F 1A 02 95 05 5F 2A 02 9A 03 9C 01 9F 37 04 9F 35 01 9F 45 02 9F 4C 08 9F 34 03 8D 0C 91 0A 8A 02 95 05 9F 37 04 9F 4C 08 9F 0D 05 F8 50 AC 08 00 9F 0E 05 00 00 00 00 00 9F 0F 05 F8 70 AC 98 00 5F 28 02 06 42 9F 4A 01 82+    } 
 +    return EXIT_SUCCESS;​ 
 +}
 </​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>​ <​code>​
-> 00 B2 02 14 47 +openssl genpkey -algorithm ML-KEM-768 -out server_private.pem 
-< 70 45 9F 08 02 00 02 57 13 54 00 49 51 48 65 15 96 D1 20 22 01 00 00 08 28 00 00 0F 5F 20 17 43 48 4F 55 44 41 52 59 20 2F 4F 4D 41 52 20 53 41 4C 49 4D 20 44 4C 5F 30 02 02 01 9F 42 02 09 78 9F 44 01 02 8F 01 04+openssl pkey -in server_private.pem -pubout -out server_public.pem
 </​code>​ </​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>​ <​code>​
-> 00 B2 03 14 96 +gcc -o mlkem_client mlkem_client.c -I/​path/​to/​openssl/​include -L/​path/​to/​openssl/​lib -lssl -lcrypto
-< 70 81 93 90 81 90 0B 69 37 0D CF E1 E7 B0 9C 00 6F CC 12 91 38 C0 7A 69 80 87 3C 1E 0A 60 04 E6 8E 23 F5 BF B7 51 08 28 00 8B 37 F4 C3 D3 30 6A 0D AE 70 92 51 2F FB B1 E8 1E AE 26 23 1A 0D BF C8 30 B3 1C F1 F6 81 9C F3 12 37 FE 74 B3 5C 5B 57 62 0A 4D C1 96 ED 06 CC 94 45 AC 0A 5B 00 BB 8E BA 7F B4 1D 97 4C A1 F9 DD A4 45 1E B3 2E FC 55 5A 16 9D 60 09 47 4E 97 09 2B 33 21 AD D5 9D 1C 35 30 11 CC C1 C1 D6 19 65 B9 12 0E 07 FC 8F B3 72 4A C0 3A 15+
 </​code>​ </​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>​ <​code>​
-> 00 B2 04 14 CA +#!/bin/bash 
-< 70 81 C7 9F 32 01 03 92 24 EF 2F D9 E8 3B D9 0C 88 A1 A6 58 84 B3 5A 63 69 08 40 36 59 83 1A FB 41 74 3C 61 E5 0F CE 32 49 1C D3 9A 81 93 81 90 86 09 6D 87 91 88 BB 1C 0C 5C C2 3D E0 85 79 96 E8 50 46 9B FF 6A F9 39 C7 3C D6 DE 78 67 75 F3 F7 1E C1 94 B0 05 2E B1 CE C5 2B 02 BD 41 CD 2D BA A5 E0 84 C5 29 00 68 90 17 FD 57 A8 4B 47 BC 8D 33 CD 1E D5 B4 81 87 B1 E0 82 F2 5B 4E F1 32 89 65 5A 2E 66 90 1F 98 96 9D 9E 9C C3 A7 30 D0 84 D5 FA DD 83 EA A7 9B 28 2B 02 E4 AA CB 5C E8 FC AC 77 0A FD EA EB 4B 8E 37 51 AD D8 25 F3 7B 36 DB 5E 45 BE D7 23 3A 50 85 29 8E 98 0B 07 5C 9F 49 03 9F 37 04 9F 47 01 03+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>​ </​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>​ <​code>​
-> 00 B2 05 14 B4 +echo "--- 2. Extracting RAW Public Key (608 bytes) ---" 
-< 70 81 B1 9F 46 81 90 8A A7 53 01 F7 40 51 03 68 95 94 6E EC CE 31 CF 9E CA A4 5E 77 FF 4F A3 39 B2 BF F9 00 49 15 26 A0 82 1F 5B 42 F5 7A 78 BD F8 4A 5B 6F C6 37 56 1B 1B F5 FB 68 C9 2E 58 C4 31 F1 D6 85 DE 5C F7 C1 9D BF 3A 05 E9 46 DC 6A C7 E6 E7 4B 43 A8 6F C0 FE 1C E9 6F C2 EB 86 F2 05 0B C1 AC 0F 70 1D B0 A9 38 84 2D BD AA F0 2D B7 9B C9 32 D2 0E 62 62 8C C4 01 F4 FA 19 93 36 0B 7C 30 FD A6 6C CF 35 C3 8F FF A1 71 19 4C 58 53 E1 E9 F0 08 59 8B 9F 48 1A F0 62 87 E0 BA F1 0B 92 F5 8E 4A 7A 09 8C 49 EF 3E 38 31 58 F6 FF B3 45 39 85+ 
 +# 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>​ </​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>​ <​code>​
-> 00 88 00 00 04 30 85 C1 63 +echo "--- 3. Extracting RAW Private Key (1184 bytes) ---" 
-<  [] 61 87 + 
-135 +# The ML-KEM-768 private key (dk) is 1184 bytes long. 
-> 00 C0 00 00 87 +# We convert to DER and again skip the ASN.1 header bytes. 
-< 77 81 84 9F 4B 81 80 99 0B 9E 27 E7 51 B7 45 5F F8 64 82 3C 82 35 94 CD E0 26 AB 9A D9 1D 02 7F 6E B8 5E DE 27 23 B7 81 40 0D BD 85 FD 20 21 07 08 A3 6C B3 09 51 33 B2 D7 30 BF 12 8B 23 C9 4D 87 3F 28 56 63 1C 19 F5 21 BB BC E2 2F 45 B2 C9 0A 7E B2 F5 C7 02 03 3C B3 AB 1C 06 F5 5A CB 44 3A E0 93 84 42 33 FF 16 D0 CE BB 75 6C E1 39 09 A6 39 5A 89 48 D1 9A BF D8 5E 29 43 0F A0 CC 16 90 7A 5C 92 CA 74 3F+# 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>​ </​code>​
  
-For this card the public ​key modulus is this (1152 bit):+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>​ <​code>​
-A6DA428387A502D7DDFB7A74D3F412BE762627197B25435B7A81716A700157DDD06F7CC99D6CA28C2470527E2C03616B9C59217357C2674F583B3BA5C7DCF2838692D023E3562420B4615C439CA97C44DC9A249CFCE7B3BFB22F68228C3AF13329AA4A613CF8DD853502373D62E49AB256D2BC17120E54AEDCED6D96A4287ACC5C04677D4A5A320DB8BEE2F775E5FEC5 +#include <​stdio.h>​ 
-</code>+#include <​stdlib.h>​ 
 +#include <​string.h>​ 
 +#include <​openssl/​evp.h>​ 
 +#include <​openssl/​err.h>​ 
 +#​include ​<openssl/provider.h>
  
-And the exponent ​is 0x03.+// --- 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
  
-In case of big big trouble, is the ASN1 file that you should obtain for the Issuer Public Key: +// --- HARDCODED Ciphertext (Generated by the Client - Replace with real data) --- 
-<code asn1 issuer_pk.asn1> +// A real ML-KEM-768 ciphertext is 1088 bytes long
-# Start with SEQUENCE +unsigned char client_ciphertext_bytes[] = { 
-asn1=SEQUENCE:​pubkeyinfo+    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 
 +    // ... 1072 more bytes of 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
  
-# pubkeyinfo contains an algorithm identifier and the public key wrapped +// --- Hardcoded Shared Secret Size (32 bytes for ML-KEM-768) --- 
-# in a BIT STRING +size_t shared_secret_len ​32;
-[pubkeyinfo] +
-algorithm=SEQUENCE:​rsa_alg +
-pubkey=BITWRAP,​SEQUENCE:​rsapubkey+
  
-algorithm ID for RSA is just an OID and a NULL +// Error handling macro 
-[rsa_alg] +#define HANDLE_ERROR(msg) \ 
-algorithm=OID:​rsaEncryption +    { \ 
-parameter=NULL+        fprintf(stderr,​ "%s failed.\n",​ msg); \ 
 +        ​ERR_print_errors_fp(stderr);​ \ 
 +        goto cleanup; \ 
 +    }
  
-# Actual public key: modulus and exponent +int main(void) { 
-[rsapubkey+    ​EVP_PKEY_CTX *kctx = NULL; 
-n=INTEGER:​0xcb3a3f60cceccabb300a57d0c7c7fc974a34d8fa4728b9bb4719fec80a41b0cd04eb2c9fdfdd9139f87de2b3cbee69ecdf2889a37888beadc7a5ed5cc51da52940b000ef806ab277d0276386493da941f390f8a1354a3040dc84a7611b0a6e46874efd463a0f0607459ea58eEF2FD9E83BD90C88A1A65884B35A636908403659831AFB41743C61E50FCE32491CD39A81+    EVP_PKEY *server_priv_key = NULL; 
 +    OSSL_PARAM params[2]
 +    unsigned char *recovered_secret = NULL; 
 +    int ret 0;
  
-e=INTEGER:​0x3 +    printf("​Starting ML-KEM Server Key Decapsulation (ML-KEM-768).\n"​);​
-</​code>​+
  
 +    // 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.1705578500.txt.gz · Last modified: 2024/01/18 13:48 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