【问题标题】:How Does BouncyCastle Generate ECDH "Keys"?BouncyCastle 如何生成 ECDH“密钥”?
【发布时间】:2019-06-23 16:50:57
【问题描述】:

我有一个使用 BouncyCastle 作为安全提供程序的应用程序,但我想切换到另一个直接使用 OpenSSL (Conscrypt) 的应用程序。我遇到的问题是我正在使用 BouncyCastle 提供的 KeyGenerator 中的 ECDH“密钥”,但在我的其他库中没有类似的 KeyGenerator。

为了比较两者,我将使用这两种方法和以下输入解码点-

为了可读性添加了换行符

BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lM
oC4aIXw_2qlTnplykArgjvwCWw-2g6L44

使用 BouncyCastle 方法-

public org.bouncycastle.jce.interfaces.ECPublicKey loadECPublicKeyBC(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("ECDH", "BC");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = (org.bouncycastle.jce.interfaces.ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

返回的getAlgorithmEC。 返回的getFormatX.509

getEncoded 的值是-

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,-50,61,2,1,48,
-127,-32,2,1,1,48,44,6,7,42,-122,72,-50,61,1,1,2,33,0,
-1,-1,-1,-1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,0,0,0,1,
0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-4,4,32,90,-58,53,-40,-86,58,-109,-25,-77,-21,-67,85,
118,-104,-122,-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,
62,39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,71,-8,-68,
-26,-27,99,-92,64,-14,119,3,125,-127,45,-21,51,-96,-12,
-95,57,69,-40,-104,-62,-106,79,-29,66,-30,-2,26,127,-101,
-114,-25,-21,74,124,15,-98,22,43,-50,51,87,107,49,94,-50,
-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,-1,-1,0,0,0,0,-1,
-1,-1,-1,-1,-1,-1,-1,-68,-26,-6,-83,-89,23,-98,-124,-13,
-71,-54,-62,-4,99,37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,
77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,-112,37,-30,
63,84,-38,-64,103,2,114,120,-89,-2,-122,72,2,50,20,101,55,
-108,-54,2,-31,-94,23,-61,-3,-86,-107,57,-23,-105,41,0,-82,
8,-17,-64,37,-80,-5,104,58,47,-114]

我只使用 BouncyCastle EC 算法(不是 ECDH)-

[48,-126,1,51,48,-127,-20,6,7,42,-122,72,
-50,61,2,1,48,-127,-32,2,1,1,48,44,6,7,42,
-122,72,-50,61,1,1,2,33,0,-1,-1,-1,-1,0,0,
0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,48,68,4,32,-1,-1,-1,-1,
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-4,4,32,90,-58,53,-40,
-86,58,-109,-25,-77,-21,-67,85,118,-104,-122,
-68,101,29,6,-80,-52,83,-80,-10,59,-50,60,62,
39,-46,96,75,4,65,4,107,23,-47,-14,-31,44,66,
71,-8,-68,-26,-27,99,-92,64,-14,119,3,125,
-127,45,-21,51,-96,-12,-95,57,69,-40,-104,-62,
-106,79,-29,66,-30,-2,26,127,-101,-114,-25,
-21,74,124,15,-98,22,43,-50,51,87,107,49,94,
-50,-53,-74,64,104,55,-65,81,-11,2,33,0,-1,-1,
-1,-1,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-68,-26,
-6,-83,-89,23,-98,-124,-13,-71,-54,-62,-4,99,
37,81,2,1,1,3,66,0,4,0,-41,-4,96,23,-89,77,
-13,-1,-102,116,-28,-19,126,-94,-98,-75,40,
-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,
23,-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,
-64,37,-80,-5,104,58,47,-114]

现在使用 Conscrypt 方法-

public ECPublicKey loadECPublicKey(String encodedPublicKey) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    Base64.Decoder base64Decoder = Base64.getUrlDecoder();
    byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
    KeyFactory keyFactory = KeyFactory.getInstance("EC", "Conscrypt");

    ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1");
    ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

    ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
    ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

    return ecPublicKey;
}

返回的getAlgorithmEC。 返回的getFormatX.509getEncoded 的值是-

