java.security.InvalidKeyException:生成 public 时密钥格式无效,来自 PEM 文件的私钥

java.security.InvalidKeyException: invalid key format while generating public, private key from PEM file

我经历过很多类似的话题,但运气不好!!

我想使用 PEM 文件生成 public 和私钥。以下是我正在使用的代码:

        String pemFileNme = "C:\Users\amitmm\Desktop\clean\key.pem";

        File pubKeyFile = new File(pemFileNme);
        File privKeyFile = new File(pemFileNme);

        // read public key DER file
        DataInputStream dis = new DataInputStream(new 
        FileInputStream(pubKeyFile));
        byte[] pubKeyBytes = new byte[(int)pubKeyFile.length()];
        dis.readFully(pubKeyBytes);
        dis.close();

        // read private key DER file
        dis = new DataInputStream(new FileInputStream(privKeyFile));
        byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
        dis.read(privKeyBytes);
        dis.close();

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        // decode public key
        X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubKeyBytes);
        RSAPublicKey pubKey = (RSAPublicKey) 
        keyFactory.generatePublic(pubSpec);

        // decode private key
        PKCS8EncodedKeySpec privSpec = new 
        PKCS8EncodedKeySpec(privKeyBytes);
        RSAPrivateKey privKey = (RSAPrivateKey) 
        keyFactory.generatePrivate(privSpec);

异常:

Exception in thread "main" java.security.spec.InvalidKeySpecException: 
java.security.InvalidKeyException: invalid key format
at
sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
at main.java.me.txedo.security.Main2.f1(Main2.java:47)
at main.java.me.txedo.security.Main2.main(Main2.java:20)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:387)

PEM 文件内容:

-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAwnEEodFEf86+Ae+wYyI//u1kekIWnA3RfzbAwWD77uG7D9Ci 9vVNbPO4XT2hKL03/q7d7KTgrA1sjBltfaOzVfA56x1S/0cYVk4xI440dpLo0F+m RIqRw5fh8IuUlUIr3I4A7ESkDQQsZbDpdgCiNbrlADqLotcZyB4rU4uURW8QUI/W eqsD6TOQs4bI+3o3xAKkky2kXujSaaa3tDxgUPTmSQ0Buk7Hx/IVzwyV7qjWiR4U C46rHpnWxfF0DWuJUOYgJmBQ8xFOQwt4Ec/u+0m8top8cqQF+gpBn9iLXpbtahA3 pqyvLuNXRH9yn8mlEneBrjjl6U0H3W/AV7/dGwIBAwKCAQEAgaCtwTYtqonUAUp1 l2wqqfOYUYFkaAk2VM8rK5X9SevSCosXT04znffQPikWGyjP/x8+ncNAcrOdsrue U8J3jqAmnL43VNoQOYl2F7Qi+bdF4D/ELbG2gmVBSwe4Y4FykwlV8thtXgLIQ8tG TqsWznyYqtGybI9mhWlyN7Ji2POMDZP5Lwx7M01pMezwpnsZSmPVL9TgVrtWv4xt C0vPyuy9THlFWtkOdHItNK+vOTcpuHn29rFUJI/D3R+SQjcdqj3aaqljOtdeBxgd yDl2/Z4rUyetgzcZMfNTt/NRT0hOJ6R6/2S7gFCTtxMHBh3vVCH+pLLnQyJvcPQu AsORSwKBgQDhOPr1x/8BioqaasoXvO9NsGktCgPDjbC4d3jR8n6lCa42X/eIahaD xi1VGWyQhdO7aMXiDmzOtox7xHcMRh+a5ySIs9gTsHkMB2hqwIUNg25INRkQ3Vr3 eWnoTBGsfJqC1TEME3ocKwmyz57ZAe4yyR/ZRdDX5DUt9qCCFeA8uQKBgQDdAzbq 7BlJkbTYfdlIRNJEJAO3wWqQTx8X0ttCMMwDluOT9l+RR/KuUxl85ph+kwJci6E/ ixfeMTW1NcsMY/lB6mTP0oooalU1MP7gpPSu+24zhLXnUHZotbNbv9nk6w/1WWhz FBt5w2DG4kQPFK6LSySqcVuzIGQyvWD5PbpGcwKBgQCWJfyj2qoBBwcRnIa6ffTe dZtzXAKCXnXQT6XhTFRuBnQkP/pa8WRX2XOOEPMLA+J88IPsCZ3fJF2n2E9dhBUR 722wd+VidaYIBPBHKwNeV57azhC16OdPpkaa3WEdqGcB43YIDPwSx1vMimnmAUl3 ML/mLos6mCNz+cBWuUAoewKBgQCTV3nx8ruGYSM6/pDa2IwtbVfP1kcK32oP4eeB dd1Xue0NTupg2qHJjLuombr/DKw9smt/sg/pdiPOI9yy7VDWnEM1NwbFnDjOIKnr GKMfUkl3rc6aNaRFzneSf+aYnLVOO5r3Yrz715XZ7C1fYx8Hh23G9j0iFZgh05X7 fnwu9wKBgHyC0X26KZQ0ukan5jDSiz4dapUp2d3F+vnRzZa2AOsmo995gsXLdfsJ n0o4Z3LsQJUDRI3tQ4dXe/5jS4oFrOdxALOAw6YmvEv/3oHwsCYPDhqLNfIJ9I6m Dt3yG61pUJiCArhPaYG17NQoCxF6Xi6GUajRsECbr8DdyGMAu5eE -----END RSA PRIVATE KEY-----

