java.security.InvalidKeyException:从 PEM 文件生成公钥、私钥时密钥格式无效

java.security.InvalidKeyException:从 PEM 文件生成公钥、私钥时密钥格式无效

问题描述:

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

I have gone through many similar threads but no luck!!

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

I want to generate public and private keys using a PEM file. Following is the code I am using for the same:

        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 文件内容:

-----开始RSA私钥--MIIEowIBAAKCAQEAwnEEodFEf86+Ae+wYyI//u1kekIWnA3RfzbAwWD77uG7D9Ci9vVNbPO4XT2hKL03/q7d7KTgrA1sjBltfaOzVfA56x1S/0cYVk4xI440dpLo0F+mRIqRw5fh8IuUlUIr3I4A7ESkDQQsZbDpdgCiNbrlADqLotcZyB4rU4uURW8QUI/WeqsD6TOQs4bI+3o3xAKkky2kXujSaaa3tDxgUPTmSQ0Buk7Hx/IVzwyV7qjWiR4UC46rHpnWxfF0DWuJUOYgJmBQ8xFOQwt4Ec/u+0m8top8cqQF+gpBn9iLXpbtahA3pqyvLuNXRH9yn8mlEneBrjjl6U0H3W/AV7/dGwIBAwKCAQEAgaCtwTYtqonUAUp1l2wqqfOYUYFkaAk2VM8rK5X9SevSCosXT04znffQPikWGyjP/x8+ncNAcrOdsrueU8J3jqAmnL43VNoQOYl2F7Qi+bdF4D/ELbG2gmVBSwe4Y4FykwlV8thtXgLIQ8tGTqsWznyYqtGybI9mhWlyN7Ji2POMDZP5Lwx7M01pMezwpnsZSmPVL9TgVrtWv4xtC0vPyuy9THlFWtkOdHItNK+vOTcpuHn29rFUJI/D3R+SQjcdqj3aaqljOtdeBxgdyDl2/Z4rUyetgzcZMfNTt/NRT0hOJ6R6/2S7gFCTtxMHBh3vVCH+pLLnQyJvcPQuAsORSwKBgQDhOPr1x/8BioqaasoXvO9NsGktCgPDjbC4d3jR8n6lCa42X/eIahaDxi1VGWyQhdO7aMXiDmzOtox7xHcMRh+a5ySIs9gTsHkMB2hqwIUNg25INRkQ3Vr3eWnoTBGsfJqC1TEME3ocKwmyz57ZAe4yyR/ZRdDX5DUt9qCCFeA8uQKBgQDdAzbq7BlJkbTYfdlIRNJEJAO3wWqQTx8X0ttCMMwDluOT9l+RR/KuUxl85ph+kwJci6E/ixfeMTW1NcsMY/lB6mTP0oooalU1MP7gpPsu+24zhLXnUHZotbNbv9nk6w/1WWhzFBt5w2DG4kQPFK6LSySqcVuzIGQyvWD5PbpGcwKBgQCWJfyj2qoBBwcRnIa6ffTedZtzXAKCXnXQT6XhTFRuBnQkP/pa8WRX2XOOEPMLA+J88IPsCZ3fJF2n2E9dhBUR722wd+VidaYIBPBHKwNeV57azhC16OdPpkaa3WEdqGcB43YIDPwSx1vMimnmAUl3ML/mLos6mCNz+cBWuUAoewKBgQCTV3nx8ruGYSM6/pDa2IwtbVfP1kcK32oP4eeBdd1Xue0NTupg2qHJjLuombr/DKw9smt/sg/pdiPOI9yy7VDWnEM1NwbFnDjOIKnrGKMfUkl3rc6aNaRFzneSf+aYnLVOO5r3Yrz715XZ7C1fYx8Hh23G9j0iFZgh05X7fnwu9wKBgHyC0X26KZQ0ukan5jDSiz4dapUp2d3F+vnRzZa2AOsmo995gsXLdfsJn0o4Z3LsQJUDRI3tQ4dXe/5jS4oFrOdxALOAw6YmvEv/3oHwsCYPDhqLNfIJ9I6mDt3yG61pUJiCarhPaYG17NQoCxF6Xi6GUajRsECbr8DdyGMAu5eE-----结束RSA私钥-----

-----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 的代码,没有运气,同样的错误.

I have tried removing file header and footer manually. I tried code from bouncycastle, no luck, same error.

适用于此文件的 Python 代码:

Python code which works with this file:

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

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

I am doing this for the first time, so bear with me if there is any silly mistake.

部分欺骗 加载来自文件的 RSA 公钥

