在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); }
提问
- 如何正确结合EC私钥与对方公钥,通过cryptography库的HKDF派生会话密钥?
- 修改后的代码为何会出现解密错误?
解答
问题1:ECDH结合HKDF的正确流程
ECDH的核心是先计算共享密钥,再用HKDF派生会话密钥,不是直接将私钥与公钥拼接:
- 使用目标ECDH算法(比如P-256),用本地私钥和对方公钥计算出共享密钥(即你代码里注释的
zAB)。 - 将这个共享密钥作为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版本解密错误的原因
你的代码存在两个关键错误:
- HKDF的info参数不一致:市民端用
"SKDevice",阅读器端用"SKReader"。ECKA-DH要求双方派生会话密钥时,info参数必须统一(或严格遵循规范定义的对应规则),不同的info会导致双方派生的密钥完全不同,直接引发解密失败。 - 算法合规性检查:如果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
相关产品推荐
相关产品推荐

