How to derive keys (HKDF) from two EC key pairs in Dart/Flutter?

372 views Asked by At

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
}
1

There are 1 answers

0
Bishop19 On BEST ANSWER

As discussed with Topaco in the comments:

  • First: Had to change the algorithm to X25519 in order to be supported in mobile devices by Flutter;
  • Second: in order to derive the session key, I had to create a sharedSecret:
final sharedSecret = await algorithm.sharedSecretKey(keyPair: eReaderKeyPriv, remotePublicKey: eDeviceKeyPub);
  • Third: Use the same info parameter across applications

(The remaining code is available in the EDITed section of the original post)