繁体   English   中英

如何在Java中将私钥写入受密码保护的DER格式?

[英]How to write private key to password protected DER format in Java?

我有一个用Java生成的RSA密钥对,我需要以编程方式将私钥写入与运行此命令时openssl相同的格式(并为提示符输入适当的数据,即保护私钥的密码短语) ):

openssl req -out request.csr -newkey rsa:2048 -keyout privkeyfile

生成密钥对的Java代码非常标准:

KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();

(在我的Windows计算机上)openssl命令的运行示例输出为:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI1c9lGdEA388CAggA
MBQGCCqGSIb3DQMHBAgKdWZEOyS2XgSCBMgI7b/vAeE6yz136BZkOzOPLv0uTz/Z
5mP4xO8IdAybE+PHJ71Mro4kgz+EMN39dk0ZWxbNnPpHGD+a6LfNKxos8fJL+dbz
dgc7I4fH9UQFLnYM64Xmq4aG66fIehuhqXBUUru+PJdBf5bfPDJDYAVEUsZ2J8bW
n4pLeS62Orwe+hhe/i/Y4gmgGAxGhUlDGc7T/N4RvhWUVNXQKGCGynj9Mv+LRzW6
rkGbBALQAKnj2tuihGSKhhR3WoNxTFqU9HsRGkzbJ5AiYhyBk7ObQw285TyIQS6k
OH25byIeaqzQ3Xn/wB6VrOQrsCvbWim1DZEIGRp6B+RKd0vUrkxFZRBsLdGXN1w/
bCM4dmhUJng2O+a9tSif7CC0emJqXgvkE2lGA9RMZltfOK+Kohi4L0WKxIX8TQqP
KzDhecSOaOkdXI0Tpho1QZDS6D+nvN2OiXlswgB0By3pkLdx4j7ZtjhqH/cg4rlE
8R1HzcilIrSlMK579UNGieis2wHaWobeinJqP6ruHK3HiAvG/WLFQ4TKexFa4/gy
EdXPaV9owRi+9nyRZGT8NsfzUDg5oTBLcg08uOlNHr8z8pF6a7l2sr4bzgcYFlko
BCIunMJpXYp/lUnL9daElbOPbGgeLNa8KfU7tXnzYsCg3iUx79fQUoql2pn2wMc3
0vQVTZ7/Enzl8cM2srl0uf1JMxMGOJ2kbdYZ8VwxaaMHnghN97eBsp+aRFCuAN4x
+D90ABBxRcBwzBOf8sT77vYXvQZNqUnzl5GJh2hlXCB5upNFqbSGaa6Yk+y4cw5e
3tB3/BHwZop2AAnPexnnQuCsn+SpCiLF+/agMouph61oWWJYQMwmUemNy/5G6AoP
KdBGqBAXonRSk8pBNqglHl0GOiBITl45+Bk4JBGM6+NcEpQ8B3OA+Vkj0n/aF/Iw
66Fo+UyA64fboC3q6DLxHZuTAY/giytwUW2QM4yFkEOm1v1WisTf0MO3Zt+ghuBn
8DG9MXGxP0XA9QzHAjCcDD8DK/hXsxaBg6xOV4bV+HhhJXsyWQAqcqKQro9Ik3L9
YvdJNU9BWyzKV79j5gYkDgLZgcA8QrGDArFZ5Hr9HdepBu8Njk09YDKJsfVMmk4E
6NbzxqHgPyYY3QtANLKg3EImBfuRHwfgfbaamrmYE0fSyh/QJMK0zDtDpkgiiTre
A8b16rBdBxZBSaO/J+Oje0pePLBRRhwX4WxcPsZeN5fO6S2NTECrWsf0jDG4D6pa
cannasXB4LoBifAYhKKTXFbQRY74wOxVfI7gw0qEjB7Jb1M2zCMwddgumOiCzGpu
d9voABJdGMdhwZ/FLbuxcr0y3p8Y5N9vW8ffSlxEtvhbPlszpgTPi2WWTNE+wTUQ
so4cvWFq9SP3Mg6Te6AStjdN1Mnhj2fb7ogxa5rsNxVrE/guRVgly4i9vG7Mi2Wd
bhT1vQypyL9g97nq0rRznDAjAtLenOagK4h+WJgZN2RpUhkWmO1trLGao/PrhgvD
8mOMCnZIQGMk5vS55druRoakPjsx4yZpzZvw5gPBXJ0H1KmbFUO1aSy/6N4nVBW+
Khr+ZHxboPD0zxJMzANjuOIJ/C46Hx5Wb/VP49NDmOLzLAi3+YSAhi3PB9D8vzxQ
MwM=
-----END ENCRYPTED PRIVATE KEY-----

编辑更改了openssl的示例输出

编辑我尝试使用下面的代码读取Java生成的openssl生成的私钥文件,以尝试获取一些参数,但最终出现以下异常:

Exception in thread "main" java.io.IOException: ObjectIdentifier() -- data isn't an object ID (tag = 48)
    at sun.security.util.ObjectIdentifier.<init>(Unknown Source)
    at sun.security.util.DerInputStream.getOID(Unknown Source)
    at com.sun.crypto.provider.PBES2Parameters.engineInit(PBES2Parameters.java:267)
    at java.security.AlgorithmParameters.init(Unknown Source)
    at sun.security.x509.AlgorithmId.decodeParams(Unknown Source)
    at sun.security.x509.AlgorithmId.<init>(Unknown Source)
    at sun.security.x509.AlgorithmId.parse(Unknown Source)
    at javax.crypto.EncryptedPrivateKeyInfo.<init>(EncryptedPrivateKeyInfo.java:95)
    at crypto.ReadOpensslKey.main(ReadOpensslKey.java:35)

