Is it possible to store secret keys on SoftHSM? - java

I've found this thread: Connecting to SoftHSM java and it works when storing private keys, just like the example.
But I need to store secret keys, such as AES.
Here's my code:
import java.security.*;
import sun.security.pkcs11.*;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) throws Exception {
// Set up the Sun PKCS 11 provider
String configName = "softhsm.cfg";
Provider p = new SunPKCS11(configName);
if (-1 == Security.addProvider(p)) {
throw new RuntimeException("could not add security provider");
}
// Load the key store
char[] pin = "mypin".toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS11", p);
keyStore.load(null, pin);
// AES key
SecretKeySpec secretKeySpec = new SecretKeySpec("0123456789ABCDEF".getBytes(), "AES");
Key key = new SecretKeySpec(secretKeySpec.getEncoded(), "AES");
keyStore.setKeyEntry("AA", key, "1234".toCharArray(), null);
keyStore.store(null); //this gives me the exception.
}
}
And this is the softhsm.cfg file:
name = SoftHSM
library = /usr/local/lib/softhsm/libsofthsm.so
slot = 0
attributes(generate, *, *) = {
CKA_TOKEN = true
}
attributes(generate, CKO_CERTIFICATE, *) = {
CKA_PRIVATE = false
}
attributes(generate, CKO_PUBLIC_KEY, *) = {
CKA_PRIVATE = false
}
When executing keyStore.store(null) I'm getting java.security.KeyStoreException Cannot convert to PKCS11 keys

Turns out the Exception occurs with SoftHSMv1. I installed SoftHSMv2, wich you can get using git to download it from GitHub, and use it to store secret keys. Don't download SoftHSMv2 from the OpenDNSsec website because it won't work!!.
Finally I had to change the softhsm.cfg file to point the new library and for some reason I ignore, SoftHSM2 changes the number of the initializated slot, you can verify it using sudo softhsm2-util --show-slots
softhsm.cfg:
name = SoftHSM
library = /usr/local/lib/softhsm/libsofthsm2.so
slot = 498488451
attributes(generate, *, *) = {
CKA_TOKEN = true
}
attributes(generate, CKO_CERTIFICATE, *) = {
CKA_PRIVATE = false
}
attributes(generate, CKO_PUBLIC_KEY, *) = {
CKA_PRIVATE = false
}
My code:
import java.security.*;
import sun.security.pkcs11.*;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) throws Exception {
// Set up the Sun PKCS 11 provider
String configName = "softhsm.cfg";
Provider p = new SunPKCS11(configName);
if (-1 == Security.addProvider(p)) {
throw new RuntimeException("could not add security provider");
}
// Load the key store
char[] pin = "mypin".toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS11", p);
keyStore.load(null, pin);
// AES key
SecretKeySpec secretKeySpec = new SecretKeySpec("0123456789ABCDEF".getBytes(), "AES");
Key key = new SecretKeySpec(secretKeySpec.getEncoded(), "AES");
keyStore.setKeyEntry("AA", key, "1234".toCharArray(), null);
keyStore.store(null); //this no longer gives me the exception.
Enumeration<String> aliases = keyStore.aliases();
while(aliases.hasMoreElements()){
String alias = aliases.nextElement();
System.out.println(alias + ": " + keyStore.getKey(alias,"1234".toCharArray()));
}
}
}
Which gives me the output:
AA: SunPKCS11-SoftHSM AES secret key, 16 bits (id 2, token object, not sensitive, unextractable)
If you try to get the key using something like keyStore.getKey("AA", "1234".toCharArray()); you'll get an objet with some attributes of the key but you wont be able to use .getEncoded() to actually get the key itself since it is unextractable.

Related

Derive Shared Secret From ECDH with existing foreign public key

