You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Dart/Flutter中基于EC密钥对通过HKDF派生会话密钥的疑问

问题背景

我正在用Dart和Flutter实现ISO 18013-5(移动驾照)标准(无法提供完整规范文档)。为防止阅读器(如警方设备)与市民设备间的数据交换被窃听,双方需通过ECDH生成临时密钥对EDeviceKey和EReaderKey,交换公钥后基于ECKA-DH(BSI TR-03111定义),使用HKDF派生各自的会话密钥skDevice和skReader。

问题1:ECDH结合HKDF的实现困惑

最初的代码中,我不清楚如何将市民私钥与阅读器公钥结合,传入HKDF的deriveKey方法:

Future<SimpleKeyPair> generateEDeviceKeyPair() async {
  final algorithm = Ecdh.p256(length: 256);

  final keyPair = await algorithm.newKeyPair();

  return keyPair;
}

// 该方法将在阅读器应用中生成公钥
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,
  );

  // 如何“合并”两个密钥?
  final secretKey = eDeviceKeyPriv + eReaderKeyPub // ??

  final output = await algorithm.deriveKey(secretKey: secretKey, info: utf8.encode("SKDevice"));

  return output;
}

问题2:X25519修改后的解密错误

之后我改用X25519算法生成共享密钥再派生会话密钥,但解密环节出现错误:

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

// 改为X25519算法
Future<SimpleKeyPair> generateEReaderKeyPair() async {
  final algorithm = X25519();

  final keyPair = await algorithm.newKeyPair();

  return keyPair;
}

// 生成共享密钥派生会话密钥
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;
}

// 使用示例
Future<void> main(List<String> arguments) async {
  // 市民端:获取mdoc数据
  final mdoc = getMDoc();

  // 市民端和阅读器生成临时密钥对
  final eDeviceKey = await generateEDeviceKeyPair();
  final eReaderKey = await generateEReaderKeyPair();

  // 市民端生成会话密钥skDevice
  final skDevice = await generateSKDevice(
      await eDeviceKey.extract(), await eReaderKey.extractPublicKey());

  // 市民端加密响应数据
  final response = await encryptMDocResponse(mdoc, skDevice);

  // 阅读器生成会话密钥skReader
  final skReader = await generateSKReader(
      await eReaderKey.extract(), await eDeviceKey.extractPublicKey());

  // 阅读器解密响应数据——此处出错
  final decrypted = await decryptMDocResponse(response, skReader);
}

提问

  1. 如何正确结合EC私钥与对方公钥,通过cryptography库的HKDF派生会话密钥?
  2. 修改后的代码为何会出现解密错误?

解答

问题1:ECDH结合HKDF的正确流程

ECDH的核心是先计算共享密钥,再用HKDF派生会话密钥,不是直接将私钥与公钥拼接:

  1. 使用目标ECDH算法(比如P-256),用本地私钥和对方公钥计算出共享密钥(即你代码里注释的zAB)。
  2. 将这个共享密钥作为HKDF的输入secretKey,配合规范要求的info、salt(可选)参数派生会话密钥。

修正后的P-256版本代码:

Future<SecretKey> generateSKDevice(EcKeyPairData eDeviceKeyPriv, EcKeyPublic eReaderKeyPub) async {
  // 用ECDH P-256计算共享密钥
  final ecdh = Ecdh.p256(length: 256);
  final sharedSecret = await ecdh.sharedSecretKey(
    keyPair: eDeviceKeyPriv,
    remotePublicKey: eReaderKeyPub,
  );

  // 用HKDF派生会话密钥
  final hkdf = Hkdf(
    hmac: Hmac(Sha256()),
    outputLength: 32,
  );
  
  // 注意:需严格遵循ISO 18013-5或ECKA-DH对info、salt的要求
  final skDevice = await hkdf.deriveKey(
    secretKey: sharedSecret,
    info: utf8.encode("SKDevice"),
    // 若规范未指定salt,可省略,库会使用全0默认值
  );

  return skDevice;
}

问题2:X25519版本解密错误的原因

你的代码存在两个关键错误:

  1. HKDF的info参数不一致:市民端用"SKDevice",阅读器端用"SKReader"。ECKA-DH要求双方派生会话密钥时,info参数必须统一(或严格遵循规范定义的对应规则),不同的info会导致双方派生的密钥完全不同,直接引发解密失败。
  2. 算法合规性检查:如果ISO 18013-5明确要求使用P-256算法,不能随意替换为X25519,否则会因算法不兼容导致密钥派生逻辑不符合规范。

修正后的generateSKReader方法:

Future<SecretKey> generateSKReader(
    SimpleKeyPairData eReaderKeyPriv, SimplePublicKey eDeviceKeyPub) async {
  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("SKDevice"), // 与市民端保持一致,或遵循规范要求的统一值
      nonce: <int>[1, 2]);

  return output;
}

额外注意:AesGcm的加密/解密逻辑是正确的——加密时自动生成的IV会被包含在SecretBox中,解密时会自动读取,无需额外传递。


内容的提问来源于stack exchange,提问作者Bishop19

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 14:55:19