当前位置: 首页 > 知识库问答 >
问题:

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

宇文梓
2023-03-14

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

我正在搜索现有的解决方案,并找到以下指南http://docs.oracle.com/javase/7/docs/technotes/guides/security/p11guide.html指南建议使用sun。安全pkcs11.SunPKCS11提供程序。

但是,我sun.security.pkcs11包有重大问题。我设法使签名工作,但我无法进行加密/解密。我正在搜索,发现开发人员不应该使用“太阳”包http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

现在,我想知道我应该用什么来代替太阳。安全pkcs11?

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

下面的代码可能应该使用C\u WrapKey和C\u UnwrapKey进行加密,但我可以在的日志中看到。所以java代码调用C\u DecryptInit的库由于某种原因失败(C\u DecryptInit()Init操作失败)。

注意:这两种方式(Cipher.PUBLIC\u KEY/Cipher.PRIVATE\u KEY和Cipher.WRAP\u MODE/Cipher.UNWRAP\u 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();
        }
    }
}

共有2个答案

施阳曜
2023-03-14

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

我们还没有设法修复包装/展开部分。我相信可以用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()));
    }
}
逑景铄
2023-03-14

我认为解决方案只是使用ENCRYPT代替WRAPDECRYPT代替UNWRAP

要了解原因,重要的是要了解WRAPUNWRAP的作用。基本上,它们只执行ENCRYPTDECRYPT,但它们只是返回一个密钥。现在,如果您在软件中这样做,那么没有区别,除了您不需要使用SecKeySpecSecKeyFactory从解密的数据中重新生成密钥。

然而,如果您在硬件上执行它,那么通常生成的密钥将保存在硬件设备(或令牌)上。如果您拥有HSM,这当然很好:它可以生成一个(会话特定的)密钥并返回一个句柄。但是在智能卡上,这通常是不可能的。即使是这样:您也不想将所有消息发送到智能卡以让它加密。

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

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

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

请注意,Sun providers中的RSA很可能意味着RSA/ECB/PKCS1Padding。如果您需要不同的RSA算法,那么您应该尝试使用算法字符串;这也可能是您面临的问题:您使用了错误的算法

 类似资料:
  • 我正试图从智能卡读取证书, 这是我从http://www.developer.com/java/other/article.php/3587361/Java-Applet-for-Signing-with-a-Smart-Card.htm我将在applet中执行这段代码,问题是每个用户都必须向我指出他们的本地pkcs11。。。dll,使用java samrtcard api是否可以避免加载此dll

  • 我正在尝试测试一个KnoxToken服务背后的应用程序,用户需要使用智能卡和他的PIN。使用JMeter来测试没有安全特性的应用程序,效果和预期的一样。 在这种情况下,我得到一个错误:javax . net . SSL . SSL handshakeexception:远程主机在握手期间关闭了连接 从Jmeter- 从日志文件:

  • 摘要:在PKCS11和OpenSC上使用JCA时,在提取证书时会请求PIN。 我有一个应用程序需要使用智能卡签名。智能卡由OpenSC支持,所以我使用Java内置的pkcs11包装提供程序来使用它。出于功能原因,我需要在不需要PIN的情况下获得卡中的证书。如果用户最终签名,那么当然需要PIN。 我明白了,我可以在命令行中完成,而无需提供PIN: 到目前为止,一切顺利。 Oracle的文档清楚地说,

  • 我正在尝试在我的web应用程序中使用小程序来使用智能卡加密一些数据。我正在关注这个链接:http://www.developer.com/java/other/article.php/3587361/Java-Applet-for-Signing-with-a-Smart-Card.htm 我能够读取存储在智能卡中的证书,并使用证书加密数据。但它需要我传递PKCS#11实现库文件(.dll)和智能

  • 我想使用USB令牌(加密狗)中的用户密钥和证书对文件进行签名。 我已经在stackoverflow和其他网站上搜索了一段时间,但除了中的一些好功能之外,没有得到任何有用的东西。NET framework(我没有使用)。 似乎由于密钥没有暴露,加密是由硬件本身完成的。这是否意味着每个硬件制造商都提供自己的API,并且没有通用方法来解决这个问题? 此外,我还了解到,一旦令牌插入计算机,其证书就会加载到

  • 问题内容: 我有一个IC接触式读卡器和SLE5528智能卡。想知道如何真正开始使用这些物品。 正在读取读取器,插入智能卡后看不到任何影响。 我还从http://www.openscdp.org/安装了opensmart的智能卡外壳 但是我不能用它来读任何读卡器。我想知道它是否有兼容性问题。 请我知道我可能不恰当地提出了这个问题,但是请那里的任何人帮助我。 任何相关的链接或有用的信息都可以帮助我入门