0

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
}
Bishop19
  • 37
  • 9
  • 1
    I don't know ISO 18013-5, but I suspect that you have to generate a shared secret with the private and public key (with [`sharedSecretKey()`](https://pub.dev/documentation/cryptography/latest/cryptography/KeyExchangeAlgorithm/sharedSecretKey.html)) and the so generated shared secret (of type `SecretKey`) is in turn the input for the HKDF or [`deriveKey()`](https://pub.dev/documentation/cryptography/latest/cryptography/Hkdf/deriveKey.html) to derive the final key. – Topaco Nov 23 '22 at 17:36
  • @Topaco According to this [issue](https://github.com/dint-dev/cryptography/issues/31), "ECDH and ECDSA are supported only in the browser at the moment". Therefore, I changed the key generation algorithm to Ed25519 (which is another supported by the ISO). However, this type of algorithm doesn't have the method ```sharedSecretKey``` – Bishop19 Nov 24 '22 at 11:08
  • 1
    Ed25519 is used for signing (Curve 25519 equivalent to ECDSA), for a key agreement X25519 (Curve 25519 equivalent to ECDH) is required, which also has a [`sharedSecretKey()`](https://pub.dev/documentation/cryptography/latest/cryptography/X25519-class.html) method. – Topaco Nov 24 '22 at 11:36
  • @Topaco yeah, I noticed that I could use that key agreement and made some changes. Now it's giving ```SecretBox has wrong message authentication code (MAC)``` when trying to decrypt the message (encapsulated in a ```SecretBox```, output of the ```encryptMDocResponse``` method). It's hard for me to debug this `:) . I've updated the original post, with the current code, under 'EDIT'. Sorry for the long post – Bishop19 Nov 24 '22 at 12:37
  • The last posted code is incomplete, so no real analysis is possible. Please complete it so that a repro is possible via copy/paste. Did you check if `skDevice` and `skReader` contain the same key? – Topaco Nov 24 '22 at 13:16
  • @Topaco you should be able to copy/past and reproduce the error – Bishop19 Nov 24 '22 at 13:56
  • 1
    You use different values for `info` in the `Hkdf#deriveKey()` calls, resulting in different keys. Apply the same value, then it works. – Topaco Nov 24 '22 at 15:02
  • @Topaco Thank you so much! It works now. According to the ISO, I should use different ```info``` values (as in https://imgur.com/a/ndi8khp). Do you want to answer the question so I can mark it as resolved? – Bishop19 Nov 24 '22 at 15:10

1 Answers1

1

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)

Bishop19
  • 37
  • 9