我想用ECDH(secp 256 r1算法)在dart和java之间进行通信。
这是我的Dart代码:
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
class EcdhHandler {
late final AsymmetricKeyPair _keyPair;
late final ECDomainParameters _domainParameters;
EcdhHandler.init() {
_domainParameters = ECDomainParameters(ECCurve_secp256r1().domainName);
var ecParams = ECKeyGeneratorParameters(_domainParameters);
var params = ParametersWithRandom<ECKeyGeneratorParameters>(ecParams, getSecureRandom());
var keyGenerator = ECKeyGenerator();
keyGenerator.init(params);
_keyPair = keyGenerator.generateKeyPair();
}
Uint8List getPublicKey() {
var publicKey = _keyPair.publicKey as ECPublicKey;
var publicPoint = publicKey.Q!.getEncoded(false);
return publicPoint;
}
ECPublicKey bytesToPublicKey(Uint8List bytes) {
var domainParams = (_keyPair.publicKey as ECPublicKey).parameters;
var curve = domainParams!.curve;
var point = curve.decodePoint(bytes);
return ECPublicKey(point, domainParams);
}
BigInt makeSharedSecret(ECPublicKey remotePublicKey) {
var agreement = ECDHBasicAgreement();
var privateKey = _keyPair.privateKey as ECPrivateKey;
agreement.init(privateKey);
return agreement.calculateAgreement(remotePublicKey);
}
SecureRandom getSecureRandom() {
var secureRandom = FortunaRandom();
var random = Random.secure();
List<int> seeds = [];
for (int i = 0; i < 32; i++) {
seeds.add(random.nextInt(255));
}
secureRandom.seed(KeyParameter(Uint8List.fromList(seeds)));
return secureRandom;
}
}
这段Java代码:
import javax.crypto.KeyAgreement;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
public class ECDHHandler {
private PublicKey publicKey;
private KeyAgreement keyAgreement;
private byte[] sharedSecret;
public ECDHHandler() {
makeKeyExchangeParams();
}
private void makeKeyExchangeParams() {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec secp256r1 = new ECGenParameterSpec("secp256r1");
kpg.initialize(secp256r1);
KeyPair kp = kpg.generateKeyPair();
keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(kp.getPrivate());
publicKey = kp.getPublic();
} catch (Exception e) {
e.printStackTrace();
}
}
public void initializeSharedSecret(PublicKey publickey) {
try {
keyAgreement.doPhase(publickey, true);
sharedSecret = keyAgreement.generateSecret();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
public PublicKey convertByteArrayToPublicKey(byte[] bytes) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(bytes);
return kf.generatePublic(pkSpec);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
我在dart应用程序上用pointycastle生成公钥并将其发送到Java。
我的问题
java上的方法convertByteArrayToPublicKey
无法解析字节码到ecdh公钥!
我总是收到:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=20, too big.
pointycastle: ^3.7.3
Dart SDK version: 3.0.2 (stable) (Tue May 23 08:26:58 2023 +0000) on "linux_x64"
java version "17.0.4" 2022-07-19 LTS
Java(TM) SE Runtime Environment GraalVM EE 22.2.0 (build 17.0.4+11-LTS-jvmci-22.2-b05)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.2.0 (build 17.0.4+11-LTS-jvmci-22.2-b05, mixed mode, sharing)
1条答案
按热度按时间q0qdq0h21#
双方使用不同的密钥格式:Dart代码中的
getPublicKey()
以原始的未压缩格式返回公钥。Java代码中的convertByteArrayToPublicKey()
需要X.509/SPKI格式的密钥,采用ASN.1/DER编码。不同的格式是您的问题的原因。解决办法是使用相同的格式。可以在Dart端(以X.509/SPKI格式导出)、Java端(导入原始的未压缩密钥)或通过密钥转换工具进行调整。
在下文中,在Dart侧进行适配。用于密钥的导入/导出,例如可以使用Dart软件包basic_utils。以下解决方案基于您发布的代码。
第一部分:密钥导出
密钥(通过
init()
和)导出可以在Dart端实现如下:这里必须考虑以下几点:
ECCurve_prime256v1()
而不是ECCurve_secp256r1()
,因为 basic_utils 不接受前者(错误消息:* 目前还不支持ObjectIdentifier secp 256 r1 )。请注意,这两个标识符命名相同的曲线: prime 256 v1 * aka NIST P-256 是 * secp 256 r1 * 的别名。Java端的密钥(生成和)导出可以实现如下:
公钥以X.509/SPKI格式导出,DER编码,私钥以PKCS#8格式导出,DER编码(选择PKCS#8格式是因为JCA/JCE默认支持该格式)。
第二部分:密钥导入和密钥协议
Dart端的密钥导入和密钥协商可以如下实现(这里使用的密钥是用第一部分的代码生成的):
在Java方面:这里,公钥必须从PEM转换为DER。这是相对简单的:header、footer和line breaks必须删除,其余部分必须进行Base64解码。或者,BouncyCastle也可以用于直接导入PEM密钥:
测试结果:在双方上,生成相同的共享秘密!