首页 > 解决方案 > java.security.InvalidKeyException:从 PEM 文件生成公钥、私钥时密钥格式无效

问题描述

我经历了许多类似的线程,但没有运气!!

我想使用 PEM 文件生成公钥和私钥。以下是我用于相同的代码:

        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+pLLnQyJvcPQuAsORSwKBgQDhOPr1x/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+vnRzZa2AOsmo995gsXLdfsJn0o4Z3LsQJUDRI3tQ4dXe/5jS4oFrOdxALOAw6YmvEv/3oHwsCYPDhqLNfIJ9I6m Dt3yG61pUJiCArhPaYG17NQoCxF6Xi6GUajRsECbr8DdyGMAu5eE -----结束 RSA 私钥-----

我尝试手动删除文件页眉和页脚。我尝试了 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

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

标签: javarsasha1public-key-encryptionpem

解决方案


部分欺骗从文件中加载 RSA 公钥

因此,您“编写”(我假设是复制的)代码清楚地表明您需要 DER 格式的两个文件,其中包含 PKCS8 和“X509”编码。(Java 在这里所说的 X.509 实际上是 X.509 的 SubjectPublicKeyInfo组件。)你给它一个文件,PEM 形式而不是 DER,包含 PKCS1 编码而不是 PKCS8X509——你很惊讶它不起作用?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按上述方式创建文件并仅使用代码中与私钥相关的部分读取它,然后执行以下操作:

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

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

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

  • 在纯 Java 中使用未转换格式的另一种方法是根据项目符号 3 去 PEM,然后解析PKCS1 的 ASN.1 结构并构造一个RSAPrivateCrtKeySpec您可以通过您的KeyFactory 而不是PKCS8 编码运行的结构,然后根据项目符号 2 提取公钥。这个更复杂,虽然我想我在哪里见过;如果我找到它会添加。同样,BouncyCastle 可以改进这种方法,但不需要,见下文。

  • 最后,如果你有 BouncyCastle,那就太容易了。您不会说您尝试了“没有运气”,但以下 BouncyCastle 代码就是您所需要的并且确实有效:

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

请注意,这为您提供KeyPair了一个文件中的 privatekey 和 publickey 对象。


推荐阅读