Im converting a method from nodeSJ to Java, but im having trouble getting it to work. Im stuck trying to calculate a derived shared secret.
Hoping someone can catch what im doing wrong porting over the nodeJS to Java.
NodeJS code:
//the public_key param here is from a different device.
sign: function(public_key)
{
//dummy values
var PRE_SALT_VALUE = 'f0f0f0f0f0';
var POST_SALT_VALUE = '0101010101';
const crypto = require('crypto');
var sha512 = crypto.createHash("sha512");
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair(); //key1 is gen before pub key
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key gen from saved cert
var derived_secret = key1.derive(key2.getPublic());
var derived_secret = Buffer.from(derived_secret.toString(16), 'hex')
var public_key_client = key1.getPublic('hex')
var pre_salt = Buffer.from(PRE_SALT_VALUE, 'hex')
var post_salt = Buffer.from(POST_SALT_VALUE, 'hex')
derived_secret = Buffer.from(pre_salt.toString('hex')+derived_secret.toString('hex')+post_salt.toString('hex'), 'hex') // finalyze shared secret
// Hash shared secret
var sha = sha512.update(derived_secret);
derived_secret = sha.digest();
return {
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
secret: derived_secret.toString('hex')
}
}
In progress Java Code:
//load cert from pem string (passed in from file), foreign cert
ByteArrayInputStream input = new ByteArrayInputStream(pem);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(input);
X509Certificate x509Cert = (X509Certificate) cert;
// get pub key from cert
PublicKey publicKeyForSignature = x509Cert.getPublicKey();
// Generate ephemeral ECDH keypair KEY1
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
kpg.initialize(256);
KeyPair kp1 = kpg.generateKeyPair();
byte[] ourPk = kp1.getPublic().getEncoded(); //use this later
//load KEY2 from others public key
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(publicKeyForSignature.getEncoded());
PublicKey otherPublicKey = kf.generatePublic(pkSpec);
// Perform key agreement
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp1.getPrivate());
ka.doPhase(otherPublicKey, true);
// Read shared secret
byte[] sharedSecret = ka.generateSecret();
// Derive a key from the shared secret and both salt keys
MessageDigest hash = MessageDigest.getInstance("SHA-512");
hash.update(Util.PRE_SALT_VALUE);
hash.update(sharedSecret);
hash.update(Util.POST_SALT_VALUE);
byte[] derivedKey = hash.digest();
... etc, derivedKey = secret returned in JS method, ourPk = public_key returned in JS method.
One thing i notice is that the public/private keys generated from nodejs vs java are different sizes? 65 bytes in node an 91 bytes in java. No idea why that would happen.
What stands out as wrong here?
Thanx
Edit:
So basically, I just need to know how to do this in Java
var EC = require('elliptic').ec;
var ec = new EC('p256');
// Generate keys
var key1 = ec.genKeyPair();
var key2 = ec.keyFromPublic(public_key, 'hex') //pub key from foreign device
var derived_secret = key1.derive(key2.getPublic());
Like already mentioned in the comments, to be able to use the shared secret between Java and Node you need to convert the keys accordingly.
For the key conversion code from this two fine stackoverflow answers can be used:
https://stackoverflow.com/a/57209308/2331445
https://stackoverflow.com/a/36033552
Test
To get a complete test case, one can write a Java program that generates a DER public key, converts it into an uncompressed EC key (65 bytes) and outputs it to the console. It then reads the other PK from the console, converts it to a public key, and prints out the shared secret.
The Node code uses the PK from the Java program, determines the shared secret and a public key. This public key can then be passed to the Java program via copy/paste, which is still waiting for the input.
The Java program finally determines the shared secret and prints it out.
If both shared secrets have the same value, we know it works.
Java
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import javax.crypto.KeyAgreement;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
public class Main {
public static byte[] ecKeyBytesFromDERKey(byte[] ourPk) {
ASN1Sequence sequence = DERSequence.getInstance(ourPk);
DERBitString subjectPublicKey = (DERBitString) sequence.getObjectAt(1);
return subjectPublicKey.getBytes();
}
private static PublicKey publicKeyFromEC(byte[] ecKeyByteArray) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory kf = KeyFactory.getInstance("EC", "BC");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
ECNamedCurveSpec params = new ECNamedCurveSpec("secp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint publicPoint = ECPointUtil.decodePoint(params.getCurve(), ecKeyByteArray);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, params);
return kf.generatePublic(pubKeySpec);
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(256);
KeyPair kp = kpg.generateKeyPair();
byte[] ourPk = kp.getPublic().getEncoded();
byte[] ecPublicKey = ecKeyBytesFromDERKey(ourPk);
System.out.println("our ec public key (65 bytes): " + Hex.encodeHexString(ecPublicKey));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("other public key (65 bytes): ");
String input = br.readLine();
br.close();
byte[] otherPk = Hex.decodeHex(input);
PublicKey otherPublicKey = publicKeyFromEC(otherPk);
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(kp.getPrivate());
ka.doPhase(otherPublicKey, true);
byte[] sharedSecret = ka.generateSecret();
System.out.println("Shared secret: " + Hex.encodeHexString(sharedSecret));
}
}
Node
One change is required in your node program:
In the line
public_key: public_key_client.toString('hex').slice(2), //anyone know why we drop the first byte here?
the .slice(2) needs to be removed:
public_key: public_key_client.toString('hex'),
because it removes the first byte (which is hex 04) needed to indicate that it is an uncompressed key.
So just using the public key from the Java program (which will be different for each run) the Node part could look like this:
var publickey = Buffer.from("<public key from java>", 'hex');
var derived = sign(publickey);
console.log(derived);
Test
In the upper area you see the Java program and in the lower area the output of the Node program. The shared secret is the same.

