2014-04-24 90 views
3

我有用於SSL連接的RSA密鑰和X.509證書。驗證RSA密鑰是否與Java中的X.509證書匹配

密鑰和證書以PEM格式(由OpenSSL生成)存儲在文件中,並在Apache HTTP服務器環境中使用。

是否有一種簡單的方法來驗證密鑰是否與僅使用Java代碼(不執行openssl二進制文件並解析輸出)的證書相匹配,例如通過使用Java安全性和/或Bouncycastle庫方法?

+0

除了owlstead的回答,OWASP還提供了一個關於[證書和公共密鑰鎖定](https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning)上的公共密鑰鎖定的Android Java示例。它使用自定義的'TrustManager',因爲這是Java的方式。我不相信Sun Java和Android Java在代碼方面有什麼區別。 – jww

回答

3

下面的代碼比較公鑰和私鑰的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

+1像往常一樣,一個非常詳細的答案。但是,如果這是實現這一目標最簡明的方法,那就讓我難過。我希望Java標準庫(或第三方庫)能夠使它成爲一個單行(當然,忽略從文件中讀取數據)。 –

+1

@Duncan在這方面有很多選擇。最簡單的方法是創建一個OpenSSL兼容性層。但是OpenSSL非常龐大,沒有很好的記錄。 –

+0

如何在Java中將PEM證書轉換爲pkcs8(無需調用OpenSSL)? – Calonthar

1

非常感謝您對上面的代碼片斷。它的工作與我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> 

很多感謝您的代碼段。