【问题标题】:Validate if RSA key matches X.509 certificate in Java验证 RSA 密钥是否与 Java 中的 X.509 证书匹配
【发布时间】:2014-06-09 13:11:08
【问题描述】:

我有一个用于 SSL 连接的 RSA 密钥和一个 X.509 证书。

密钥和证书以 PEM 格式(由 OpenSSL 生成)存储在文件中,并在 Apache HTTP 服务器环境中使用。

是否有一种简单的方法可以仅使用 Java 代码(不执行 openssl 二进制文件并解析输出)验证密钥是否与证书匹配,例如使用 Java 安全性和/或 Bouncycastle 库方法?

【问题讨论】:

  • 除了 owlstead 的回答之外,OWASP 在Certificate and Public Key Pinning 上提供了一个关于公钥固定的 Android Java 示例。它使用自定义TrustManager,因为那是Java 方式。我认为 Sun Java 和 Android Java 之间的代码没有任何区别。

标签: java ssl-certificate rsa x509 pem


【解决方案1】:

非常感谢上面的代码sn-p。它适用于 bouncycastle 1.51 版

<bcprov-jdk15on-version>1.51</bcprov-jdk15on-version>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>${bcprov-jdk15on-version}</version>
</dependency>

非常感谢您的代码 sn-p。

【讨论】:

    【解决方案2】:

    以下代码将 SHA-1 与公钥和私钥内的模数进行比较。每对的模数应该是唯一的(当然,除非您的密钥对生成机制或随机生成器被破坏)。

    请注意,以下代码要求密钥为未加密的 PKCS#8 格式。最好改用 PKCS#12 并将二进制 PKCS#12 文件加载到 KeyStore 中(提供密码)。

    openssl pkcs8 -topk8 -in key.pem -out keypk8.pem -nocrypt
    

    最后是 Java 代码:

    import static org.bouncycastle.util.encoders.Hex.toHexString;
    
    import java.io.ByteArrayInputStream;
    import java.io.FileReader;
    import java.security.KeyFactory;
    import java.security.MessageDigest;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.KeySpec;
    import java.security.spec.PKCS8EncodedKeySpec;
    
    import org.bouncycastle.util.io.pem.PemObject;
    import org.bouncycastle.util.io.pem.PemReader;
    
    public class CompareCertAndKey {
    
        /**
         * Checks if the certificate and RSA private key match.
         * 
         * @param args the path to the certificate file in args[0] and that of the private key in args[1]
         */
        public static void main(String[] args) {
            try {
                final PemReader certReader = new PemReader(new FileReader(args[0]));
                final PemObject certAsPemObject = certReader.readPemObject();
                if (!certAsPemObject.getType().equalsIgnoreCase("CERTIFICATE")) {
                    throw new IllegalArgumentException("Certificate file does not contain a certificate but a " + certAsPemObject.getType());
                }
                final byte[] x509Data = certAsPemObject.getContent();
                final CertificateFactory fact = CertificateFactory.getInstance("X509");
                final Certificate cert = fact.generateCertificate(new ByteArrayInputStream(x509Data));
                if (!(cert instanceof X509Certificate)) {
                    throw new IllegalArgumentException("Certificate file does not contain an X509 certificate");
                }
    
                final PublicKey publicKey = cert.getPublicKey();
                if (!(publicKey instanceof RSAPublicKey)) {
                    throw new IllegalArgumentException("Certificate file does not contain an RSA public key but a " + publicKey.getClass().getName());
                }
    
                final RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
                final byte[] certModulusData = rsaPublicKey.getModulus().toByteArray();
    
                final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
                final byte[] certID = sha1.digest(certModulusData);
                final String certIDinHex = toHexString(certID);
    
    
                final PemReader keyReader = new PemReader(new FileReader(args[1]));
                final PemObject keyAsPemObject = keyReader.readPemObject();
                if (!keyAsPemObject.getType().equalsIgnoreCase("PRIVATE KEY")) {
                    throw new IllegalArgumentException("Key file does not contain a private key but a " + keyAsPemObject.getType());
                }
    
                final byte[] privateKeyData = keyAsPemObject.getContent();
                final KeyFactory keyFact = KeyFactory.getInstance("RSA");
                final KeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyData);
                final PrivateKey privateKey = keyFact.generatePrivate(keySpec);
                if (!(privateKey instanceof RSAPrivateKey)) {
                    throw new IllegalArgumentException("Key file does not contain an X509 encoded private key");
                }
                final RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
                final byte[] keyModulusData = rsaPrivateKey.getModulus().toByteArray();
                final byte[] keyID = sha1.digest(keyModulusData);
                final String keyIDinHex = toHexString(keyID);
    
                System.out.println(args[0] + " : " + certIDinHex);
                System.out.println(args[1] + " : " + keyIDinHex);
                if (certIDinHex.equalsIgnoreCase(keyIDinHex)) {
                    System.out.println("Match");
                    System.exit(0);
                } else {
                    System.out.println("No match");
                    System.exit(-1);
                }
            } catch (Exception e) {
                e.printStackTrace(System.err);
                System.exit(-2);
            }
        }
    }
    

    【讨论】:

    • +1 像往常一样,一个非常详细的答案。但是,如果这是实现这一目标的最简洁方式,我会感到难过。我曾希望 Java 标准库(或第三方库)能做到这一点(当然,忽略从文件中读取数据)。
    • @Duncan 在此过程中需要做出很多选择。最简单的方法是创建一个 OpenSSL 兼容层。但是 OpenSSL 非常庞大,并且没有很好地记录。
    • 如何在 Java 中将 PEM 证书转换为 pkcs8(不调用 OpenSSL)?
    • OpenSSL 有自己的文件格式,我不确定如果没有编程是否可行。
    猜你喜欢
    • 2021-11-06
    • 2011-10-01
    • 2013-09-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-08
    • 2011-07-09
    • 1970-01-01
    相关资源
    最近更新 更多