I'm currently implementing ISO 18013-5 (mobile driving license) using Dart and Flutter (sorry, can't share the full specification document). Briefly, in order to prevent eavesdropping during a data exchange between a reader (e.g. Police) and a citizen, two ephemeral keys (EDeviceKey
and EReaderKey
) are generated (using ECDH), one in each device. After, the public keys are exchanged to create one Session Key per device (skDevice
and skReader
). These keys are derived using ECKA-DH (as defined in BSI TR-03111).
In order to explain my struggle, lets focus on one application, the citizen: To derive the session key, I need to use HKDF, with the citizen's private key and the reader's public key. For that I have the following:
Future<SimpleKeyPair> generateEDeviceKeyPair() async {
final algorithm = Ecdh.p256(length: 256);
final keyPair = await algorithm.newKeyPair();
return keyPair;
}
// The public key will be generated on the reader application, using this method
Future<EcKeyPair> generateEReaderKeyPair() async {
final algorithm = Ecdh.p256(length: 256);
final keyPair = await algorithm.newKeyPair();
return keyPair;
}
Future<SecretKey> generateSKDevice(EcKeyPairData eDeviceKeyPriv, EcKeyPublic eReaderKeyPub) async {
final zAB = null;
final algorithm = Hkdf(
hmac: Hmac(Sha256()),
outputLength: 32,
);
// HOW TO "MERGE" BOTH KEYS?
final secretKey = eDeviceKeyPriv + eReaderKeyPub // ??
final output = await algorithm.deriveKey(secretKey: secretKey, info: utf8.encode("SKDevice"));
return output;
}
Then, to use the Session Key to encrypt the data:
Future<List<int>> encryptMDocResponse(Object mdocResponse, SecretKey skDevice) async {
final algorithm = AesGcm.with256bits();
// Encrypt
final secretBox = await algorithm.encrypt(
utf8.encode(json.encode(mdocResponse)),
secretKey: skDevice
);
return secretBox.cipherText;
}
The problem lies in the generateSKDevice
method. It has to receive the private key from the citizen (eDeviceKeyPriv
) and the public key from the reader (eReaderKeyPub
). My question is: how to merge both keys in order to derive one, using the deriveKey
method (crypto lib: https://pub.dev/packages/cryptography)? Making the parallel to Javascript, I could use the crypto.subtle.deriveKey
method.
Since this is my first time working with cryptography, I hope this isn't a dummy question.
Thank you in advance!
EDIT:
import 'package:cryptography/cryptography.dart';
import 'dart:convert';
Object getMDoc() {
return {
"test": "test",
"test2": {"a": "b"},
"test3": 3
};
}
Future<SimpleKeyPair> generateEDeviceKeyPair() async {
final algorithm = X25519();
final keyPair = await algorithm.newKeyPair();
return keyPair;
}
// Changed algorithm to X25519
Future<SimpleKeyPair> generateEReaderKeyPair() async {
final algorithm = X25519();
final keyPair = await algorithm.newKeyPair();
return keyPair;
}
// Generated a sharedSecretKey to derive the session key
Future<SecretKey> generateSKDevice(
SimpleKeyPairData eDeviceKeyPriv, SimplePublicKey eReaderKeyPub) async {
final algorithm = X25519();
final sharedSecretKey = await algorithm.sharedSecretKey(
keyPair: eDeviceKeyPriv, remotePublicKey: eReaderKeyPub);
final deriveAlgorithm = Hkdf(
hmac: Hmac(Sha256()),
outputLength: 32,
);
final skDevice = await deriveAlgorithm.deriveKey(
secretKey: sharedSecretKey,
info: utf8.encode("SKDevice"),
nonce: <int>[1, 2]);
return skDevice;
}
Future<SecretKey> generateSKReader(
SimpleKeyPairData eReaderKeyPriv, SimplePublicKey eDeviceKeyPub) async {
// final algorithm = Ecdh.p256(length: 256);
final algorithm = X25519();
final sharedSecret = await algorithm.sharedSecretKey(
keyPair: eReaderKeyPriv, remotePublicKey: eDeviceKeyPub);
final deriveAlgorithm = Hkdf(
hmac: Hmac(Sha256()),
outputLength: 32,
);
final output = await deriveAlgorithm.deriveKey(
secretKey: sharedSecret,
info: utf8.encode("SKReader"),
nonce: <int>[1, 2]);
return output;
}
Future<SecretBox> encryptMDocResponse(
Object mdocResponse, SecretKey skDevice) async {
final algorithm = AesGcm.with256bits();
final secretBox = await algorithm.encrypt(
utf8.encode(json.encode(mdocResponse)),
secretKey: skDevice,
);
return secretBox;
}
Future<List<int>> decryptMDocResponse(
SecretBox mdocResponse, SecretKey skReader) async {
final algorithm = AesGcm.with256bits();
final chipherText = await algorithm.decrypt(
mdocResponse,
secretKey: skReader,
);
return chipherText;
}
// Example of use
Future<void> main(List<String> arguments) async {
// Holder: get mdoc
final mdoc = getMDoc();
// Holder & Reader: generate Ephemeral Keys
final eDeviceKey = await generateEDeviceKeyPair();
final eReaderKey = await generateEReaderKeyPair();
// Holder: generate Session Key (skDevice)
final skDevice = await generateSKDevice(
await eDeviceKey.extract(), await eReaderKey.extractPublicKey());
// Holder: encrypt device response
final response = await encryptMDocResponse(mdoc, skDevice);
// Reader: generate Session Key (skDevice)
final skReader = await generateSKReader(
await eReaderKey.extract(), await eDeviceKey.extractPublicKey());
// Reader: decrypt device response
final decrypted = await decryptMDocResponse(response, skReader); // Error here
}
As discussed with Topaco in the comments:
X25519
in order to be supported in mobile devices by Flutter;sharedSecret
:info
parameter across applications(The remaining code is available in the EDITed section of the original post)