Java 中 ECDSA 的 PEM 编码

PEM encoding of ECDSA in Java

我需要有关如何正确构建 Java Private.pem 的帮助。我在 java 中创建了一个 ECDSA pem 文件,在 python 中创建了一个。我正确地实施了 python 一个,但是 java 一个不正确。

KeyPair pair = GenerateKeys();
PrivateKey priv = pair.getPrivate();
PublicKey pub = pair.getPublic();

// Convert to Bytes then Hex for new account params
byte[] bytePriv = priv.getEncoded();
byte[] bytePub = pub.getEncoded();

// save keys
SaveKeyToFile(bytePriv, "private", "private");

// Calls this function
public static void SaveKeyToFile(byte[] Key, String filename, String keyType) throws IOException, NoSuchAlgorithmException, NoSuchAlgorithmException, InvalidKeySpecException {
    StringWriter stringWriter = new StringWriter();
    PemWriter pemWriter = new PemWriter(stringWriter);
    PemObjectGenerator pemObject = new PemObject("EC " + keyType.toUpperCase() + " KEY", Key);


    pemWriter.flush();
    pemWriter.close();
    String privateKeyString = stringWriter.toString();
    FileUtils.writeStringToFile(new File(filename + ".pem"), privateKeyString);
}

对于使用 Java 和 Python 的 OpenSSL 的 ASN1 解析(不包括 hexdump,因为它的长度):

JAVA
0:d=0  hl=3 l= 141 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           :secp256k1
24:d=1  hl=2 l= 118 prim: OCTET STRING     [HEXDUMP] 

PYTHON
0:d=0  hl=2 l= 116 cons: SEQUENCE          
2:d=1  hl=2 l=   1 prim: INTEGER           :01
5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEXDUMP] 
39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
41:d=2  hl=2 l=   5 prim: OBJECT           :secp256k1
48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
50:d=2  hl=2 l=  66 prim: BIT STRING    

至于输出PEM:

Java:
-----BEGIN EC PRIVATE KEY-----
MIGNAgEAMBAGByqGSM49AgEGBSuBBAAKBHYwdAIBAQQgiEc2TOzXj4mrUisuv+0p
xZ/z+Z/VyIvWug17zjNOPIKgBwYFK4EEAAqhRANCAATWi28vsiZdfqbqodDZc1uz
UFwNcu90l2ezayH0L4ZYB+MfReMCBFG/P6kn12GLj3DipqmvRucgmOlFVmggm+nH
-----END EC PRIVATE KEY-----

Python:
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEINTaEQvUvtOQlanp0lWv0KBcSs8IltKYH26OkoNxLQc5oAcGBSuBBAAK
oUQDQgAEcp3sseSpAXzIzWCwswYnsVnIZ0EEtkXl/CJWChQHilWLwWsxGq11/g/6
38UfZbsjE5TSf6zT8VXvTZE++u9c+A==
-----END EC PRIVATE KEY-----

非常感谢您的帮助!

(抱歉耽搁了。)

好的,您确实需要特定算法的形式,但 JCE PrivateKey.getEncoded() returns PKCS8(处理所有算法)编码,除了您之外,它与 几乎相同想要 ECC 而不是 RSA,并且(更重要的是)您正在使用 Java with BouncyCastle.

那么,您的选择是:

  • 编写 PKCS8 编码,用 PEM 或直接用二进制 (DER),然后使用 openssl ec 或在 1.1.0 openssl pkey -traditional 中将其转换为算法-特定的 PEM 形式,即 SEC1(rfc5915 基本上是 SEC1 的 republication)。

  • 编写PKCS8编码(PEM或DER均可),使用openssl asn1parse -strparse提取算法特定部分,或者确定算法特定部分的偏移量,直接提取。一个 256 位 ECC 密钥对 DER 编码的长度大多为单八位字节,但标识(命名的)曲线的 OID 可能因不同的曲线而在大小上有所不同;如您的示例所示,secp256k1 为 24+2,因此您可以将 Arrays.copyOfRange(Key,26,Key.length) 赋给当前逻辑。

    对于大于 256 位的 ECC 密钥对,DER 编码可能需要变化,并且接近边界线可能取决于密钥对中的 public 值是未压缩形式还是压缩形式,您有没有实用的控制方法(JCE 不提供编码内的选项)。这种情况下至少需要手动处理一些 DER,在这种情况下我会转到下一个选项。

  • 您已经在使用的
  • BC 公开了操纵 PKCS8 结构的能力,您可以使用它来提取算法特定的 SEC1 编码,如下所示:

    import org.bouncycastle.asn1.ASN1Object;
    import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
    import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; // as defined by PKCS8
    ...
    PrivateKey k = /* whatever */;
    PemWriter w = new PemWriter (/* underlying writer */);
    PrivateKeyInfo i = PrivateKeyInfo.getInstance(ASN1Sequence.getInstance(k.getEncoded()));
    if( ! i.getPrivateKeyAlgorithm().getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey) ){
        throw new Exception ("not EC key");
    }
    ASN1Object o = (ASN1Object) i.parsePrivateKey(); 
    w.writeObject (new PemObject ("EC PRIVATE KEY", o.getEncoded("DER")));  
    // DER may already be the default but safer to (re)specify it 
    w.close();