"Scalar is not in the interval [1, n - 1]" 使用 SHA-384 签署 ECDSA 时出现异常

"Scalar is not in the interval [1, n - 1]" exception when signing ECDSA with SHA-384

我想使用 SHA-384 生成 384 位椭圆曲线签名,以生成密钥我 运行 通过查看此 SO Generate EC KeyPair from OpenSSL command line

下面的步骤
openssl ecparam -name secp384r1 -genkey -noout -out d:/private.ec.key
openssl ec -in d:/private.pem -pubout -out d:/public.pem
openssl pkcs8 -topk8 -nocrypt -in d:/private.ec.key -out d:/private.pem

当我被要求输入密码时我按下了回车键(因为我不想让密码保护密钥)。

然后我为我的 JRE 14 下载了 bcprov-jdk15on-168.jar Bouncy Castle 库,并将其添加到我的 class 路径中。并制作了小测试应用,将这些键的内容直接复制到java源代码中来简单的东西。

制作了一个简单的剥离方法来删除密钥的 'text' 部分并解码其 base64 内容:

  private static byte[] bytesFromKeyStrings(String string) {
    string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
    string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");   
    string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
    string = string.replaceAll("-----END PRIVATE KEY-----", "");    
    string = string.replaceAll("\r", "");
    string = string.replaceAll("\n", "");
    var bytes = Base64.getDecoder().decode(string);
    return bytes;
  }  

然后从这些字节中我生成一个私钥:

  private static PrivateKey keyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException {
    Security.addProvider(BOUNCY_CASTLE_PROVIDER);
    var spec = ECNamedCurveTable.getParameterSpec("secp384r1");;
    var ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, pkcs8key), spec);
      
    var factory = KeyFactory.getInstance("ECDSA", BOUNCY_CASTLE_PROVIDER);
    return factory.generatePrivate(ecPrivateKeySpec);
  }

但是当我要使用 PrivateKey 来签署消息时(在本例中为零数组)然后我得到异常:

  var key = keyFromBytes(bytesFromKeyStrings(privatePem));
  var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
  ecdsaSign.initSign(key, new SecureRandom());
  ecdsaSign.update(content);
  var signature = ecdsaSign.sign();

造成上线:

ecdsaSign.initSign(key, new SecureRandom());

java.lang.IllegalArgumentException: Scalar is not in the interval [1, n - 1]
    at org.bouncycastle.provider/org.bouncycastle.crypto.params.ECDomainParameters.validatePrivateScalar(ECDomainParameters.java:146)
    at org.bouncycastle.provider/org.bouncycastle.crypto.params.ECPrivateKeyParameters.<init>(ECPrivateKeyParameters.java:16)
    at org.bouncycastle.provider/org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil.generatePrivateKeyParameter(ECUtil.java:245)
    at org.bouncycastle.provider/org.bouncycastle.jcajce.provider.asymmetric.ec.SignatureSpi.engineInitSign(Unknown Source)
    at java.base/java.security.SignatureSpi.engineInitSign(SignatureSpi.java:131)
    at java.base/java.security.Signature$Delegate.engineInitSign(Signature.java:1364)
    at java.base/java.security.Signature.initSign(Signature.java:658)
    at ecdsa/ecdsa.Main.main(Main.java:75)

如果我对 ECDSA 的理解正确,我们需要将私钥乘以某个随机值,那么我将提供 secureRandom。奇怪的是,当我没有尝试读取密钥并使用 KeyPairGenerator.getInstance("EC") (没有 bouncycastle 库)动态生成它们时,我之前的尝试然后这些生成的密钥可用于签署消息无一例外,我分别验证了生成的签名是正确的。

是我生成的密钥不好,还是有其他方法可以提供乘数常数,这样它就不会尝试直接用私钥签名并给我例外?

还是我遗漏了一些愚蠢的东西?

下面是我的测试应用程序的完整列表,它可以重现异常:

package ecdsa;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;

public class Main {
   
  private final static String privatePem = "-----BEGIN PRIVATE KEY-----\r\n"
      + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx\r\n"
      + "bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs\r\n"
      + "zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm\r\n"
      + "MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
      + "-----END PRIVATE KEY-----\r\n";  
  
