2017-06-22 92 views
1

我在寫一個用USB證書(智能卡)加密和簽名的Java程序。我有一個共享庫(在Windows上是.dll,在Linux上是.so),它爲硬件實現了PKCS11。用USB證書加密的Java(智能卡)

我正在尋找現有的解決方案,並找到以下指南http://docs.oracle.com/javase/7/docs/technotes/guides/security/p11guide.html本指南建議使用sun.security.pkcs11.SunPKCS11提供程序。

但是,我有sun.security.pkcs11包的主要問題。我設法做了簽名工作,但我無法進行加密/解密。我正在搜索,發現開發人員不應該使用'太陽'包http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

現在,我想知道我應該用什麼來代替sun.security.pkcs11?

我有一個工作的C++代碼(使用NSS庫來處理硬件)。我發現,NSS庫正在使用C_WrapKey和C_UnwrapKey進行加密。

下面的代碼也許應該使用C_WrapKey和C_UnwrapKey爲encrption,但我可以在Java代碼中調用C_DecryptInit的.so庫由於某種原因失敗的日誌中看到(C_DecryptInit()初始化操作失敗。)。

注意:兩者(Cipher.PUBLIC_KEY/Cipher.PRIVATE_KEY和Cipher.WRAP_MODE/Cipher.UNWRAP_MODE均可在軟證書上正常工作)。該代碼僅適用於Java 1.7(Windows計算機上的32位Java)的硬核證書。

堆棧跟蹤:

Exception in thread "main" java.security.InvalidKeyException: init() failed 
     at sun.security.pkcs11.P11RSACipher.implInit(P11RSACipher.java:239) 
     at sun.security.pkcs11.P11RSACipher.engineUnwrap(P11RSACipher.java:479) 
     at javax.crypto.Cipher.unwrap(Cipher.java:2510) 
     at gem_test.Test.decryptDocument(Test.java:129) 
     at gem_test.Test.main(Test.java:81) 
Caused by: sun.security.pkcs11.wrapper.PKCS11Exception: CKR_KEY_FUNCTION_NOT_PERMITTED 
     at sun.security.pkcs11.wrapper.PKCS11.C_DecryptInit(Native Method) 
     at sun.security.pkcs11.P11RSACipher.initialize(P11RSACipher.java:304) 
     at sun.security.pkcs11.P11RSACipher.implInit(P11RSACipher.java:237) 
     ... 4 more 

代碼:

package gem_test; 

import java.io.ByteArrayInputStream; 
import java.io.FileInputStream; 
import java.security.KeyStore; 
import java.security.PrivateKey; 
import java.security.PublicKey; 
import java.security.Security; 
import java.security.Signature; 
import java.security.cert.X509Certificate; 
import java.util.Enumeration; 

import javax.crypto.Cipher; 
import javax.crypto.SecretKey; 
import javax.crypto.spec.SecretKeySpec; 

import sun.security.pkcs11.SunPKCS11; 

public class Test { 
    private static final String ALGORITHM = "RSA"; 

    static int hard_soft = 1; // 1 - smart card, 2 - soft certificate 
    static int sign_encrypt = 2; // 1- sign, 2 - encryption 