读取文件的Java代码:

package crypto;

import org.bouncycastle.util.encoders.Base64;
import javax.crypto.EncryptedPrivateKeyInfo;  
import javax.crypto.SecretKeyFactory;  
import javax.crypto.spec.PBEKeySpec;  
import java.io.IOException;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.security.InvalidKeyException;  
import java.security.KeyFactory;  
import java.security.NoSuchAlgorithmException;  
import java.security.PrivateKey;  
import java.security.spec.InvalidKeySpecException;  
import java.security.spec.PKCS8EncodedKeySpec; 

public class ReadOpensslKey {

    public static void main(String[] args) throws Exception {

        String encrypted = new String(Files.readAllBytes(Paths.get("<insert path to openssl generated privkeyfile>")));  

        //Create object from encrypted private key  
        encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");  
        encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");  
        EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.decode(encrypted));  // exception is thrown here
        System.out.println(pkInfo.getAlgName());
        PBEKeySpec keySpec = new PBEKeySpec("abcde".toCharArray()); // password  
        SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());  
        PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(pbeKeyFactory.generateSecret(keySpec));  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        PrivateKey encryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);  
    }

}

首先,您是否真的需要这种特定格式,或者仅可以使用OpenSSL格式(以及使用OpenSSL的程序,例如Apache httpd和nginx以及curl和PHP等)? 如果是后者,那么还有其他一些更简便,更好的选择。 但是您没有要求,所以我不会回答。

其次,您必须有一个非常老的OpenSSL。 从2010年的1.0.0版本开始, req -newkey -keyout写入PKCS8格式,而不是传统的aka遗留格式。

第三,这种格式是PEM而不是DER; 有传统的DER格式,但无法加密。 (可以使用DER或PEM对PKCS8进行加密。)

第四,如果可以使用BouncyCastle,则可以直接执行此操作。 来自bcpkix的版本(任何最新版本),在标准JCE PrivateKey上使用org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator和一个JcePEMEncryptorBuilder指定DES-EDE3-CBC在内存中创建PEMObject ,然后由PEMWriter写出。 即使您实际上不能使用BC,它也是开源的(而且IMO的设计很好,尽管大多被轻描淡写了),它可以帮助您查看其代码。

那些人说,您所要求的内容(几乎)由PEM_write_RSAPrivateKey手册页PEM_write_RSAPrivateKey (应在您的系统上,但由于您的版本较旧,您最好使用网络副本 )在末尾“ PEM ENCRYPTION FORMAT”部分中,并结合参考的EVP_BytesToKey手册页。 特别:

  • 从PKCS1(当前为rfc8017)构造为RSAPrivateKey的“传统”编码,而不是JCE PrivateKey.getEncoded()返回的PKCS8 / rfc5208 PrivateKeyInfo编码。 PKCS8编码确实包含PKCS1编码的一部分(PKCS8是围绕任何数量的算法特定编码的算法通用包装器),因此您可以从PKCS8中提取PKCS1(如BC一样),也可以直接从PKCS1中构造PKCS1。关键组件n,e,d,p,q,dmp1,dmq1,qinvp。 otherPrimeInfos仅适用于所谓的“ otherPrimeInfos数” RSA,它表示n的两个以上因子,几乎没有人实际使用。)

  • 使用应用于password||salt的MD5的一个(!)迭代从密码中得出实际的加密密钥,其中salt是随机IV(3DES为8字节)的副本加上(因为不够) MD5(firstblock||password||salt)然后将总数截断为24个字节。

  • 用CBC(上面的IV)和PKCS5填充用3DES(JCE称为DESEDE)加密。 (对于3DES或DES,密钥每个字节的低位名义上都是奇偶校验,但是您不需要设置它们,因为JCE没有实现它们。)

  • 转换为base64,每64个字符使用换行符,并添加BEGIN和END行以及标头行,如您所正确猜测的那样,以十六进制显示IV

我最终使用以下代码使它工作:

import java.io.FileOutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.util.io.pem.PemObject; 

public class WriteOpensslKey {

    public static void main(String[] args) throws Exception {

        // provider is needed for the encryptor builder
        Security.addProvider(new BouncyCastleProvider());

        // generate key pair
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");  
        kpGen.initialize(2048, new SecureRandom());  
        KeyPair keyPair = kpGen.generateKeyPair();  

        // construct encryptor builder to encrypt the private key
        JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.AES_256_CBC);  
        encryptorBuilder.setRandom(new SecureRandom());  
        encryptorBuilder.setPasssword("password".toCharArray());
        OutputEncryptor encryptor = encryptorBuilder.build();  

        // construct object to create the PKCS8 object from the private key and encryptor
        JcaPKCS8Generator pkcsGenerator = new JcaPKCS8Generator(keyPair.getPrivate(), encryptor);  
        PemObject pemObj = pkcsGenerator.generate();  
        StringWriter stringWriter = new StringWriter();  
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {  
            pemWriter.writeObject(pemObj);  
        }  

        // write PKCS8 to file
        String pkcs8Key = stringWriter.toString();  
        FileOutputStream fos = new FileOutputStream("<path to output file>");  
        fos.write(pkcs8Key.getBytes(StandardCharsets.UTF_8));  
        fos.flush();  
        fos.close();  

    }

}

然后,我可以将此私人密钥与openssl一起使用,以对文件进行签名,以对其进行快速测试。

非常感谢@ dave_thompson_085向我指出了正确的方向!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM