如何在 Java 中使用 OpenSSL 生成的密钥?

信息安全 加密 证书 电子签名
2021-08-23 22:42:26

我有一对公钥和私钥以及我的证书。那些在 OpenSSL 中创建的(为什么?因为我被要求在 OpenSSL 中创建)。现在我想用这些东西来制作一个使用数字签名的 Java 应用程序。如何使用我的私钥和公钥来加密/解密信息(并使用证书来查看数据是否有效?Java 中有哪些类允许我这样做?

我的密钥是这样的纯文本:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDuyg3h0VbP9iZ6RCxSU6x4WX4
anAwedMVUTqF0WHlvHl1Kiqa6N6TiUk23uXAVUX8RwLFjXWHlG0xwW7mGByA2mX9
5oPQpQFu8C70aMuUotGv87iiLi0UKCZV+9wS9rMdg5LHu1mMPilwgOO6MlyTxKem
-----END PUBLIC KEY-----

更新

我制作了这段代码,但仍然无法使用私钥对字符串进行签名。

public void encryptHash(String hashToEncrypt, String pathOfKey, String Algorithm) {
    FileInputStream fis = null;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int len;

        File f = new File(pathOfKey);

        fis = new FileInputStream(pathOfKey);
        len = 0;
        while((len = fis.read()) != -1){
            baos.write(len);
        }

        KeyFactory kf = KeyFactory.getInstance(Algorithm); //Algorithm = "RSA"
        KeySpec keySpec = new PKCS8EncodedKeySpec(baos.toByteArray());
        baos.close();
        PrivateKey privateKey = kf.generatePrivate(keySpec);  //Here's the exception thrown

        Signature rsaSigner = Signature.getInstance("SHA1withRSA");
        rsaSigner.initSign(privateKey);

        fis = new FileInputStream(hashToEncrypt);
        BufferedInputStream bis = new BufferedInputStream(fis);
        byte[] buffer = new byte[1024];
        len = 0;
        while((len = bis.read(buffer)) >= 0){
            try {
                rsaSigner.update(buffer, 0, len);
            } catch (SignatureException ex) {
                Logger.getLogger(DataEncryptor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        bis.close();

        byte[] signature = rsaSigner.sign();

        System.out.println(new String(signature));
}

我得到的例外是

dic 09, 2011 12:49:02 PM firmaelectronica.DataEncryptor encryptHash
Grave: null
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : DER input, Integer tag error
    at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
    at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
    at firmaelectronica.DataEncryptor.encryptHash(DataEncryptor.java:40)
    at firmaelectronica.FirmaElectronica.main(FirmaElectronica.java:39)
Caused by: java.security.InvalidKeyException: IOException : DER input, Integer tag error
    at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:361)
    at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:367)
    at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:91)
    at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75)
    at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:316)
    at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:213)
    ... 3 more
1个回答

如果您有这种形式的公钥(而不是在证书中),我建议使用BouncyCastlePEMReader. 它的readObject()方法可以读取很多格式:公钥、证书、私钥(尽管您可能需要使用带有密码的方法)...

如果您不想使用 BouncyCastle,可以使用CertificateFactory读取证书(参见示例)。在 InputStream 中使用 PEM 格式的证书:

CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);

对于私钥,如果您的私钥是 DER 格式的 PKCS#8 结构,您可以使用PKCS8EncodedKeySpec直接读取它。例如:

KeyFactory kf = KeyFactory.getInstance("RSA");
// Read privateKeyDerByteArray from DER file.
KeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDerByteArray);
PrivateKey key = kf.generatePrivate(keySpec);

您可以使用将您的私钥转换为 PKCS#8 openssl pkcs8 -topk8(请记住-outform DER,您可能还需要检查密码套件,因为 Java 和 OpenSSL 可能并不普遍支持所有密码套件)。

  • 从密钥库使用的角度来看:

如果您不想为处理密钥做太多编程,在 Java 和 OpenSSL 之间切换,使用 PKCS#12 格式很方便。

如果您使用 OpenSSL 生成的密钥和证书尚未在 p12 容器中:

openssl pkcs12 -export -in cert.pem -inkey key.pem -out store.p12

通常,您可以直接使用 Java 的 " PKCS12" 密钥库类型(而不是JKS默认情况下的 " ")。

keytool如果需要,您可以使用(Java 6+)将此 PKCS12 密钥库转换为另一种格式(例如 JKS ):

keytool -importkeystore -srckeystore store.p12 -srcstoretype PKCS12 \
     -destkeystore store.jks -deststoretype JKS

(本质上,与此问题中描述的操作相反。)

无论哪种方式,无论是通过使用PEMReader还是通过从 a 加载您的密钥/证书KeyStore,您都应该能够获得PrivateKeyand Certificate(或PublicKey直接)的实例。

您可以通过使用其方法Certificate与给定公钥匹配的私钥验证 a 的签名是否已完成。verify(PublicKey)

有了它们,您还可以使用数字签名 API对于任何文档签名,它都是一个更通用的 API,我不一定会用它来验证证书签名(我宁愿为此使用证书路径 API,因为它也会构建链)。