    public static void main(String[] args) throws Exception { 
     PrivateKey privateKey; 
     PublicKey pubKey; 
     if (hard_soft == 1) { 
      String pkcsConf = (
       "name = Personal\n" + 
       "library = /usr/local/lib/personal/libP11.so\n" + 
//     "library = c:\\perso\\bin\\personal.dll\n" + 
       "slot = 0\n" 
      ); 

      char[] pin = "123456".toCharArray(); 
      String useCertAlias = "Digital Signature"; 
//    String useCertAlias = "Non Repudiation"; 

      SunPKCS11 provider = new SunPKCS11(new ByteArrayInputStream(pkcsConf.getBytes())); 
      String providerName = provider.getName(); 
      Security.addProvider(provider); 

      KeyStore keyStore = KeyStore.getInstance("PKCS11", providerName); 
      keyStore.load(null, pin); 

      privateKey = (PrivateKey) keyStore.getKey(useCertAlias, pin); 
      X509Certificate certificate = (X509Certificate) keyStore.getCertificate(useCertAlias); 
      pubKey = certificate.getPublicKey(); 
     } else if (hard_soft == 2) { 
      /* 
      mkdir /tmp/softkey 
      cd /tmp/softkey 

      openssl genrsa 2048 > softkey.key 
      chmod 400 softkey.key 
      openssl req -new -x509 -nodes -sha1 -days 365 -key softkey.key -out softkey.crt 
      openssl pkcs12 -export -in softkey.crt -inkey softkey.key -out softkey.pfx 
      rm -f softkey.key softkey.crt 
      */ 
      String pfx = "/tmp/softkey/softkey.pfx"; 
      String useCertAlias = "1"; 

      KeyStore keyStore1 = KeyStore.getInstance("PKCS12"); 
      keyStore1.load(new FileInputStream(pfx), new char[]{}); 

      privateKey = (PrivateKey) keyStore1.getKey(useCertAlias, new char[]{}); 
      X509Certificate certificate = (X509Certificate) keyStore1.getCertificate(useCertAlias); 
      pubKey = certificate.getPublicKey(); 
     } else { 
      throw new IllegalStateException(); 
     } 

     if (sign_encrypt == 1) { 
      byte[] sig = signDocument("msg content".getBytes(), privateKey); 
      boolean result = verifyDocument("msg content".getBytes(), sig, pubKey); 
      System.out.println("RESULT " + result); 
     } else if (sign_encrypt == 2) { 
      byte[] encrypted = encryptDocument("msg content".getBytes(), pubKey); 
      byte[] decryptedDocument = decryptDocument(encrypted, privateKey); 
      System.out.println("RESULT " + new String(decryptedDocument)); 
     } else { 
      throw new IllegalStateException(); 
     } 
    } 

    private static byte[] signDocument(byte[] aDocument, PrivateKey aPrivateKey) throws Exception { 
     Signature signatureAlgorithm = Signature.getInstance("SHA1withRSA"); 
     signatureAlgorithm.initSign(aPrivateKey); 
     signatureAlgorithm.update(aDocument); 
     byte[] digitalSignature = signatureAlgorithm.sign(); 
     return digitalSignature; 
    } 

    private static boolean verifyDocument(byte[] aDocument, byte[] sig, PublicKey pubKey) throws Exception { 
     Signature signatureAlgorithm = Signature.getInstance("SHA1withRSA"); 
     signatureAlgorithm.initVerify(pubKey); 
     signatureAlgorithm.update(aDocument); 
     return signatureAlgorithm.verify(sig); 
    } 


    private static byte[] encryptDocument(byte[] aDocument, PublicKey pubKey) throws Exception { 
     int encrypt_wrap = 2; 
     if (encrypt_wrap == 1) { 
      Cipher cipher = Cipher.getInstance(ALGORITHM); 
      cipher.init(Cipher.PUBLIC_KEY, pubKey); 
      return cipher.doFinal(aDocument); 
     } else if (encrypt_wrap == 2) { 
      SecretKey data = new SecretKeySpec(aDocument, 0, aDocument.length, "AES"); 
      Cipher cipher = Cipher.getInstance(ALGORITHM); 
      cipher.init(Cipher.WRAP_MODE, pubKey); 
      return cipher.wrap(data); 
     } else { 
      throw new IllegalStateException(); 
     } 
    } 

    public static byte[] decryptDocument(byte[] encryptedDocument, PrivateKey aPrivateKey) throws Exception { 
     int encrypt_wrap = 2; 
     if (encrypt_wrap == 1) { 
      Cipher cipher = Cipher.getInstance(ALGORITHM); 
      cipher.init(Cipher.PRIVATE_KEY, aPrivateKey); 
      return cipher.doFinal(encryptedDocument); 
     } else if (encrypt_wrap == 2) { 
      Cipher cipher = Cipher.getInstance(ALGORITHM); 
      cipher.init(Cipher.UNWRAP_MODE, aPrivateKey); 
      SecretKey res = (SecretKey) cipher.unwrap(encryptedDocument, "AES", Cipher.SECRET_KEY); 
      return res.getEncoded(); 
     } else { 
      throw new IllegalStateException(); 
     } 
    } 
} 
+0