[48,89,48,19,6,7,42,-122,72,-50,61,2,1,6,8,42,
-122,72,-50,61,3,1,7,3,66,0,4,0,-41,-4,96,23,
-89,77,-13,-1,-102,116,-28,-19,126,-94,-98,-75,
40,-112,37,-30,63,84,-38,-64,103,2,114,120,-89,
-2,-122,72,2,50,20,101,55,-108,-54,2,-31,-94,23,
-61,-3,-86,-107,57,-23,-105,41,0,-82,8,-17,-64,
37,-80,-5,104,58,47,-114]

忽略两个 EC 生成的密钥之间的差异。 BouncyCastle 在 ECDH 密钥生成器中的作用是什么?

DH 是一个 KeyAgreement,我假设它正在生成一个 EC 密钥并通过 DH KeyAgreement 运行它——但是当 KeyGenerator 规范中没有指定任何内容时,它初始化为私钥是什么?

还有。为什么当我对两个提供商都使用 EC 算法时,当两者都使用 prime256v1 规范时,我会为相同的算法得到不同的结果?我会假设这些至少是相等的。

编辑:

ECUtil 来自sun.security.util.ECUtil

对于在我的示例中 BC 和 Java 安全库共享一个通用名称的任何类(例如 ECPoint),它始终是 Java 安全库。只有当类以 bouncycastle 路径为前缀(例如 org.bouncycastle.jce.interfaces.ECPublicKey)时,它才是 BC 类。 openSSLProvider 是来自 Conscrypt 库的 OpenSSLProvider 的一个实例。

该项目可以在这里找到。

https://github.com/google/conscrypt

要安装的 pom 在这里-

<dependency>
    <groupId>org.conscrypt</groupId>
    <artifactId>conscrypt-openjdk-uber</artifactId>
    <version>2.1.0</version>
</dependency>

///

import org.conscrypt.OpenSSLProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import sun.security.util.ECUtil;

Security.addProvider(new BouncyCastleProvider());
Security.addProvider(new OpenSSLProvider());

ECUtil.getECParameterSpec(new OpenSSLProvider, "prime256v1");

编辑编辑:

完整的最小可重现示例-

编辑编辑编辑:

示例现在包括手动加载公钥,而不是使用 KeyFactory。

手动加载BC公钥时,其编码值与通过密钥工厂加载时Conscrypt公钥的编码值匹配...

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.JCEECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.conscrypt.OpenSSLProvider;
import sun.security.util.ECUtil;

import java.io.IOException;
import java.security.*;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.util.*;

public class Main {

    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
        Security.addProvider(new BouncyCastleProvider());
        Security.addProvider(new OpenSSLProvider());

        String pubKey = "BADX_GAXp03z_5p05O1-op61KJAl4j9U2sBnAnJ4p_6GSAIyFGU3lMoC4aIXw_2qlTnplykArgjvwCWw-2g6L44";

        ECPublicKey publicKey = (ECPublicKey)loadPublicKey(pubKey, "Conscrypt");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC = (org.bouncycastle.jce.interfaces.ECPublicKey)loadPublicKey(pubKey, "BC");
        org.bouncycastle.jce.interfaces.ECPublicKey publicKeyBC2 = (org.bouncycastle.jce.interfaces.ECPublicKey) loadPublicKeyManually(pubKey);

        System.out.println(Arrays.toString(publicKey.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC.getEncoded()));
        System.out.println(Arrays.toString(publicKeyBC2.getEncoded()));
    }

    public static PublicKey loadPublicKey(String encodedPublicKey, String provider) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);

        ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(new OpenSSLProvider(), "prime256v1");
        ECPoint ecPoint = ECUtil.decodePoint(decodedPublicKey, ecParameterSpec.getCurve());

        ECPublicKeySpec pubSpec = new ECPublicKeySpec(ecPoint, ecParameterSpec);
        ECPublicKey ecPublicKey = (ECPublicKey)keyFactory.generatePublic(pubSpec);

        return ecPublicKey;
    }

    public static PublicKey loadPublicKeyManually(String encodedPublicKey) {
        Base64.Decoder base64Decoder = Base64.getUrlDecoder();
        byte[] decodedPublicKey = base64Decoder.decode(encodedPublicKey);

        ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");

        org.bouncycastle.jce.spec.ECPublicKeySpec ecPublicKeySpec = new org.bouncycastle.jce.spec.ECPublicKeySpec(
                parameterSpec.getCurve().decodePoint(decodedPublicKey),
                parameterSpec
        );
        org.bouncycastle.jce.interfaces.ECPublicKey ecPublicKey = new JCEECPublicKey(
                "EC",
                ecPublicKeySpec
        );

        return ecPublicKey;
    }
}

【问题讨论】:

  • ECUtil?那个班是哪里来的? ECParameterSpec ecParameterSpec = ECUtil.getECParameterSpec(openSSLProvider, "prime256v1"); 行中 openSSLProvider 的值是多少?最后,这些类中有许多在 Bouncycastle 库和 Java 安全库中具有相同的名称,因此除非您显示导入语句,否则不清楚您指的是哪一个。
  • 对于 BC 和 Java 安全库共享一个通用名称的任何类 - 它始终是 Java 安全库。只有当类以bouncycastle 路径为前缀时,它才是BC 类。 openSSLProvider 是来自 Conscrypt 库的 OpenSSLProvider 的一个实例。我将更新我的帖子以包含此信息和图书馆的 pom
  • 添加了完整的最小可重现示例

标签: java encryption openssl bouncycastle


【解决方案1】:

这两个公钥表示本质上是等价的。两者都是 RFC 5280 中描述的 DER 编码的 SubjectPublicKeyInfo 结构的实例。该结构不仅包含公钥,还包含一些描述公钥算法上下文的元数据。对于其中一种形式,元数据仅声明上下文是“prime256v1”曲线。另一方面,提供了该曲线的所有参数。公钥本身作为两种形式的最后一部分出现,您可以看到它们是相同的。

您所拥有的是同一个公钥共有 3 种不同的表示形式。 base-64 编码字符串仅包含根据SEC 1 第 2.3.3 节编码的类型 4(未压缩)椭圆曲线点。您在 sun.security.util.ECUtil 类中发现了一个未记录且不受支持的 API,可将其转换为 PublicKey。

我不完全确定您对私钥的问题是什么。对于包括 ECDH 在内的 DH 方案,私钥只是一个从基础组顺序范围内“安全”选择的整数。然后通过将该整数乘以曲线的基点(在椭圆曲线的意义上)来计算公钥。结果公钥也是曲线上的一个点。

【讨论】:

  • 那么您是说在 Conscrypt 的情况下还是在我手动构建密钥时我实际上并没有使用曲线部分?我对DH过程感到困惑。我知道 DH KeyAgreement 需要在调用 doPhase 之前使用 PrivateKey 进行初始化 - 但我不知道如何获取 PrivateKey。你是说我以某种方式从ecParameterSpec.getOrder() 得到这个?
  • 是你问私钥的。您使用 base64 编码的公钥所做的事情根本不涉及任何私钥,它只是一个公钥。
  • 如果我试图将 BouncyCastle 的 ECDH 与 Conscrypt 匹配,我的假设是我必须生成一个 EC 密钥并通过 DH KeyAgreement 传递它,对吗?如果我必须通过 DH KeyAgreement 传递它,我需要提供一些私人的东西吗?如果这是一条红鲱鱼,我可以没有它继续前进。
  • 提供者的行为不应有任何差异。您在这里所做的一切都不是特定于提供商的,KeyAgreement 也不是。
  • 使用任一键在下游都有不同的结果。如果我使用 BC 密钥,我可以加密最终可以由用户代理解密的有效负载。如果我通过相同的方法传递 conscrypt 密钥,我会得到一些加密结果,但用户代理无法解密。不过,您的回答确实有帮助-我现在看到两个键都包含相同的结束字节。我应该能够在示例位置使用 Conscrypt 生成的密钥作为 BC 生成的密钥。
猜你喜欢
  • 2017-10-28
  • 2014-06-03
  • 2019-06-24
  • 2019-05-03
  • 2020-02-14
  • 1970-01-01
  • 1970-01-01
  • 2015-06-18
  • 1970-01-01
相关资源
最近更新 更多