  private final static String privateEc = "-----BEGIN EC PRIVATE KEY-----\r\n"
      + "MIGkAgEBBDBBdsy5smSA2+DvnIdxbqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSf\r\n"
      + "BpYPgCWlybGgBwYFK4EEACKhZANiAAQ0iyw2wPjszhale0mPkiCCTzcNzTW1g7zq\r\n"
      + "UoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMmMWGYZUSSq4noniTDt8INj/El\r\n"
      + "ndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
      + "-----END EC PRIVATE KEY-----\r\n";
  
  private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
  
  private static byte[] bytesFromKeyStrings(String string) {
    string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
    string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");   
    string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
    string = string.replaceAll("-----END PRIVATE KEY-----", "");    
    string = string.replaceAll("\r", "");
    string = string.replaceAll("\n", "");
    var bytes = Base64.getDecoder().decode(string);
    return bytes;
  }  
  
  
  private static PrivateKey keyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException {
    Security.addProvider(BOUNCY_CASTLE_PROVIDER);
    var spec = ECNamedCurveTable.getParameterSpec("secp384r1");;
    var ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, pkcs8key), spec);
      
    var factory = KeyFactory.getInstance("ECDSA", BOUNCY_CASTLE_PROVIDER);
    return factory.generatePrivate(ecPrivateKeySpec);
  }
  
  
  public static void main(String[] args) {
    var content = new byte[100];
    
    try {
      var key = keyFromBytes(bytesFromKeyStrings(privatePem));
      var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
      ecdsaSign.initSign(key, new SecureRandom());
      ecdsaSign.update(content);
      
      var signature = ecdsaSign.sign();
    } catch (Exception e) {
      e.printStackTrace();
    }     
  }

}

编辑:更新了示例(之前我使用的是加密私钥)

根据https://lapo.it/asn1js/,您的 PEM 编码私钥仍然是 encrypted 一个 - 直接可见 "-----BEGIN ENCRYPTED PRIVATE KEY-----":

