Problem: need to save RSA private key in encrypted place. Try to use KeyStore
for this purpose.
Code snippet:
package com.example.encryptiontest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Date;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import android.content.Context;
import android.util.Base64;
public class Encryption {
private static final String ALIAS = "myAlias";
private static final String FILE_NAME="key_store";
private Context mContext;
public Encryption(Context context){
mContext = context;
}
public void save() throws NoSuchAlgorithmException, InvalidKeySpecException, CertificateEncodingException, InvalidKeyException, NoSuchProviderException, SignatureException, KeyStoreException, CertificateException, FileNotFoundException, UnsupportedEncodingException, IOException{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
KeyFactory fact = KeyFactory.getInstance("RSA");
RSAPublicKeySpec pub = fact.getKeySpec(kp.getPublic(),
RSAPublicKeySpec.class);
RSAPrivateKeySpec priv = fact.getKeySpec(kp.getPrivate(),
RSAPrivateKeySpec.class);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(pub.getModulus(),
pub.getPublicExponent());
PublicKey pubKey = fact.generatePublic(keySpec);
PrivateKey privateKey = fact.generatePrivate(new RSAPrivateKeySpec(
priv.getModulus(), priv.getPrivateExponent()));
saveToKeyStore(pubKey,ALIAS,mContext.getFilesDir() + FILE_NAME,privateKey);
}
public void load() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableEntryException{
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = generatePassword(ALIAS);
FileInputStream in = new FileInputStream(mContext.getFilesDir() + FILE_NAME);
keyStore.load(in, password);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) keyStore
.getEntry(ALIAS, null);
PrivateKey pubKey = (PrivateKey) keyEntry.getPrivateKey();
}
private void saveToKeyStore(PublicKey publicKey, String alias,
String fileName, PrivateKey privateKey)
throws CertificateEncodingException, NoSuchProviderException,
NoSuchAlgorithmException, SignatureException, InvalidKeyException,
KeyStoreException, IOException, CertificateException,
FileNotFoundException, UnsupportedEncodingException {
X509Certificate cert = getCertificate(publicKey, privateKey);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = generatePassword(alias);
keyStore.load(null);
keyStore.setKeyEntry(alias, privateKey, null,
new Certificate[] { cert });
FileOutputStream out = new FileOutputStream(fileName);
keyStore.store(out, password);
out.close();
}
private X509Certificate getCertificate(PublicKey publicKey,
PrivateKey privateKey) throws CertificateEncodingException,
NoSuchProviderException, NoSuchAlgorithmException,
SignatureException, InvalidKeyException {
Date startDate = new Date(); // time from which certificate is valid
Date expiryDate = new Date(2050, 3, 3); // time after which certificate
// is not valid
BigInteger serialNumber = BigInteger.ONE; // serial number for
// certificate
KeyPair keyPair = new KeyPair(publicKey, privateKey); // EC
// public/private
// key pair
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
X500Principal dnName = new X500Principal("CN=Test CA Certificate");
certGen.setSerialNumber(serialNumber);
certGen.setIssuerDN(dnName);
certGen.setNotBefore(startDate);
certGen.setNotAfter(expiryDate);
certGen.setSubjectDN(dnName); // note: same as issuer
certGen.setPublicKey(keyPair.getPublic());
certGen.setSignatureAlgorithm("SHA512withRSA");
X509Certificate cert = certGen.generate(keyPair.getPrivate(), "BC");
return cert;
}
private char[] generatePassword(String userName)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
String defaultUdid = android.provider.Settings.System.getString(
mContext.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID);
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bSalt = defaultUdid.getBytes("UTF-8");
byte[] bPw = userName.getBytes("UTF-8");
md.update(bPw);
byte[] r = md.digest(bSalt);
return Base64.encodeToString(r, Base64.URL_SAFE).toCharArray();
}
}
I use bouncycastle library for Certificate generation
Stack trace
03-16 14:00:52.106: W/System.err(27883): java.security.UnrecoverableKeyException: no match
03-16 14:00:52.108: W/System.err(27883): at com.android.org.bouncycastle.jce.provider.JDKKeyStore$StoreEntry.getObject(JDKKeyStore.java:310)
03-16 14:00:52.110: W/System.err(27883): at com.android.org.bouncycastle.jce.provider.JDKKeyStore.engineGetKey(JDKKeyStore.java:611)
03-16 14:00:52.111: W/System.err(27883): at java.security.KeyStoreSpi.engineGetEntry(KeyStoreSpi.java:372)
03-16 14:00:52.113: W/System.err(27883): at java.security.KeyStore.getEntry(KeyStore.java:644)
03-16 14:00:52.114: W/System.err(27883): at com.example.encryptiontest.Encryption.load(Encryption.java:71)
03-16 14:00:52.115: W/System.err(27883): at com.example.encryptiontest.MainActivity.onCreate(MainActivity.java:16)
03-16 14:00:52.117: W/System.err(27883): at android.app.Activity.performCreate(Activity.java:5122)
03-16 14:00:52.118: W/System.err(27883): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1081)
03-16 14:00:52.120: W/System.err(27883): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2270)
03-16 14:00:52.121: W/System.err(27883): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2358)
03-16 14:00:52.123: W/System.err(27883): at android.app.ActivityThread.access$600(ActivityThread.java:156)
03-16 14:00:52.124: W/System.err(27883): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1340)
03-16 14:00:52.125: W/System.err(27883): at android.os.Handler.dispatchMessage(Handler.java:99)
03-16 14:00:52.126: W/System.err(27883): at android.os.Looper.loop(Looper.java:153)
03-16 14:00:52.128: W/System.err(27883): at android.app.ActivityThread.main(ActivityThread.java:5299)
03-16 14:00:52.129: W/System.err(27883): at java.lang.reflect.Method.invokeNative(Native Method)
03-16 14:00:52.130: W/System.err(27883): at java.lang.reflect.Method.invoke(Method.java:511)
03-16 14:00:52.132: W/System.err(27883): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
03-16 14:00:52.133: W/System.err(27883): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
03-16 14:00:52.134: W/System.err(27883): at dalvik.system.NativeStart.main(Native Method)
When I execute your code I get an exception that the private key entry must be protected with a password:
java.security.UnrecoverableKeyException: requested entry requires a password
at java.security.KeyStoreSpi.engineGetEntry(KeyStoreSpi.java:459)
at java.security.KeyStore.getEntry(KeyStore.java:1290)
at KeyStorage.load(KeyStorage.java:50)
at PKI.main(PKI.java:518)
It looks like implementations of Java (both Oracle's and Android's) allow to create a private key entry without any protection (in keystore the keystore itself and each entry might have its own password protection), but during load they require a mandatory password. To fix that just specify password in KeyStore.getEntry()
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(
ALIAS, new KeyStore.PasswordProtection(password));
Another option is to use
PrivateKey key = (PrivateKey) keyStore.getKey(ALIAS, null);
Could be a bad password. Write out your password from generatePassword
to debug and use the keytool to check if it works to read the contents of the generated KeyStore.
Another Stack Overflow / Spongy Castle user said that it helped to specify a constructor when constructing his KeyStore. For him:
KeyStore ks = new KeyStore("BKS","SC");
instead of
KeyStore ks = new KeyStore("BKS");
I know you're not using the constructor to create a Keystore, but maybe this info will help.
The link that @Barret is referring is this Unlimited Strength Jce and Android
I faced the similar problem. In my case the password while storing and retrieving was different courtesy toString method. Try providing hard coded password for debug purpose.
There are several places to change:
1) I changed bouncycastle library to spongycastle
2) added
static {
Security.addProvider(new BouncyCastleProvider());
}
3) used KeyStore keyStore = KeyStore.getInstance("BKS", "SC");
4) used
KeyFactory fact1 = KeyFactory.getInstance("RSA","SC");
PublicKey pubKey = fact1.generatePublic(keySpec);
PrivateKey privateKey = fact1.generatePrivate(new RSAPrivateKeySpec(priv
.getModulus(), priv.getPrivateExponent()));
The reason is than different providers provide different binary representation of keys. So if you generate keys using one provider you can have issues if try to restore from binary stream using another provider
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With