因此,您编写"(我假设是复制)代码,明确说明您需要两个 DER 格式的文件,其中包含 PKCS8 和X509"编码.(这里 Java 所说的 X.509 实际上是 X.509 的 SubjectPublicKeyInfo component.)你给它一个文件,以 PEM 形式而不是 DER,包含一个 PKCS1 编码而不是 PKCS8 em> X509——你很惊讶它不起作用?Python 之所以能工作,是因为它调用了 OpenSSL,而 OpenSSL 支持十多种私钥的编码和格式,包括这个;Java 仅支持一种(在密钥库之外),而不是这种.(裸)公钥好一点;libcrypto内部支持多种形式,但在实践中只使用了其中两种,其中一种匹配Java——虽然很多公钥都是以X.509证书的形式分发、存储和使用的,这提供了更多令人担忧的形式约.

So, you 'wrote' (I assume, copied) code that clearly says you need two files, in DER form, containing PKCS8 and 'X509' encodings. (What Java calls X.509 here is really the SubjectPublicKeyInfo component of X.509.) You give it one file, in PEM form not DER, containing a PKCS1 encoding not PKCS8 or X509 -- and you're surprised it doesn't work? Python works because it calls OpenSSL, and OpenSSL supports over a dozen encodings and formats for privatekeys, including this one; Java supports only one (outside of keystores) which isn't this one. (Bare) publickeys are a little better; internally libcrypto supports multiple forms, but in practice only two of them are used, and one of them matches Java -- although many publickeys are distributed, stored, and used in the form of X.509 certificates, which provides several more forms to worry about.

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

There are approximately 7 solutions to your situation:

  • 最简单的方法是使用 OpenSSL 命令行将您的一个文件转换为 Java 想要的两个文件:

  • the simplest is to use OpenSSL commandline to convert your one file to the two files Java wants:

# (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

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

Those files can now be read by the code you posted (except with the filenames separated). Note this conversion doesn't have to be done on the same system; if necessary you can do it elsewhere and copy the files, if you use a method that works for binary files i.e. NOT cut&paste.

如果你真的只想要一个文件,但它可以被转换,创建 private.der 文件,只用你的代码中与私钥相关的部分来读取它,然后做:

if you really want only one file, but it can be converted, create the private.der file as above and read it with only the privatekey-related parts of your code, then do:

RSAPrivateCrtKey priv2 = (RSAPrivateCrtKey)privKey;
PublicKey pubkey = keyFactory.generatePublic(new RSAPublicKeySpec(priv2.getModulus(), priv2.getPublicExponent()));

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

  • you could convert the files to PKCS8 and 'X509' PEM by omitting -outform der from the above conversions, then read those files and manually 'de-PEM' by removing the header and trailer lines and converting the base64 to binary (removing or skipping the linebreaks); this results in binary PKCS8 and X509 encodings you can run through your existing code. This is as much work on the openssl side and more work on the Java side so there is no apparent advantage, except that PEM files are valid text and can be cut&pasted if necessary.

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

    combining these, you could convert to PKCS8 PEM only, read that per bullet 3 (de-PEM then the privatekey parts of your code), then extract publickey from privatekey per bullet 2

    在普通 Java 中使用您拥有的(未转换的)格式的一种方法是对每个项目符号 3 进行 de-PEM,为您提供 PKCS1 编码,然后手动构建 PKCS8 编码,然后像以前一样继续运行 PKCS8KeyFactory 并提取每个项目符号 2 的公钥.在 Java:将 DKIM 私钥从 RSA 转换为 JavaMail 的 DER,这是一种非常丑陋的方法(包括一种 de-PEM 方法).如果您使用 BouncyCastle(它有一个用于此 ASN.1 类型的类),则有更好的方法,但如果您使用 BouncyCastle,则最好完全不要使用此方法,请参见下文.

    one way to use the format you have (unconverted) in plain Java is to de-PEM per bullet 3 giving you a PKCS1 encoding, then manually construct the PKCS8 encoding, then proceed as before to run the PKCS8 through the KeyFactory and extract publickey per bullet 2. See my answer at Java: Convert DKIM private key from RSA to DER for JavaMail for a really ugly way to do this (including one de-PEM method). There is a better way if you use BouncyCastle (which has a class for this ASN.1 type), but if you use BouncyCastle it's better not to use this method at all, see below.

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

    another way to use the unconverted format in plain Java is to de-PEM per bullet 3, then parse the ASN.1 structure of PKCS1 and construct an RSAPrivateCrtKeySpec which you can run through your KeyFactory instead of a PKCS8 encoding then extract publickey per bullet 2. This is even more complicated, although I think I have seen it somewhere; will add if I find it. Again BouncyCastle can improve this method, but doesn't need to, see below.

    最后,如果你有 BouncyCastle,那就太容易了.你没有说你尝试过什么没有运气",但下面的 BouncyCastle 代码是你所需要的并且可以工作:

    finally, if you have BouncyCastle it's dead easy. You don't say what you tried with 'no luck', but the following BouncyCastle code is all you need and does work:

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

    请注意,这会为您提供一个 KeyPair,其中包含同一个文件中的私钥和公钥对象.

    Note this gives you a KeyPair with both privatekey and publickey objects from the one file.