我试过手动删除文件页眉和页脚。我尝试了 bouncycastle 的代码,没有运气,同样的错误。

Python 适用于此文件的代码:

def t2e_enc(plaintext, pk_pem_file = './2017-12-04T062008Z.pem'):
''' 
Function for encryption of Track2 credit card data.
This function uses private key to derivate public part used for encryption

'''
with open(pk_pem_file, 'rb') as pk:
    private_key = serialization.load_pem_private_key(pk.read(), 
    password=None, backend=default_backend())

public_key = serialization.load_pem_public_key(
    private_key.public_key().public_bytes(
        serialization.Encoding.PEM, 
        serialization.PublicFormat.SubjectPublicKeyInfo),
        backend=default_backend()
    )

ciphertext = public_key.encrypt(
    plaintext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA1()),
        algorithm=hashes.SHA1(),
        label=None
    )
)
b64ciphertext=base64.b64encode(ciphertext)
return b64ciphertext

我是第一次这样做,如果有任何愚蠢的错误,请多多包涵。

部分欺骗Load RSA public key from file

因此,您 'wrote'(我假设,已复制)代码清楚地表明您需要两个 DER 格式的文件,其中包含 PKCS8 和 'X509' 编码。 (Java 在这里调用 X.509 实际上是 X.509 的 SubjectPublicKeyInfo component。)你给它一个文件,以 PEM 形式而不是 DER,包含一个 PKCS1 编码不是PKCS8 or X509——你很惊讶它不起作用? Python 之所以有效,是因为它调用了 OpenSSL,而 OpenSSL 支持十几种私钥编码和格式,包括这个; Java 只支持一个(在密钥库之外)而不是这个。 (裸)公钥好一点; libcrypto 内部支持多种形式,但实际上只使用了其中两种,其中一种匹配 Java -- 尽管许多公钥以 X.509 证书的形式分发、存储和使用,它提供了几种更多形式需要担心。

您的情况大约有 7 种解决方案:

  • 最简单的就是用OpenSSL命令行把你的一个文件转换成两个文件Java想要:

      # (corrected! pkey is inconsistent!) 
      openssl pkcs8 -topk8 -nocrypt -in input.pem -outform der -out private.der
    
      openssl pkey -in input.pem -pubout -outform der -out public.der
      # or for very old versions (should not be needed now)
      openssl rsa -in input.pem -pubout -outform der -out public.der
    

    这些文件现在可以通过您发布的代码读取(文件名分开除外)。请注意,不必在同一系统上进行此转换;如有必要,您可以在其他地方执行此操作并复制文件,前提是您使用适用于二进制文件的方法,即不剪切和粘贴。

  • 如果您真的只想要一个文件,但它可以转换,请按上述方式创建 private.der 文件并仅使用代码的 privatekey-related 部分读取它,然后做:

      RSAPrivateCrtKey priv2 = (RSAPrivateCrtKey)privKey;
      PublicKey pubkey = keyFactory.generatePublic(new RSAPublicKeySpec(priv2.getModulus(), priv2.getPublicExponent()));
    
  • 您可以通过省略上述转换中的 -outform der 将文件转换为 PKCS8 和 'X509' PEM,然后读取这些文件并手动 'de-PEM' 通过删除 header 和尾行并将 base64 转换为二进制(删除或跳过换行符);这会导致二进制 PKCS8 和 X509 编码,您可以 运行 通过现有代码。这与 openssl 方面的工作一样多,Java 方面的工作更多,因此没有明显的优势,除了 PEM 文件是有效文本并且可以在必要时剪切和粘贴。

  • 结合这些,您可以仅转换为 PKCS8 PEM,按项目符号 3 读取(de-PEM 然后是代码的私钥部分),然后从每个项目符号 2 的私钥中提取公钥

  • 一种使用您拥有的(未转换的)格式 Java 的方法是 de-PEM 每个项目符号 3 为您提供 PKCS1 编码,然后手动构造 PKCS8 编码,然后像以前一样通过 KeyFactory 进入 PKCS8 运行 并提取每个项目符号 2 的公钥。请参阅我在 Java: Convert DKIM private key from RSA to DER for JavaMail 的回答,了解一种非常丑陋的方法(包括一个 de-PEM 方法)。如果您使用 BouncyCastle(此 ASN.1 类型具有 class),则有更好的方法,但如果您使用 BouncyCastle,则最好不要使用此方法,请参见下文。

  • 在普通 Java 中使用未转换格式的另一种方法是 de-PEM 每个项目符号 3,然后解析 the ASN.1 structure of PKCS1 并构造一个 RSAPrivateCrtKeySpec您可以 运行 通过 KeyFactory 而不是 PKCS8 编码,然后提取每个项目符号 2 的公钥。这更复杂,尽管我想我已经看到了某处;如果我找到它会添加。同样,BouncyCastle 可以改进此方法,但不需要,请参见下文。

  • 最后,如果您有 BouncyCastle,那就太简单了。你没有说你用 'no luck' 尝试了什么,但下面的 BouncyCastle 代码是你所需要的并且确实有效:

      try( Reader r = new FileReader(filename) ){
        KeyPair pair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)new PEMParser(r).readObject());
      }
    

请注意,这会为您提供一个 KeyPair,同时包含一个文件中的私钥和公钥 objects。