SEQUENCE (2 elem)
  SEQUENCE (2 elem)
    OBJECT IDENTIFIER 1.2.840.113549.1.5.13 pkcs5PBES2 (PKCS #5 v2.0)
    SEQUENCE (2 elem)
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.5.12 pkcs5PBKDF2 (PKCS #5 v2.0)
        SEQUENCE (3 elem)
          OCTET STRING (8 byte) 1B9485597126CBC4
          INTEGER 2048
          SEQUENCE (2 elem)
            OBJECT IDENTIFIER 1.2.840.113549.2.9 hmacWithSHA256 (RSADSI digestAlgorithm)
            NULL
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC (NIST Algorithm)
        OCTET STRING (16 byte) 6EE87374559F76BC362E29AE5A2E80FC
  OCTET STRING (192 byte) 50AF006CB4E8E42D9992155F9D749F35E7D4BA9D88975C70174157AC0D96909FDDF9F…

要获得有效且未加密的文件,请使用此 OpenSSL 命令将加密文件转换为未加密文件:

openssl pkcs8 -topk8 -nocrypt -in ecprivateencrypted.pem -out ecprivateunencrypted.pem 

我试图自己转换文件,但我的 OpenSSL 拒绝了,因为密码长度(只需按回车键)太短。

我在第二个答案中回答,因为我的第一个答案不再适合这个问题,因为你编辑了你的问题并将私钥 pem 更改为未加密的数据。

由于您的密钥是私钥的编码形式,它具有 PKCS#8 结构,因此任何 reader 都能够识别密钥的类型和基础曲线,因此您不需要使用 ECPrivateKeySpec 构建它。使用此代码有一种更简单的方法:

KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8key);
return keyFactory.generatePrivate(privateKeySpec);

同样适用于 public 密钥,只需使用 X509EncodedKeySpec(参见完整代码示例)。

我附加了您的 bytesFromKeyStrings 函数以将 public 键的键数据也转换为字节数组。

下面是一个完整的示例代码,它读取私钥 pem,对消息签名,读取 public 密钥 pem(从私钥派生)并使用“true”验证签名作为预期结果.

输出:

signatureVerified: true

安全警告:以下代码无异常处理,仅供学习使用:

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class MainSo {

    private final static String privatePem = "-----BEGIN PRIVATE KEY-----\r\n"
            + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx\r\n"
            + "bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs\r\n"
            + "zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm\r\n"
            + "MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
            + "-----END PRIVATE KEY-----\r\n";
/*
Key Details:
   Type: EC
   Size (bits): 384
   Curve Name: secp384r1
   Curve OID: 1.3.132.0.34
   Public key (x):
348b2c36c0f8ecce16a57b498f9220824f370dcd35b583bcea5283775cdb996d
a2b514785554cb32c14692e8c494248e
   Public key (y):
abd50714ce2326316198654492ab89e89e24c3b7c20d8ff1259dd2a16e1de848
2ab78f5a0a2bd7185e2dee1ae0c55e4b
   Private key (d):
4176ccb9b26480dbe0ef9c87716eaaba1b0c0769d11db47a9c28ef4df21074fc
d2af14e0a61c149f06960f8025a5c9b1

Public Key in PEM Format:
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAENIssNsD47M4WpXtJj5Iggk83Dc01tYO8
6lKDd1zbmW2itRR4VVTLMsFGkujElCSOq9UHFM4jJjFhmGVEkquJ6J4kw7fCDY/x
JZ3SoW4d6Egqt49aCivXGF4t7hrgxV5L
-----END PUBLIC KEY-----
 */

    private final static String publicPem = "-----BEGIN PUBLIC KEY-----\n" +
            "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAENIssNsD47M4WpXtJj5Iggk83Dc01tYO8\n" +
            "6lKDd1zbmW2itRR4VVTLMsFGkujElCSOq9UHFM4jJjFhmGVEkquJ6J4kw7fCDY/x\n" +
            "JZ3SoW4d6Egqt49aCivXGF4t7hrgxV5L\n" +
            "-----END PUBLIC KEY-----";

    private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();

    private static byte[] bytesFromKeyStrings(String string) {
        string = string.replaceAll("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
        string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
        string = string.replaceAll("-----BEGIN PUBLIC KEY-----", "");
        string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");
        string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
        string = string.replaceAll("-----END ENCRYPTED PRIVATE KEY-----", "");
        string = string.replaceAll("-----END PRIVATE KEY-----", "");
        string = string.replaceAll("-----END PUBLIC KEY-----", "");
        string = string.replaceAll("\r", "");
        string = string.replaceAll("\n", "");
        return Base64.getDecoder().decode(string);
    }

    private static PrivateKey privateKeyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
        Security.addProvider(BOUNCY_CASTLE_PROVIDER);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8key);
        return keyFactory.generatePrivate(privateKeySpec);
    }

    private static PublicKey publicKeyFromBytes(byte[] x509key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
        Security.addProvider(BOUNCY_CASTLE_PROVIDER);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(x509key);
        return keyFactory.generatePublic(publicKeySpec);
    }

    public static void main(String[] args) {
        System.out.println("
        var content = new byte[100];
        try {
            // signature with private key
            var privateKey = privateKeyFromBytes(bytesFromKeyStrings(privatePem));
            var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
            ecdsaSign.initSign(privateKey, new SecureRandom());
            ecdsaSign.update(content);
            var signature = ecdsaSign.sign();

            // verification with the public key
            var publicKey = publicKeyFromBytes(bytesFromKeyStrings(publicPem));
            var ecdsaVerify = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
            ecdsaVerify.initVerify(publicKey);
            ecdsaVerify.update(content);
            boolean signatureVerified = ecdsaVerify.verify(signature);
            System.out.println("signatureVerified: " + signatureVerified);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

为什么没用

OpenSSL支持的所有密钥格式不仅仅是原始密钥,还包括一些元数据;特别是所有 EC 密钥仅对包含在密钥文件中的特定曲线有意义且可用。 openssl pkcs8 -topk8 [-nocrypt] 生成的两种格式是 unencrypted and encrypted formats defined in PKCS8, more conveniently available as RFC5208, which you could easily find for example in wikipedia. The use of these formats in PEM types 'BEGIN/END PRIVATE KEY' and 'BEGIN/END ENCRYPTED PRIVATE KEY' are formalized in RFC7468 section 10 and section 11,尽管 OpenSSL 早在几十年前就已将它们作为事实上的标准。

Michael Fehr 的第一个答案向您展示了加密形式的分解,但即使是未加密形式仍然包含元数据,将其标识为使用 secp384r1 又名 P-384 曲线的 EC 算法:

$ cat 65740255.clr
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx
bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs
zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm
MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=
-----END PRIVATE KEY-----
$ openssl asn1parse <65740255.clr -i
    0:d=0  hl=3 l= 182 cons: SEQUENCE
    3:d=1  hl=2 l=   1 prim:  INTEGER           :00
    6:d=1  hl=2 l=  16 cons:  SEQUENCE
    8:d=2  hl=2 l=   7 prim:   OBJECT            :id-ecPublicKey
   17:d=2  hl=2 l=   5 prim:   OBJECT            :secp384r1
   24:d=1  hl=3 l= 158 prim:  OCTET STRING      [HEX DUMP]:30819B02010104304176CCB9B26480DBE0EF9C87716EAABA1B0C0769D11DB47A9C28EF4DF21074FCD2AF14E0A61C149F06960F8025A5C9B1A16403620004348B2C36C0F8ECCE16A57B498F9220824F370DCD35B583BCEA5283775CDB996DA2B514785554CB32C14692E8C494248EABD50714CE2326316198654492AB89E89E24C3B7C20D8FF1259DD2A16E1DE8482AB78F5A0A2BD7185E2DEE1AE0C55E4B

熟悉 ASN.1 的人都清楚,PKCS8 未加密的最后一个字段(算法相关编码)本身就是一个 ASN.1 结构。 RFC5915 基于 'SEC1' 文档指定 X9 类型 EC 私钥的 ASN.1 结构(但不是较新的 EC 算法,如 Bernstein 等人的 EdDSA)。解析我们看到它实际上包含实际的私钥(即通常表示为 d 但在 Java S 中的数字)和公钥(通常表示为 Q 但在 Java W 中,采用 X9 未压缩格式) (这在技术上是多余的,因为它可以从私钥和曲线方程重新计算):

$ openssl asn1parse <65740255.clr -i -strparse 27 -dump
    0:d=0  hl=3 l= 155 cons: SEQUENCE
    3:d=1  hl=2 l=   1 prim:  INTEGER           :01
    6:d=1  hl=2 l=  48 prim:  OCTET STRING
      0000 - 41 76 cc b9 b2 64 80 db-e0 ef 9c 87 71 6e aa ba   Av...d......qn..
      0010 - 1b 0c 07 69 d1 1d b4 7a-9c 28 ef 4d f2 10 74 fc   ...i...z.(.M..t.
      0020 - d2 af 14 e0 a6 1c 14 9f-06 96 0f 80 25 a5 c9 b1   ............%...
   56:d=1  hl=2 l= 100 cons:  cont [ 1 ]
   58:d=2  hl=2 l=  98 prim:   BIT STRING
      0000 - 00 04 34 8b 2c 36 c0 f8-ec ce 16 a5 7b 49 8f 92   ..4.,6......{I..
      0010 - 20 82 4f 37 0d cd 35 b5-83 bc ea 52 83 77 5c db    .O7..5....R.w\.
      0020 - 99 6d a2 b5 14 78 55 54-cb 32 c1 46 92 e8 c4 94   .m...xUT.2.F....
      0030 - 24 8e ab d5 07 14 ce 23-26 31 61 98 65 44 92 ab   $......#&1a.eD..
      0040 - 89 e8 9e 24 c3 b7 c2 0d-8f f1 25 9d d2 a1 6e 1d   ...$......%...n.
      0050 - e8 48 2a b7 8f 5a 0a 2b-d7 18 5e 2d ee 1a e0 c5   .H*..Z.+..^-....
      0060 - 5e 4b                                             ^K

另一种选择

如果除了 bcprov 之外还使用 BouncyCastle bcpkix jar,它提供了直接读取(和写入)所有 OpenSSL PEM 格式的例程,包括 PKCS8 和 OpenSSL 的两种形式 'traditional' 或 'legacy' 特定于算法的格式 BEGIN/END EC PRIVATE KEY 这只是 PEM 包装中的 SEC1。参见例如:


Reading elliptic curve private key from file with BouncyCastle
How to Load RSA Private Key From File
(虽然其中一些是针对 RSA 的,但问题与 EC 相同)。

    String pk8e = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n"
            + "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIG5SFWXEmy8QCAggA\r\n"
            + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBu6HN0VZ92vDYuKa5aLoD8BIHA\r\n"
            + "UK8AbLTo5C2ZkhVfnXSfNefUup2Il1xwF0FXrA2WkJ/d+fokLijgkTyal43t8iMJ\r\n"
            + "ne3gOjYP8Q558x+4k4TAMWug0nDh9RPINMqABnhPkf5wyCCYI2RN2b3C4SZCBMQw\r\n"
            + "r+cwVgwiShhPHcuUM9BmFlt9eCr3kuFnfMxlvvpR482kIT+Q6+hZsyUL/oJjOVC9\r\n"
            + "R2S7AwiKH4BPj5TBjMKF4ZI5cS0DMXn1q3h21AUdMPUchX6itcDTfWvEIfTwpUr0\r\n"
            + "-----END ENCRYPTED PRIVATE KEY-----\r\n";
    String pk8u = "-----BEGIN PRIVATE KEY-----\r\n"
            + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx\r\n"
            + "bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs\r\n"
            + "zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm\r\n"
            + "MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
            + "-----END PRIVATE KEY-----\r\n";
    String trad = "-----BEGIN EC PRIVATE KEY-----\r\n"
            + "MIGkAgEBBDBBdsy5smSA2+DvnIdxbqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSf\r\n"
            + "BpYPgCWlybGgBwYFK4EEACKhZANiAAQ0iyw2wPjszhale0mPkiCCTzcNzTW1g7zq\r\n"
            + "UoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMmMWGYZUSSq4noniTDt8INj/El\r\n"
            + "ndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
            + "-----END EC PRIVATE KEY-----\r\n";
    
    @SuppressWarnings("resource") // should use try-resources or similar
    Object o1 = new PEMParser (new StringReader (pk8e)).readObject();
    PrivateKey k1 = new JcaPEMKeyConverter().getPrivateKey(
            ((org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo)o1).decryptPrivateKeyInfo(
                    new JceOpenSSLPKCS8DecryptorProviderBuilder().build(/*password*/"".toCharArray()) ));
    System.out.println ( ((java.security.interfaces.ECPrivateKey)k1).getS() .toString(16));
    
    @SuppressWarnings("resource") // should use try-resources or similar
    Object o2 = new PEMParser (new StringReader (pk8u)).readObject();
    PrivateKey k2 = new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo)o2);
    System.out.println ( ((java.security.interfaces.ECPrivateKey)k2).getS() .toString(16));
    
    @SuppressWarnings("resource") // should use try-resources or similar
    Object o3 = new PEMParser (new StringReader (trad)).readObject();
    PrivateKey k3 = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)o3).getPrivate();
    System.out.println ( ((java.security.interfaces.ECPrivateKey)k3).getS() .toString(16));

->
4176ccb9b26480dbe0ef9c87716eaaba1b0c0769d11db47a9c28ef4df21074fcd2af14e0a61c149f06960f8025a5c9b1
4176ccb9b26480dbe0ef9c87716eaaba1b0c0769d11db47a9c28ef4df21074fcd2af14e0a61c149f06960f8025a5c9b1
4176ccb9b26480dbe0ef9c87716eaaba1b0c0769d11db47a9c28ef4df21074fcd2af14e0a61c149f06960f8025a5c9b1

那个(十六进制)数字是 'raw' 私钥,这是您可以为您发布的方法在 ECPrivateKeySpec 中输入的值(作为正数 BigInteger)。 (我必须使一些名称符合包条件,因为在我的开发环境中我有冲突的导入;你可能不需要那个。)