Java encrypting with USB certificates (smart cards)

I'm writing a Java program which is encrypting and signing with USB certificates (smart cards). I have a shared library (.dll on Windows, .so on Linux) which implements PKCS11 for the hardware.

I was searching for existing solutions and found the following guide http://docs.oracle.com/javase/7/docs/technotes/guides/security/p11guide.html The guide suggests to use sun.security.pkcs11.SunPKCS11 provider.

However, I have major problems with sun.security.pkcs11 package. I managed to make signature working, but I cannot make encryption / decryption. I was searching and found that developers should not use 'sun' packages http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

Now, I'm wondering what should I use instead of sun.security.pkcs11?

I have a working C++ code (which is using NSS library to work with the hardware). I found, that NSS library is using C_WrapKey and C_UnwrapKey for encrption.

The following code should probably use C_WrapKey and C_UnwrapKey for encrption, but I can see in the logs of the .so library that the java code calls C_DecryptInit which for some reason fails (C_DecryptInit() Init operation failed.).

Note: Both (Cipher.PUBLIC_KEY/Cipher.PRIVATE_KEY and Cipher.WRAP_MODE/Cipher.UNWRAP_MODE works fine with soft certificates). The code works with hard certificates only with Java 1.7 (32-bit Java on Windows machine).

Stack trace:

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

Code:

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 = Personaln" +
                "library = /usr/local/lib/personal/libP11.son" +
//                    "library = c:persobinpersonal.dlln" +
                "slot = 0n"
            );

            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();
        }
    }
}

I think the solution is simply to use ENCRYPT instead of WRAP and DECRYPT instead of UNWRAP .

To understand why, it is important to see what WRAP and UNWRAP do. Basically they perform simply ENCRYPT and DECRYPT but they simply return a key. Now if you do this in software then there is no difference except for the fact that you don't need to use a SecretKeySpec or SecretKeyFactory to regenerate a key from the decrypted data.

However if you perform it on hardware then generally the resulting key will be kept on the hardware device (or Token). This is of course fine if you possess a HSM: it can just generate a (session specific) key and return a handle. But on smart cards this is generally not possible. And even if it was: you don't want to send all of the message to the smart card to let it encrypt.

Furthermore, if you use Java you don't directly have control over the PKCS#11 input parameters to the wrapping or unwrapping calls.


So try ENCRYPT and DECRYPT and then regenerate the key in software.

Alternatively you could replicate the PKCS#11 wrap and unwrap calls using the Open Source IAIK wrapper library; mimicking the C functionality. But that would not be compatible with calls that require the Cipher class.


Note that RSA in Sun providers in all likelihood means RSA/ECB/PKCS1Padding . If you need a different RSA algorithm then you should experiment with the algorithm string; this could also be the problem that you are facing: that you use the wrong algorithm.


In the end we used this solution for the problem of making / verifying signatures and encryption / decryption from Java 8 with smart cards. It works on both Linux and Windows using 64 bit Java.

We haven't managed to fix the Wrap / Unwrap part. I believe that it would be possible to fix the bug with java.lang.instrument , but instead we decided to replace all smartcards so that they support "Data Encipherement".

The code to monkey patch the JDK 8 SunPKCS11 provider bug:

String pkcsConf = (
    "name = "Personal"n" +
    String.format("library = "%s"n", hardCertLib) +
    String.format("slot = %dn", 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()));
    }
}
链接地址: http://www.djcxy.com/p/73822.html

上一篇: Java 9中的SunPKCS11提供程序

下一篇: 用USB证书加密Java(智能卡)