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()));
}
}
Related
I am loading a public key in java using bouncy castle library but always getting error Invalid point encoding 0x45.
The public key is generated at client side using C# CNG APIs.
Java method 1:
public PublicKey loadPublicKey(String encodedPublicKey)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keybytes = java.util.Base64.getDecoder().decode(encodedPublicKey);
Security.addProvider(new BouncyCastleProvider());
ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
ECPublicKeySpec keySpec = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
return new BCECPublicKey("ECDH", keySpec, BouncyCastleProvider.CONFIGURATION);
}
Method 2
public PublicKey loadPublicKey(String pKey) throws Exception {
byte[] keybytes = java.util.Base64.getDecoder().decode(pKey);
Security.addProvider(new BouncyCastleProvider());
ECParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
ECPublicKeySpec pubKey = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
return kf.generatePublic(pubKey);
}
Exception
java.lang.IllegalArgumentException: Invalid point encoding 0x45
at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:443)
Below method to create public key
public static (byte[] publicKey, byte[] privateKey) CreateKeyPair()
{
using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(
// need to do this to be able to export private key
CngKey.Create(
CngAlgorithm.ECDiffieHellmanP256,
null,
new CngKeyCreationParameters
{ ExportPolicy = CngExportPolicies.AllowPlaintextExport })))
{
cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
cng.HashAlgorithm = CngAlgorithm.Sha256;
// export both private and public keys and return
var pr = cng.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
var pub = cng.PublicKey.ToByteArray();
return (pub, pr);
}
}
Public Key generated RUNLMSAAAAHddHI6TOEDG/Ka7naBbLQH0u/DSFfbKJI2w0WSoxrmFkwKm1tktz4wD0rqnwkZp8FwdHJ+8OVrTcpDMmxrwvS6
The key which I am receiving at java is of 72 bytes. But I think bouncy castle java supports 64 bytes of key.
I was also looking into this but did not get any help
The C# code exports the public key as a Base64 encoded EccPublicBlob whose format is described in the link given in the question:
The first 4 bytes 0x45434B31 denote in little endian order a public ECDH key for curve P-256, the following 4 bytes are in little endian order the key length in bytes (0x20000000 = 32), the rest are the x and y coordinates of the EC point i.e. the public key, 32 bytes each.
It is striking that in the key you posted, the second 4 bytes are 0x20000001, but the x and y coordinates are 32 bytes each. Possibly there is a copy/paste error here. Anyway, with the posted C# code, I cannot reproduce a key that has a value other than 0x20000000 in the second 4 bytes.
Java/BC does not directly support importing an EccPublicBlob (which is MS proprietary), but it does support importing an uncompressed public key. This results when the x and y coordinates are concatenated and 0x04 is used as prefix. The import with Java/BC is then possible as follows:
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
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;
...
public static PublicKey getPubKeyFromCurve(byte[] uncompRawPubKey, String curveName) throws Exception {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
ECNamedCurveSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), uncompRawPubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pubKey;
}
Test (assuming EccPublicBlob is Base64 encoded like the posted one):
import java.util.Base64;
...
String publicKeyBlob = "RUNLMSAAAAAFzw4IGY4N8PKVt0MGF38SAKU5ixJhptVUdrWzuPhFDOcj/2k4SlGRN1RpRMbar9Iu7Uvcx7Vtm8Wa0HSzWJdE";
byte[] rawPublic = new byte[65];
rawPublic[0] = 0x04;
System.arraycopy(Base64.getDecoder().decode(publicKeyBlob), 8, rawPublic, 1, 64);
PublicKey pub = getPubKeyFromCurve(rawPublic, "P-256");
System.out.println(Base64.getEncoder().encodeToString(pub.getEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBc8OCBmODfDylbdDBhd/EgClOYsSYabVVHa1s7j4RQznI/9pOEpRkTdUaUTG2q/SLu1L3Me1bZvFmtB0s1iXRA==
The test imports the EccPublicBlob and exports it as a Base64 encoded DER key in X.509/SPKI format. This can be read with an ASN.1 parser, e.g. https://lapo.it/asn1js/, and thus be verified.
Note that C# also supports the export of other formats. However, this depends on the version. E.g. as of .NET Core 3.0 there is the method ExportSubjectPublicKeyInfo() that exports the public key in X.509/SPKI format, DER encoded. This format and encoding can be imported directly into Java using X509EncodedKeySpec (even without BouncyCastle).
In other versions of C#, BouncyCastle for C# can be used for the export, which also supports the X.509/SPKI format.
Since you didn't post your .NET version, it's unclear what specific alternatives exist for you.
Keep in mind that an ECDH key for P-256 can also be created more simply with:
ECDiffieHellmanCng cng = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256)
or cross-platform with
ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)
I need to encrypt the data using public key located in Safenet HSM Luna SA device and also need to decrypt the data using private key which also located in HSM device in JAVA.
I'm completely new to the HSM device. I have encrypted/ decrypted data using keys which are located in epass e-token device as follows:
private void loadKeys() {
logger.info("In loadKeys method at "+new Date());
try {
char password[] = hsmServiceAppProps.getDigiSigPfxPassword().toCharArray();
Provider userProvider = new sun.security.pkcs11.SunPKCS11(this.getClass().getClassLoader().getResourceAsStream("/pkcs11.cfg"));
Security.addProvider(userProvider);
KeyStore ks = KeyStore.getInstance("PKCS11");
ks.load(null, password);
String alias = null;
/*X509Certificate userCert = null;
PrivateKey userCertPrivKey = null;
PublicKey userCertPubKey = null;
Enumeration<String> e = ks.aliases();
while (e.hasMoreElements()) {
alias = (String) e.nextElement();
logger.info("Alias of the e-Token : " + alias);
userCert = (X509Certificate) ks.getCertificate(alias);
userCertPubKey = (PublicKey) ks.getCertificate(alias).getPublicKey();
userCertPrivKey = (PrivateKey) ks.getKey(alias, password);
}*/
alias = "*************************************";
//X509Certificate certificate = (X509Certificate) ks.getCertificate(alias);
publicKey = (PublicKey) ks.getCertificate(alias).getPublicKey();
privateKey = (PrivateKey) ks.getKey(alias, password);
} catch (Exception e) {
logger.error("Error while getting public and private keys ->> ",e);
}
}
private String performEncryption(String content,PublicKey publicKey) throws Exception {
logger.debug("Encrypting using public key : "+content);
Cipher publicEncryptCipher = Cipher.getInstance("RSA");
publicEncryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBinaryData = publicEncryptCipher.doFinal(content.getBytes());
Base64 encoder = new Base64();
String encodedEncryptedContent = new String(encoder.encode(encryptedBinaryData),"UTF-8");
logger.debug("Encrypted Content ->> "+encodedEncryptedContent);
return encodedEncryptedContent;
}
private String performDecryption(String encodedEncryptedContent, PrivateKey privateKey) throws Exception {
logger.debug("Decrypting with private key ->> "+encodedEncryptedContent);
Base64 decoder = new Base64();
byte[] encryptedString = decoder.decode(encodedEncryptedContent.getBytes());
Cipher privateDecryptCipher = Cipher.getInstance("RSA");
privateDecryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBinaryData = privateDecryptCipher.doFinal(encryptedString);
String decryptedContent = new String(decryptedBinaryData,"UTF-8");
logger.debug("Decrypted Content ->> "+decryptedContent);
return decryptedContent;
}
In the same way I need to do encryption/decryption using HSM device. I have installed Luna client software and imported keys to the HSM device.
Could any one please help me
Once you have successfully installed Luna client. you can use use either Luna JSP or JCProv libraries to perform cryptographic operation on HSM by using keys residing on HSM.
To check if Luna client is installed and registered with the remote HSM correctly, you can run the following command: "VTL.exe verify" from your luna client directory.
Output of successfully VTL verify
Here is an example of Encryption and Decryption using public and private keys of RSA.
void asymetricEncDec(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE hPublicKey,
CK_OBJECT_HANDLE hPrivateKey)
{
//session - handle to an open session
//hPublicKey - handle to public asymetric key to use for encryption
//hPrivateKey - handle to private asymetric key to use for decryption
String startString = "this is 16 bytes";
byte[] plainText = startString.getBytes();
byte[] cipherText = null;
LongRef lRefEnc = new LongRef();
LongRef lRefDec = new LongRef();
//mechanism to use
CK_MECHANISM mechanism = new CK_MECHANISM(CKM.RSA_PKCS);
/* get ready to encrypt */
CryptokiEx.C_EncryptInit(session, mechanism, hPublicKey);
/* get the size of the cipher text */
CryptokiEx.C_Encrypt(session, plainText, plainText.length, null,
lRefEnc);
/* allocate space */
cipherText = new byte[(int)lRefEnc.value];
/* encrypt */
CryptokiEx.C_Encrypt(session, plainText, plainText.length, cipherText,
lRefEnc);
/* get ready to decrypt */
CryptokiEx.C_DecryptInit(session, mechanism, hPrivateKey);
/* get the size of the plain text */
CryptokiEx.C_Decrypt(session, cipherText, lRefEnc.value, null, lRefDec);
/* allocate space */
plainText = new byte[(int)lRefDec.value];
/* decrypt */
CryptokiEx.C_Decrypt(session, cipherText, lRefEnc.value, plainText,
lRefDec);
/* make sure that we end up with what we started with */
String endString = new String(plainText, 0, (int)lRefDec.value);
if (startString.compareTo(endString) == 0)
{
println("Decrypted string matches original string - hurray");
}
else
{
println("Decrypted string does not match original string - boo");
}
}
This example is using JCProv library provided by luna client. Note: JCProv lower level library close to the 'C' implementation of PKCS#11.
You can also use the IAIK PKCS#11 wrapper to make all kinds of operations on the HSM. The wrapper is open source. It is well documented and working code examples are available.
Ref: https://jce.iaik.tugraz.at/products/core-crypto-toolkits/pkcs11-wrapper/
My password is encrypted with RSA in an android app. On the server side, I need to decrypt it., What I have is a .pem file, and the php code for decrypting:
function privatekey_decodeing($crypttext, $fileName, $fromjs = FALSE)
{
$key_content = file_get_contents( $fileName );
$prikeyid = openssl_get_privatekey( $key_content, "1234" );
$crypttext = str_replace(' ', '+', $crypttext);
$crypttext = base64_decode( $crypttext );
$padding = $fromjs ? OPENSSL_NO_PADDING : OPENSSL_PKCS1_PADDING;
if( openssl_private_decrypt( $crypttext, $sourcestr, $prikeyid, $padding ) )
{
return $fromjs ? rtrim( strrev( $sourcestr ), "/0" ) : "" . $sourcestr;
}
return;
}
the fileName is frivatekey file(.pem file)
Now I need to use java to decrypt it. I have tried some methods, all have failed. Here is what I have tried:
using the .pem file to generate a .der key file
reading the .der file to get the privateKey
using the byte[] read from .der file to generate keyfile
public static PrivateKey generatePrivateKey(byte[] key)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec keySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
decrypt my password
public static byte[] decrypt(PrivateKey privateKey, byte[] data)
throws Exception {
Cipher ci = Cipher.getInstance(ALGORITHOM, DEFAULT_PROVIDER);
ci.init(Cipher.DECRYPT_MODE, privateKey);
return ci.doFinal(data);
}
But it does not work, and I do not know where is going wrong.
In the php code I see $prikeyid = openssl_get_privatekey( $key_content, "1234" );
But I don't know what does the "1234" means. Does it mean using "1234" to encrypt the keyfile? Is this the reason the decrypt failed?
$padding = $fromjs ? OPENSSL_NO_PADDING : OPENSSL_PKCS1_PADDING;
These are both bad options:
Unpadded RSA is insecure
PKCS1 padding (which is also the default padding mode) is vulnerable to chosen-ciphertext attacks; thus it's also insecure
Please don't implement RSA yourself. You're going to make your application incredibly insecure.
Recommended reading:
Cryptographic right answers
The original cryptographic right answers
Recommended PHP cryptography libraries
Crypto Fails
How to safely implement cryptography in any application (covers Java and PHP)
For obvious security reasons i need to encrypt and decrypt User's PIN codes with RSA private and public key, I have found working solution, which looks like:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(512);
KeyPair rsaKeyPair = kpg.genKeyPair();
byte[] txt = "This is a secret message.".getBytes();
System.out.println("Original clear message: " + new String(txt));
// encrypt
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, rsaKeyPair.getPublic());
txt = cipher.doFinal(txt);
} catch (Throwable e) {
e.printStackTrace();
return;
}
System.out.println("Encrypted message: " + new String(txt));
// decrypt
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivate());
txt = cipher.doFinal(txt);
} catch (Throwable e) {
e.printStackTrace();
return;
}
System.out.println("Decrypted message: " + new String(txt));
}
everything works fine, but in this example key-pair is not static and generate new values everytime, but I need to use same keys, which are represented as String variables:
public static final String PrivateKey = "MIICXAIBAAKBgQDx0PSJr6zEP9914k1eM+sS8/eW+FenhBQI/jf6ARe8kZHFig9Y"
+ bla bla bla
+ "wdK3jBzObK319yNFr/2LukNZ9Bgv7fS78roBvxbe2gI=";
public static final String PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDx0PSJr6zEP9914k1eM+sS8/eW"
+ bla bla bla
+ "jYo5w2Nhxe2cukCQMQIDAQAB";
Is there any way to cast these variables to PublicKey and PrivateKey Class?
If I understand what you want, to obtain PublicKey and PrivateKey instances from your static variables you can do, for example, this way:
private static final String privateKeyString = "...";
private static PrivateKey privateKey;
private static final String publicKeyString = "...";
private static PublicKey publicKey;
static {
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
byte[] encodedPv = Base64.decodeBase64(privateKeyString);
PKCS8EncodedKeySpec keySpecPv = new PKCS8EncodedKeySpec(encodedPv);
privateKey = kf.generatePrivate(keySpecPv);
byte[] encodedPb = Base64.decodeBase64(publicKeyString);
X509EncodedKeySpec keySpecPb = new X509EncodedKeySpec(encodedPb);
publicKey = kf.generatePublic(keySpecPb);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
}
}
After (mostly) concurring with #JB that passwords (usually) shouldn't be encrypted, they should be "hashed" -- using a method specifically designed to "stretch" and salt such as scrypt, not a fast hash like SHA-1 -- and also noting that RSA-512 as used in your original code is broken and even RSA-1024 as apparently used in your modification is considered weak:
Your PrivateKey value appears (from its beginning) to be base64 of a plain PKCS#1 DER encoding, which basically is used only by OpenSSL and things that use OpenSSL (format) like older versions of OpenSSH. The Java standard "Sun" providers do not handle this, although I think BouncyCastle might if you want to explore that. For Sun you need to convert it to binary DER from base64; wrap it into PKCS#8 format (which in binary is just adding a header and maybe EOC trailers because the algorithm-specific part of PKCS#8 for RSA is PKCS#1); and put it in a PKCS8EncodedKeySpec and run it through generatePrivate of a KeyFactory of type RSA. See
http://docs.oracle.com/javase/8/docs/api/java/util/Base64.html (Java8 only)
http://docs.oracle.com/javase/8/docs/api/java/security/KeyFactory.html
https://www.rfc-editor.org/rfc/rfc5208#section-5 for the structure of unencrypted PKCS#8 (Java doesn't do the encrypted format in section 6) and look at the publickey form for the OID for RSA.
Alternatively add the header/trailer to make it proper PEM, use OpenSSL to convert it to PKCS#8 (unencrypted), and optionally binary at the same time, and run that through generatePrivate.
Your PublicKey similarly appears to be base64 of an X.509 SubjectPublicKeyInfo encoding, which OpenSSL (but not OpenSSH) uses and standard Java does support under the name "X.509". So just convert from base64 to binary, put in an X509EncodedKeySpec, and run through generatePublic of the RSA KeyFactory. Note if your encryption will be done remote or distributed, which is the usual scenario for publickey-encryption, the encryptor must be certain to use the correct publickey; if an attacker can substitute a wrong publickey they can decrypt and steal at least some of your supposedly secure data. That's why real PK systems don't use a plain publickey, they use a certificate, either X.509 like SSL/TLS and S/MIME, or web-of-trust like PGP.
I got this running doing the following:
public Key loadPrivateKey(String stored) throws GeneralSecurityException {
PKCS8EncodedKeySpec keySpec =
new PKCS8EncodedKeySpec(
Base64.getDecoder().decode(stored.getBytes(StandardCharsets.UTF_8)));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
public Key loadPublicKey(String stored) throws GeneralSecurityException {
byte[] data = Base64.getDecoder().decode(stored.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePublic(spec);
}
You must also to remove -----BEGIN PRIVATE KEY-----, -----END PRIVATE KEY----- and all \n from the strings that contain you keys.
Cannot match up the size of key generated using public/private keys for licensing application. Ive written a self contained example that creates public/private key, create a license by signing user emailaddress with public key, and then check using public key, license and email address that the license indeed was encoded using private key (Obviously this wouldn't all be in one class usually).
This all works but the hex version of the license key is 96 characters (i.e representing 48 bytes/384 bits) which is a little longer than I wanted (In contrast the length of public/private keys is not a problem and the longer the better). What could I use to generate a 32 (64 hex chars) byte or maybe 16 byte (32 hex chars), and would the security of this be reasonable ?
Picking another algorithm is somewhat hard as I do not understand the the interaction between the algorithm picked for generating the keys
KeyPairGenerator.getInstance("DSA");
and the algorithm for signing
Signature.getInstance("SHA/DSA");
and I cant find a list for either.
One other point when I generate a public/private key pairs I specify key size of
keyGen.initialize(1024, new SecureRandom());
yet neither the public key (443 bytes) or the private key (335 bytes) or the sum of both (778 bytes) match this number.
import org.apache.commons.codec.binary.Hex;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
*
*/
public class CreateLicense
{
private String PUBLIC_KEY;
private String PRIVATE_KEY;
public static void main(final String[] args)
{
try
{
String email = args[0];
System.out.println("Creating license for:"+email);
CreateLicense cl = new CreateLicense();
cl.generatePublicPrivateKeyPair();
String license = cl.createLicense(email);
cl.checkLicense(email, license);
}
catch(Throwable t)
{
t.printStackTrace();
}
}
//Would only be done once on server
private void generatePublicPrivateKeyPair() throws Exception
{
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
keyGen.initialize(1024, new SecureRandom());
final KeyPair pair = keyGen.generateKeyPair();
PrivateKey privateKey = pair.getPrivate();
PRIVATE_KEY=Hex.encodeHexString(privateKey.getEncoded());
PublicKey publicKey = pair.getPublic();
PUBLIC_KEY=Hex.encodeHexString(publicKey.getEncoded());
System.out.println("PrivateKeyHexLength:"+privateKey.getEncoded().length);
System.out.println("PublicKeyHexLength:"+publicKey.getEncoded().length);
}
private PrivateKey reinflatePrivateKey(String keyAsHexString) throws Exception
{
byte[] keyBytes = Hex.decodeHex(keyAsHexString.toCharArray());
final PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
final PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec);
return privateKey;
}
private PublicKey reinflatePublicKey(String keyAsHexString) throws Exception
{
byte[] keyBytes = Hex.decodeHex(keyAsHexString.toCharArray());
final X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyBytes);
final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
final PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
return publicKey;
}
//License Create on server based on email address
private String createLicense(String emailAddress) throws Exception
{
String message=emailAddress;
PrivateKey privateKey = reinflatePrivateKey(PRIVATE_KEY);
final Signature dsa = Signature.getInstance("SHA/DSA");
dsa.initSign(privateKey);
dsa.update(message.getBytes());
final byte[] m1 = dsa.sign();
String license = Hex.encodeHexString(m1);
System.out.println("CreateLicense:"+license+":Size:"+license.length());
return license;
}
//Client checks that given known emailaddress and public key that a if a license was derived from
//that and corresponding privatekey it would match license.
private boolean checkLicense(String emailAddress, String license) throws Exception
{
String message=emailAddress;
PublicKey publicKey = reinflatePublicKey(PUBLIC_KEY);
final Signature dsa = Signature.getInstance("SHA/DSA");
dsa.initVerify(publicKey);
dsa.update(message.getBytes());
boolean result = dsa.verify(Hex.decodeHex(license.toCharArray()));
System.out.println("Result"+result);
return result;
}
}
gives output like
Creating license for:testuser#nowhere.com
PrivateKeyHexLength:335
PublicKeyHexLength:443
CreateLicense:302c021425f7ad7289b073f82a1d808838f43e0134c5591402140d2a7a4e3967706d4659dc73ace6455040a5fc6b:Size:92
Resulttrue
#Paul - I think your solution here would be to use ECDSA.
Change your line of code
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA");
to
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA");
The keys are much shorter than DSA - and I'm sure hex version signature would be shorter. I suggest you use a prime ECC curve of say 256 or 128 bits.
Please let us know if this solves the problem.