可能與(或重複)[PKCS11Exception:CKR_KEY_FUNCTION_NOT_PERMITTED](https://stackoverflow.com/questions/8887218/pkcs11exception-ckr - 關鍵功能不允許) –

+0

謝謝@ M.Prokhorov:我看到了這個。我有一個C++和NSS庫的工作解決方案,所以硬件(智能卡)應該工作。 – Predkambrij

+0

那麼,你的異常記錄爲「試圖將密鑰用於密鑰的目的,該密鑰的屬性未設置爲允許它執行」。你確定你的java實現使用你的庫嗎?如果是這樣,也許在某處添加一些日誌語句,並檢查所有值是否正確傳遞。 –

回答

0

我認爲解決的辦法就是使用ENCRYPT,而不是WRAPDECRYPT而不是UNWRAP

爲了理解爲什麼,重要的是看看WRAPUNWRAP是做什麼的。基本上他們只是執行ENCRYPTDECRYPT,但他們只是返回一個密鑰。現在如果你用軟件做這個,那麼之外沒有區別,因爲你不需要使用SecretKeySpecSecretKeyFactory從解密的數據重新生成密鑰。

但是,如果您在硬件上執行它,通常所得到的密鑰將保留在硬件設備(或令牌)上。如果您擁有HSM,這當然很好:它只能生成(會話特定的)密鑰並返回句柄。但在智能卡上這通常是不可能的。即使是這樣:您不希望將所有消息發送到智能卡以進行加密。此外,如果使用Java,則不直接控制打包或解包呼叫的PKCS#11輸入參數。


所以儘量ENCRYPTDECRYPT,然後重新生成軟件的關鍵。

或者,您可以複製使用Open Source IAIK包裝庫打包和展開調用的PKCS#11;模仿C的功能。但這不符合需要Cipher類的呼叫。


注意RSA在所有的可能性孫提供商意味着RSA/ECB/PKCS1Padding。如果你需要一個不同的RSA算法,那麼你應該嘗試算法字符串;這也可能是你面臨的問題:你使用錯誤的算法。

+0

感謝您的書面。我需要通用字節數組的加密/解密,而不是加密密鑰。我認爲C_DecryptInit失敗是因爲證書缺少某些屬性(如@ M.Prokhorov提到的),但是不需要用C_WrapKey/C_UnwrapKey執行加密(這可能是C++代碼工作的原因)。我將得到一張啓用了該屬性的智能卡,並使用ENCRYPT/DECRYPT進行測試。謝謝 :) – Predkambrij

0

最後,我們使用該解決方案製作/智能卡從Java 8驗證簽名和加密/解密的問題。它可以在使用64位Java的Linux和Windows上運行。

我們沒有設法解決裹/展開部分。我相信可以用java.lang.instrument來解決這個問題,但是我們決定替換所有的智能卡,以便它們支持「數據加密」。

到猴子的代碼修補JDK 8 SunPKCS11提供商蟲:

String pkcsConf = (
    "name = \"Personal\"\n" + 
    String.format("library = \"%s\"\n", hardCertLib) + 
    String.format("slot = %d\n", slotId) 
); 

SunPKCS11 provider = new SunPKCS11(new ByteArrayInputStream(pkcsConf.getBytes())); 
tryFixingPKCS11ProviderBug(provider); 

.... 


/** 
* This a fix for PKCS11 bug in JDK8. This method prefetches the mech info from the driver. 
* @param provider 
*/ 
public static void tryFixingPKCS11ProviderBug(SunPKCS11 provider) { 
    try { 
     Field tokenField = SunPKCS11.class.getDeclaredField("token"); 
     tokenField.setAccessible(true); 
     Object token = tokenField.get(provider); 

     Field mechInfoMapField = token.getClass().getDeclaredField("mechInfoMap"); 
     mechInfoMapField.setAccessible(true); 
     @SuppressWarnings("unchecked") 
     Map<Long, CK_MECHANISM_INFO> mechInfoMap = (Map<Long, CK_MECHANISM_INFO>) mechInfoMapField.get(token); 
     mechInfoMap.put(PKCS11Constants.CKM_SHA1_RSA_PKCS, new CK_MECHANISM_INFO(1024, 2048, 0)); 
    } catch(Exception e) { 
     logger.info(String.format("Method tryFixingPKCS11ProviderBug failed with '%s'", e.getMessage())); 
    } 
}