Shared Secret Derivation
This document defines a SharedSecret interface responsible for deriving shared secret information on both sides of the communication. The document also contains a specification for ECDHE-KEM.
The SharedSecret interface is using the following entities:
SharedSecretRequestan object representing a request data generated on the client and received by the server.SharedSecretResponsean object representing a response data generated on the server and received by the client.SharedSecretClientContextis object representing a data required for proper shared secret deduction on the client. The object is defined asList<PrivateKey>.
Request
The SharedSecretRequest object contains the following properties:
| Property | Type | Description |
|---|---|---|
| algorithm | String | Selected algorithm |
| encapsulationKeys | List<byte[]> | Array with encapsulation keys, each key encoded as Base64 |
Response
The SharedSecretResponse object contains the following properties:
| Property | Type | Description |
|---|---|---|
| salt | byte[] | Salt used for the shared secret derivation, encoded as Base64 |
| encapsulatedKeys | List<byte[]> | Array with encapsulated keys, each key encoded as Base64 |
Algorithm
Input parameters:
String VERSION- protocol version, for example “4.0”String ALGORITHM- selected algorithm name.List<KEM> KEM_ALGORITHMS, array of KEM implementations used for the shared secret computation.
Implementation:
- Let’s compute common constants:
final String LABEL = "shared-secret/" + ALGORITHM; - Let’s define the final derivation function that corresponds to NIST SP 800-56C-rev2:
SecretKey computeSharedSecret(List<SecretKey> secretKeys, byte[] salt) { // Concatenate all secrets byte[] concatenatedSecrets = ByteUtils.concatWithSizes(secretKeys.toArray(new byte[0][]); byte[] fixedInfo = ByteUtils.concatWithSizes(LABEL, VERSION); // Due to fact that we always require 256-bits long key, we need just one iteration. // This is because output length is equal to KMAC's output length. byte[] X = ByteUtils.concat( ByteUtils.encode((int)1), // counter concatenatedSecrets, // || Z fixedInfo // || fixedInfo ); byte[] custom = ByteUtils.encode("KDF"); byte[] SHARED_SECRET = Mac.kmac256(salt, X, 32, custom); return KeyConversion.secretKeyFromBytes(SHARED_SECRET); } Pair<SharedSecretRequest, SharedSecretClientContext> SharedSecret.generateRequestCryptogram()List<PrivateKey> decapsulationKeys = new ArrayList<PrivateKey>(); List<String> encapsulationKeys = new ArrayList<String>(); for (KEM kem: KEM_ALGORITHMS) { // For each KEM algorithm, do KeyPair keyPair = kem.generateKeyPair(); decapsulationKeys.add(keyPar.getPrivateKey()); encapsulationKeys.add(KeyConversion.getBytes(keyPair.getPublicKey())); } SharedSecretRequest request = new SharedSecretRequest(); request.setAlgorithm(ALGORITHM); request.setEncapsulationKeys(encapsulationKeys); // decapsulationKeys type is equal to SharedSecretClientContext return new Pair<>(request, decapsulationKeys);Pair<SharedSecretResponse, SecretKey> ServerSharedSecret.generateResponseCryptogram(SharedSecretRequest request)String selectedAlgorithm = request.getAlgorithm(); List<byte[]> encapsulationKeys = request.getEncapsulationKeys(); // Input request validation if (!selectedAlgorithm.equals(ALGORITHM)) { throw Exception("Unexpected algorithm"); } if (encapsulationKeys.size() != KEM_ALGORITHMS.size()) { throw Exception("Unexpected count of encapsulation keys"); } // Now iterate over all KEMs and call encapsulate() List<SecretKey> secretKeys = new ArrayList<SecretKey>(); List<byte[]> encapsulatedKeys = new ArrayList<byte[]>(); for (int i = 0; i < KEM_ALGORITHMS.size(); i++) { KEM kem = KEM_ALGORITHMS.get(i); PublicKey encapKey = KeyConversion.publicKeyFromBytes(encapsulationKeys.get(i)); Pair<SecretKey, byte[]> secretWithEncapsulated = kem.encapsulate(encapKey); secretKeys.add(secretWithEncapsulated.getFirst()); encapsulatedKeys.add(secretWithEncapsulated.getSecond()); } // Generate salt byte[] salt = Generator.randomBytes(32); // Calculate shared secret SecretKey sharedSecret = computeSharedSecret(secretKeys, salt); // Build response and return... SharedSecretResponse response = new SharedSecretResponse(); response.setSalt(salt); response.setEncapsulatedKeys(encapsulatedKeys); return new Pair<>(response, sharedSecret);SecretKey SharedSecret.computeSharedSecret(SharedSecretClientContext context, SharedSecretResponse response)List<PrivateKey> decapsulationKeys = context; List<byte[]> encapsulatedKeys = response.getEncapsulatedKeys(); // Input validations if (decapsulationKeys.size() != encapsulatedKeys.size() || encapsulatedKeys.size() != KEM_ALGORITHMS.size()) { throw Exception("Unexpected count of encapsulated keys"); } // Now iterate over all KEMs and call decapsulate List<SecretKey> secretKeys = new ArrayList<SecretKey>(); for (int i = 0; i < KEM_ALGORITHMS.size(); i++) { KEM kem = KEM_ALGORITHMS.get(i); SecretKey secretKey = kem.decapsulate(decapsulationKeys.get(i), encapsulatedKeys.get(i)); secretKeys.add(secretKey); } // Calculate shared secret SecretKey sharedSecret = computeSharedSecret(secretKeys, response.getSalt());
SharedSecret configurations
EC_P384
- ALGORITHM =
"EC_P384" - KEM_ALGORITHMS are:
- DHKEM(P-384, HKDF-SHA384)
EC_P384_ML_L3
- ALGORITHM =
"EC_P384_ML_L3" - KEM_ALGORITHMS are:
- DHKEM(P-384, HKDF-SHA384)
- ML-KEM-768
EC_P384_ML_L5
- ALGORITHM =
"EC_P384_ML_L5" - KEM_ALGORITHMS are:
- DHKEM(P-384, HKDF-SHA384)
- ML-KEM-1024
DHKEM implementation
DHKEM is specified in RFC 9180. We’re using HPKE in “base mode”.
Last updated on Feb 06, 2026 (14:52)
Edit on Github
Send Feedback