使用USB证书进行Java加密(智能卡)

使用USB证书进行Java加密(智能卡),java,encryption,smartcard,pkcs#11,Java,Encryption,Smartcard,Pkcs#11,我正在编写一个Java程序,它使用USB证书(智能卡)进行加密和签名。我有一个共享库(.dll在Windows上,.so在Linux上),它为硬件实现了PKCS11 我正在搜索现有的解决方案,并找到了该指南建议使用sun.security.pkcs11.SunPKCS11提供程序的以下指南 但是,我在sun.security.pkcs11包中遇到了一些主要问题。我设法使签名生效,但无法进行加密/解密。我在搜索时发现,开发人员不应该使用“sun”软件包 现在,我想知道应该用什么来代替sun.sec

我正在编写一个Java程序,它使用USB证书(智能卡)进行加密和签名。我有一个共享库(.dll在Windows上,.so在Linux上),它为硬件实现了PKCS11

我正在搜索现有的解决方案,并找到了该指南建议使用sun.security.pkcs11.SunPKCS11提供程序的以下指南

但是,我在sun.security.pkcs11包中遇到了一些主要问题。我设法使签名生效,但无法进行加密/解密。我在搜索时发现,开发人员不应该使用“sun”软件包

现在,我想知道应该用什么来代替sun.security.pkcs11

我有一个工作C++代码(使用NSS库来处理硬件)。我发现,NSS库正在使用C_WrapKey和C_UnwrapKey进行加密

下面的代码可能应该使用C_WrapKey和C_UnwrapKey进行加密,但我可以在.so库的日志中看到java代码调用C_DecryptInit,但由于某种原因失败(C_DecryptInit()Init操作失败)

注意:两者(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
代码:


我认为解决方法很简单,就是使用
加密
而不是
包装
解密
而不是
展开

要了解原因,重要的是查看
WRAP
UNWRAP
的作用。基本上,它们只执行
加密
解密
,但它们只返回一个密钥。现在,如果您在软件中执行此操作,那么除了不需要使用
SecretKeySpec
SecretKeyFactory
从解密数据重新生成密钥之外,没有什么区别

但是,如果在硬件上执行该操作,则生成的密钥通常会保留在硬件设备(或令牌)上。如果您拥有一个HSM,这当然很好:它可以生成一个(特定于会话的)密钥并返回一个句柄。但在智能卡上,这通常是不可能的。即使是这样:你也不想把所有的信息都发送到智能卡,让它进行加密

此外,如果使用Java,则无法直接控制包装或展开调用的PKCS#11输入参数


因此,请尝试
加密
解密
,然后在软件中重新生成密钥

或者,您可以使用开源IAIK包装库复制PKCS#11包装和展开调用;模仿C的功能。但这与需要
Cipher
类的调用不兼容



请注意,
RSA
in-Sun providers在所有可能性中都意味着
RSA/ECB/PKCS1Padding
。如果您需要一个不同的RSA算法,那么您应该使用算法字符串进行实验;这也可能是您所面临的问题:您使用了错误的算法。

我认为解决方案只是使用
加密
,而不是
包装
解密
,而不是
展开

要了解原因,重要的是查看
WRAP
UNWRAP
的作用。基本上,它们只执行
加密
解密
,但它们只返回一个密钥。现在,如果您在软件中执行此操作,那么除了不需要使用
SecretKeySpec
SecretKeyFactory
从解密数据重新生成密钥之外,没有什么区别

但是,如果在硬件上执行该操作,则生成的密钥通常会保留在硬件设备(或令牌)上。如果您拥有一个HSM,这当然很好:它可以生成一个(特定于会话的)密钥并返回一个句柄。但在智能卡上,这通常是不可能的。即使是这样:你也不想把所有的信息都发送到智能卡,让它进行加密

此外,如果使用Java,则无法直接控制包装或展开调用的PKCS#11输入参数


因此,请尝试
加密
解密
,然后在软件中重新生成密钥

或者,您可以使用开源IAIK包装库复制PKCS#11包装和展开调用;模仿C的功能。但这与需要
Cipher
类的调用不兼容



请注意,
RSA
in-Sun providers在所有可能性中都意味着
RSA/ECB/PKCS1Padding
。如果您需要一个不同的RSA算法,那么您应该使用算法字符串进行实验;这也可能是您面临的问题:您使用了错误的算法。

最后,我们使用此解决方案解决了使用智能卡从Java 8生成/验证签名和加密/解密的问题。它可以在Linux和Windows上使用64位Java

我们还没有设法修复包裹/展开部分。我相信可以用
java.lang.instrument
修复这个bug,但是我们决定替换所有智能卡,以便它们支持“数据加密”

修补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()));
    }
}
字符串pkcsConf=(
“名称=\”个人\“\n”+
格式(“库=\%s\”\n,hardCertLib)+
格式(“插槽=%d\n”,插槽ID)
);
SunPKCS11 provider=newsunpkcs11(newbytearrayinputstream(pkcsConf.getBytes());
tryFixingPKCS11ProviderBug(提供者);
....
/**
*这是对JDK8中PKCS11错误的修复。此方法从驾驶员处预取机械信息。
*@param提供程序
*/
公共静态无效tryFixingPKCS11ProviderBug(SunPKCS11提供程序){
试一试{
字段tokenField=SunPKCS11.class.getDeclaredField(“令牌”);
tokenField.setAccessible(true);
对象令牌=tokenField.get(提供者);
场信息映射
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()));
    }
}