Bouncy Castle - how to get Public Key Info from JceOpenSSLPKCS8DecryptorProviderBuilder

I have the following code to extract Private Key
PEMParser parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(decoded)));
Object object = parser.readObject();
PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder()
.build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
if (object instanceof PEMEncryptedKeyPair) {
KeyPair pair = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(provider));
return loadPublic ? pair.getPublic() : pair.getPrivate();
} else if (object instanceof PEMKeyPair) {
return loadPublic ? converter.getPublicKey(((PEMKeyPair) (object)).getPublicKeyInfo())
: converter.getPrivateKey(((PEMKeyPair) (object)).getPrivateKeyInfo());
} else {
InputDecryptorProvider p2 = new JceOpenSSLPKCS8DecryptorProviderBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());
return converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(p2));
}
I would like to get the Public Key from converter when it's JceOpenSSLPKCS8DecryptorProviderBuilder. Is there any way?
Thanks,
The simplest way, although it feels rather ugly to me, is to convert the private key 'back' to one of OpenSSL's 'legacy' forms, which PEMParser is then able to turn into a PEMKeyPair with both halves, from which the public can be selected. Otherwise, the method must be tailored depending on the key algorithm aka type, but can be more efficient which I like better. Here are both options for your consideration:
public static void SO57043669PKCS8_Public_BC (String[] args) throws Exception {
Object p8e = new PEMParser (new FileReader (args[0])).readObject();
// for PKCS8-encrypted result is org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo
PrivateKeyInfo p8i = ((PKCS8EncryptedPrivateKeyInfo)p8e).decryptPrivateKeyInfo(
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(args[1].toCharArray()) );
// or get org.bouncycastle.asn1.pkcs.PrivateKeyInfo directly from PEMParser for PKCS8-clear
PublicKey pub = null;
if( args.length>=3 ){ // the simple way:
PrivateKey prv = new JcaPEMKeyConverter().getPrivateKey(p8i);
PemObject old = new JcaMiscPEMGenerator (prv,null).generate();
StringWriter w1 = new StringWriter();
PemWriter w2 = new PemWriter(w1);
w2.writeObject(old); w2.close();
Object pair = new PEMParser(new StringReader(w1.toString())).readObject();
pub = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)pair).getPublic();
}else{
ASN1ObjectIdentifier id = p8i.getPrivateKeyAlgorithm().getAlgorithm();
PKCS8EncodedKeySpec p8s = new PKCS8EncodedKeySpec (p8i.getEncoded());
if( id.equals(PKCSObjectIdentifiers.rsaEncryption) ){
// the standard PKCS1 private key format for RSA redundantly includes e
KeyFactory rfact = KeyFactory.getInstance("RSA");
RSAPrivateCrtKey rprv = (RSAPrivateCrtKey) rfact.generatePrivate(p8s);
// or JcaPEMKeyConverter.getPrivateKey does the same thing
pub = /*(RSAPublicKey)*/ rfact.generatePublic(
new RSAPublicKeySpec (rprv.getModulus(), rprv.getPublicExponent()));
}else if( id.equals(X9ObjectIdentifiers.id_dsa) ){
// the apparently ad-hoc format OpenSSL uses for DSA does not include y but it can be computed
KeyFactory dfact = KeyFactory.getInstance("DSA");
DSAPrivateKey dprv = (DSAPrivateKey) dfact.generatePrivate(p8s);
// or JcaPEMKeyConverter.getPrivateKey does the same thing
BigInteger p = dprv.getParams().getP(), q = dprv.getParams().getQ(), g = dprv.getParams().getG();
pub = /*(DSAPublicKey)*/ dfact.generatePublic (
new DSAPublicKeySpec(g.modPow(dprv.getX(),p), p, q, g) );
// warning: naive computation probably vulnerable to sidechannel attack if any
}else if( id.equals(X9ObjectIdentifiers.id_ecPublicKey) ){
// the SECG SEC1 format for EC private key _in PKCS8 by OpenSSL_
// includes []] BITSTR(Q) (but not [0] params which is already in the PKCS8 algid)
org.bouncycastle.asn1.sec.ECPrivateKey eprv = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(p8i.parsePrivateKey());
byte[] eenc = new SubjectPublicKeyInfo (p8i.getPrivateKeyAlgorithm(), eprv.getPublicKey().getOctets()).getEncoded();
KeyFactory efact = KeyFactory.getInstance("EC");
pub = /*(ECPublicKey)*/ KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(eenc));
//}else if maybe others ...
}else throw new Exception ("unknown private key OID " + id);
}
System.out.println (pub.getAlgorithm() + " " + pub.getClass().getName());
}
In addition to the other answer, here's a way to convert Ed25519 private keys to public keys.
Edit: As #tytk notes in the comments, given a BCEdDSAPrivateKey, you can just call getPublicKey(). I am not entirely sure if it's possible to obtain a private EdDSAKey that wouldn't be BCEdDSAPrivateKey, using BC or otherwise, but just in case I'm leaving an alternative codepath that works.
private val bouncyCastleProvider = BouncyCastleProvider()
private val pkcs8pemKeyConverter = JcaPEMKeyConverter().setProvider(bouncyCastleProvider)
fun makeKeyPair(keyReader: Reader, passphrase: CharArray): KeyPair {
var obj = PEMParser(keyReader).readObject()
...
if (obj is PrivateKeyInfo) {
val privateKey = pkcs8pemKeyConverter.getPrivateKey(obj)
when (privateKey) {
is BCEdDSAPrivateKey -> return KeyPair(privateKey.publicKey, privateKey)
is EdDSAKey -> return KeyPair(genEd25519publicKey(privateKey, obj), privateKey)
}
...
}
...
}
private fun genEd25519publicKey(privateKey: EdDSAKey, privateKeyInfo: PrivateKeyInfo): PublicKey {
val privateKeyRaw = ASN1OctetString.getInstance(privateKeyInfo.parsePrivateKey()).octets
val privateKeyParameters = Ed25519PrivateKeyParameters(privateKeyRaw)
val publicKeyParameters = privateKeyParameters.generatePublicKey()
val spi = SubjectPublicKeyInfo(privateKeyInfo.privateKeyAlgorithm, publicKeyParameters.encoded)
val factory = KeyFactory.getInstance(privateKey.algorithm, bouncyCastleProvider)
return factory.generatePublic(X509EncodedKeySpec(spi.encoded))
}

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

Convert a PGP Public Key

Does anybody know if there is a way to convert a public key with the pgp public key format to the X.509 key format? Maybe using Bouncy Castle or something familiar?
Because right now I am able to decode a X.509 public key using X509EncodedKeySpecs and PublicKey, but this doesn't work with the PGP key format.
byte[] decodeValue = Base64.decode(schluesselstring.getBytes(), Base64.DEFAULT);
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(decodeValue);
try {
KeyFactory keyFact = KeyFactory.getInstance("RSA");
try {
PublicKey publicKey = keyFact.generatePublic(pubKeySpec);
schluessel = "schluessel";
Log.d("TEST", "publicKey = " + publicKey.toString());
Log.d("TEST", "Algorithm = " + publicKey.getAlgorithm());
Log.d("TEST", "Format = " + publicKey.getFormat());
}
catch...
}
When I try to use this code on a PGP key I get an error message because it's not ANSC.1 . I also tried to use different KeySpecs but none worked.
The standard that "X.509" (SPKI) and "PKCS8" keys, and other things like certificates, use is Abstract Syntax Notation One ASN.1. Standard Java crypto doesn't handle PGP but yes BouncyCastle (bcpg) can do this just fine (updated 2021-02: JcaPGPKeyConverter does the whole job, and for all algorithms):
static void SO40831894PGPPubkeyCvtBC (String[] args) throws Exception {
// adapted from org.bouncycastle.openpgp.examples.PubringDump
try (InputStream in = new FileInputStream (args[0])){
PGPPublicKeyRingCollection pubRings = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> rIt = pubRings.getKeyRings();
while (rIt.hasNext()){
PGPPublicKeyRing pgpPub = (PGPPublicKeyRing)rIt.next();
Iterator<PGPPublicKey> it = pgpPub.getPublicKeys();
while (it.hasNext()){
PGPPublicKey pgpKey = (PGPPublicKey)it.next();
System.out.println(pgpKey.getClass().getName()
+ " KeyID: " + Long.toHexString(pgpKey.getKeyID())
+ " type: " + pgpKey.getAlgorithm()
+ " fingerprint: " + new String(Hex.encode(pgpKey.getFingerprint())));
/* don't need to do this >>>
BCPGKey bcKey = pgpKey.getPublicKeyPacket().getKey();
//System.out.println (bcKey.getClass().getName());
if( bcKey instanceof RSAPublicBCPGKey ){
RSAPublicBCPGKey bcRSA = (RSAPublicBCPGKey)bcKey;
RSAPublicKeySpec specRSA = new RSAPublicKeySpec( bcRSA.getModulus(), bcRSA.getPublicExponent());
PublicKey jceKey = KeyFactory.getInstance("RSA").generatePublic(specRSA);
<<< instead just: */
{
PublicKey jceKey = new JcaPGPKeyConverter().getPublicKey(pgpKey);
// if you want to use the key in JCE, jceKey is now ready
// if you want to write "X.509" (SPKI) DER format to a file:
Files.write(new File(args[1]).toPath(), jceKey.getEncoded());
// if you want to write in PEM, bouncycastle can do that too
return;
}
}
}
}
}

How to obtain Session Handle in SunPKCS11

I am using SoftHSM and am able to generate and store keys in a token. In order to use SunPKCS Interfaces many methods require the session handle and i am at a loss on how to retrieve them.
Currently i am using SoftHSM and PKCS11 as follows. THe code within the comments are what i tried to work with the SUNPKCS11 interface signatures.
Any example code on how to wrap and unwrap keys will also be much appreciated. I am attempting to backup keys using PKCS11 from one token to another and if my understanding is right, the approach must be via wrapping....
Sitaraman
public static void main(String[] args) {
// TODO code application logic hereString configName = "softhsm.cfg";
try {
// Set up the Sun PKCS 11 provider
String configName = "/etc/softhsm.cfg";
Provider p = new SunPKCS11(configName);
String configName1 = "/etc/softhsm1.cfg";
Provider p1 = new SunPKCS11(configName1);
if (-1 == Security.addProvider(p)) {
throw new RuntimeException("could not add security provider");
}
PKCS11 p11 = PKCS11.getInstance("/usr/local/lib/softhsm/libsofthsm.so", "C_GetFunctionList", null, false);
/* p11.C_GetSessionInfo(0);
CK_INFO cki = p11.C_GetInfo();
long[] slots = p11.C_GetSlotList(true);
String label = new String(p11.C_GetTokenInfo(slots[0]).label);
Object obj = new Object();
long sessionhandle = p11.C_OpenSession(slots[0], 1, null, null);
CK_MECHANISM ckm = new CK_MECHANISM();
ckm.mechanism = PKCS11Constants.CKM_RSA_PKCS;
CK_ATTRIBUTE[] cka = new CK_ATTRIBUTE[1];
CK_ATTRIBUTE[] cka1 = new CK_ATTRIBUTE[1];
long[] keypair =p11.C_GenerateKeyPair(slots[1], ckm, cka, cka1);
*/
//System.out.println("No. of slots" + slots.length + "label" + label);
// Load the key store
char[] pin = "vit12345".toCharArray();
char[] pin1 = "User12345".toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS11", p);
KeyStore ks1 = KeyStore.getInstance("PKCS11", p1);
ks.load(null, pin);
ks1.load(null, pin1);
Entry e;
KeyStore.PrivateKeyEntry e1;
// Generate the key
SecureRandom sr = new SecureRandom();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", p);
keyGen.initialize(1024, sr);
KeyPair keyPair = keyGen.generateKeyPair();
PrivateKey pk = keyPair.getPrivate();
// Java API requires a certificate chain
X509Certificate[] chain = generateV3Certificate(keyPair);
ks.setKeyEntry("ALIAS-GOES-HERE", pk, "1234".toCharArray(), chain);
//ks1.setKeyEntry("ALIAS-GOES-HERE1", pk, "1234".toCharArray(), chain);
ks.store(null);
//ks1.store(null);
Key k = ks.getKey("ALIAS-GOES-HERE", "1234".toCharArray());
System.out.println("key string is " + k.toString());
PrivateKey pk1 = (PrivateKey) k;
System.out.println("OK");
} catch (Exception ex) {
ex.printStackTrace();
}
}
You can use approach from SunPKCS11 wrapper like you do in commented block in your sample code.
You can obtain a session handle by the following sample code:
CK_C_INITIALIZE_ARGS initArgs = new CK_C_INITIALIZE_ARGS();
PKCS11 p11 = PKCS11.getInstance("D:\\cryptoki.dll", "C_GetFunctionList", initArgs, false);
long hSession = p11.C_OpenSession(0, CKF_SERIAL_SESSION| CKF_RW_SESSION, null, null);
char [] pin = {'1', '2', '3', '4', '5', '6', '7', '8'};
p11.C_Login(hSession, CKU_USER, pin);
// do work
p11.C_Logout(hSession);
p11.C_CloseSession(hSession);
For example to generate AES key and obtain a handle to generated key use the following (as an example):
CK_ATTRIBUTE[] aesKeyObject = new CK_ATTRIBUTE[13];
try
{
aesKeyObject[0] = new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY);
aesKeyObject[1] = new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_AES);
aesKeyObject[2] = new CK_ATTRIBUTE(CKA_VALUE_LEN, 32);
aesKeyObject[3] = new CK_ATTRIBUTE(CKA_TOKEN, true);
aesKeyObject[4] = new CK_ATTRIBUTE(CKA_LABEL, label);
aesKeyObject[5] = new CK_ATTRIBUTE(CKA_PRIVATE, true);
aesKeyObject[6] = new CK_ATTRIBUTE(CKA_EXTRACTABLE, false);
aesKeyObject[7] = new CK_ATTRIBUTE(CKA_WRAP, true);
aesKeyObject[8] = new CK_ATTRIBUTE(CKA_UNWRAP, true);
aesKeyObject[9] = new CK_ATTRIBUTE(CKA_ENCRYPT, true);
aesKeyObject[10] = new CK_ATTRIBUTE(CKA_DECRYPT, true);
aesKeyObject[11] = new CK_ATTRIBUTE(CKA_TRUSTED, true);
aesKeyObject[12] = new CK_ATTRIBUTE(CKA_ID, 1550);
CK_MECHANISM mech = new CK_MECHANISM(CKM_AES_KEY_GEN);
long newAESKeyHandle = p11.C_GenerateKey(hSession, mech, aesKeyObject);
}catch(Exception e)
{
}
Then you can work with this handle to use newly generated key.
Also the PKCS11 wrapper has methods for wrapping and unwrapping keys which you can use to backup:
public native byte[] C_WrapKey(long hSession,
CK_MECHANISM pMechanism,
long hWrappingKey,
long hKey) throws PKCS11Exception
public native long C_UnwrapKey(long hSession,
CK_MECHANISM pMechanism,
long hUnwrappingKey,
byte[] pWrappedKey,
CK_ATTRIBUTE[] pTemplate) throws PKCS11Exception

Categories

Resources