Following this blog, I'm using this code to create and store a KeyPair in Android KeyStore:
Context ctx = getApplicationContext();
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(1, Calendar.YEAR);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx).
setAlias(RSA_KEYS_ALIAS).setSubject(
new X500Principal(String.format("CN=%s, OU=%s",
getApplicationName(), ctx.getPackageName()))).
setSerialNumber(BigInteger.ONE).
setStartDate(notBefore.getTime()).setEndDate(notAfter.getTime()).build();
KeyPairGenerator kpGenerator;
try {
kpGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpGenerator.initialize(spec);
kpGenerator.generateKeyPair();
} catch (Exception e) {
showException(e);
}
When I try to retrieve public key from the KeyStore using this code, a NullPointerException with the message chain == null is thrown.
public RSAPublicKey getRSAPublicKey() {
RSAPublicKey result = null;
try {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry(RSA_KEYS_ALIAS, null); // --< exception is thrown here
result = (RSAPublicKey) keyEntry.getCertificate().getPublicKey();
}
} catch (Exception e) {
showException(e);
}
return result;
}
The same goes with the the code to retrieve private key.
Update:
I compared my code with Google BasicAndroidKeyStore sample. The mechanism to generate, store and retrieve the key pair in that sample is virtually the same to what I've implemented. I'm puzzled as to why this code has stopped functioning after a few months of perfectly working.
Any suggestions or hints would be appreciated.
Apparently names in Android KeyStore must be unique amongst all Apps. I had another app which used the same name for its keys. After changing the the common library used by both apps to create and and use keys to include package name in its key names, the problem went away...
In my case, I had multiple calls to obtain the KeyStore at nearly the same time. I had to create a single instance and refer to that if it existed, otherwise KeyStore.getInstance("AndroidKeyStore") returned null and raised an Exception.
To protect against multiple async requests causing a crash, use a stored instance from just one KeyStore.getInstance().
In my case, I was trying to get public key before generating it. (getRSAPublicKey() called before generateKeyPair())
Related
Which method stores Private and Public keys in AndroidKeyStore?
I have implemented below code to initialise keystore and generate private and public keys.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyPair keyPair;
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 50);
KeyPairGenerator generator= KeyPairGenerator.getInstance("RSA","AndroidKeyStore");
KeyPairGeneratorSpec keyPairGeneratorSpec = new KeyPairGeneratorSpec.Builder(context).
setAlias("alias").
setSubject(new X500Principal("O=Authority")).
setSerialNumber(BigInteger.ONE).
setStartDate(start.getTime()).
setEndDate(end.getTime()).build();
if (generator != null) {
generator.initialize(keyPairGeneratorSpec);
}
keyPair = generator.generateKeyPair();
To your question, this line
generator.generateKeyPair(); implement the keypair generating and storing processs.
You might be confused when seeing codes in java.security.KeyPairGenerator like this
public KeyPair generateKeyPair() {
// ...
return null;
}
But actually since KeyPairGenerator is an abstract class, the 'true' class here using is
java.security.KeyPairGenerator$Delegate, which delegates the generateKeyPair like this
You can also check the difference of alias list in "AndroidKeyStore" before and after this process.
I would like to share the gist about how to view alias list of any keystore. Hope it might help you testing: https://gist.github.com/davidkhala/4aa1d6b44f287699aeac028786633c7a
When you generate a key pair with AndroidKeyStore, it is already automatically stored. You can retrieve it when you need it using the alias you specified ("alias" in your example).
Note, however, that when you get the PrivateKey, you do not actually get the private key secrets. Those stay in secure hardware and never leave it, so they can't leak. But you get a PrivateKey object which you can use just as though you had the secrets; your requests to encrypt or sign are sent to the secure hardware, which uses the secrets to perform the operation and hands the result back to you.
In my app we are using RSA key, that the app generate (using android key store) on the first launch. From unknown reason, the app failed to retrieved the key from the key store on some of the devices. I've checked the logs, and I could not find a correlation between this bug to a specific OS version or to a specific device model. Also, I know for sure the app tried to read it only after the key created. So - my question is that: As far as I know, android key store should be persistent. What can cause such a bug?
Bellow are relevant code samples.
Key generation:
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", keyStore.getProvider());
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
KeyGenParameterSpec spec;
spec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT| KeyProperties.PURPOSE_SIGN| KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setKeySize(2048)
.build();
generator.initialize(spec);
} else {
Calendar start = new GregorianCalendar();
Calendar end = new GregorianCalendar();
end.add(Calendar.YEAR, 500);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(alias)
.setSubject(new X500Principal("CN="+ subject))
.setSerialNumber(BigInteger.valueOf(new Random().nextInt(Integer.MAX_VALUE)))
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setKeySize(2048)
.build();
generator.initialize(spec);
}
return generator.generateKeyPair();
} catch (Exception e) {
logger.warn("Failed to create private key in store", e);
return null;
}
The keystore itself intialized using the following code:
KeyStore androidKeyStore = KeyStore.getInstance("AndroidKeyStore");
androidKeyStore.load(null);
return androidKeyStore;
And we use the following code to retrieve the key, the bug is that on some devices the keystore returns null:
try {
Key key = keyStore.getKey(alias, null);
if (key == null){
logger.warn("Key not found in key store");
return null;
}
if (key instanceof PrivateKey) {
// Get certificate of public key
Certificate cert = keyStore.getCertificate(alias);
// Get public key
PublicKey publicKey = cert.getPublicKey();
// Return a key pair
return new KeyPair(publicKey, (PrivateKey) key);
} else {
logger.warn("Key found, but not from current type. type found: " + key.getClass().getSimpleName());
}
return null;
}catch (Exception e){
logger.warn("Failed to get private key in store", e);
return null;
}
Thanks,
Omer
Just in case someone will run into the same problem:
I've found out that Azure Active Directory library for android suffer from similar issue, and from reading the code I've saw they linked to two issues that are similar to this problem and to another issue we have. Because of that I am planing to use keystore based on p12 file, stored in the app private storage.
Is it that you appear to lose your keys immediately after generation or some time later they are lost? Take a look at this question AndroidKeyStore getEntry is consistently failing after certain point which links to this great article: http://doridori.github.io/android-security-the-forgetful-keystore/
The moral of this story is if you use the AndroidKeyStore be prepared to lose your keys under certain circumstances!
With Android 4.3, this code return null.
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setKeyEntry(alias, privateKey, null, certificateChain);
PrivateKeyEntry entry=(PrivateKeyEntry)keyStore.getEntry(alias, new PasswordProtection(password));
assert(entry.getPrivateKey().getEncoded()!=null);
How it's possible to get the encoded version of private key ?
Or, is it possible to transmit the private key handler to another application ?
Thank's
The Android KeyChain API prevents you from being able to get an encoded private key.
See the method at line 158 of OpenSSLRSAPrivateKey.java
#Override
public final BigInteger getPrivateExponent() {
if (key.isEngineBased()) {
throw new UnsupportedOperationException("private exponent cannot be extracted");
}
But the benefit of using the KeyChain API is that it provides system-wide credential storage. Any app should be able to retrieve the key pair and certificate by its alias. Refer to the KeyStore docs.
I'm developing an Android app which based on a web-server. Users, who installed the app, should register on web, so they can login. When someone try to login I verify their information with API.
So I'm curious about persisting and encryption processes. Should I encrypt the values or just put them all to SharedPreferences? If encryption is needed what's the efficient way?
And last but not least, Is SharedPreferences enough in terms of security?
Thanks.
Encryption is easy, but the real question is with what key? If you hardcode the key in the app, or derive it from some known value, anyone with access to the device can easily decrypt those values. What you are achieving is merely obfuscation. Since Android doesn't have a public API to the system keystore, there is not much else you can do if you need to save the actual password. Unless of course you make the user input a password each time they start the app, which kind of defeats the purpose.
If you control both the server and the client, another approach is to use some form of token-based authentication and only save the token. Since tokens can expire and be revoked, the damage by someone getting hold of your token is much less, than exposing an actual password (which may be used on other sites as well).
Of course you should encrypt user settings like login, password or maybe email. I prefer SharedPreferences for storing, and yes it's enough in terms of security.
I've found this two method on StackOverflow, it's fair enough:
protected String encrypt( String value ) {
try {
final byte[] bytes = value!=null ? value.getBytes(UTF8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.System.ANDROID_ID).getBytes(UTF8), 20));
return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP),UTF8);
} catch( Exception e ) {
throw new RuntimeException(e);
}
}
protected String decrypt(String value){
try {
final byte[] bytes = value!=null ? Base64.decode(value,Base64.DEFAULT) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(),Settings.System.ANDROID_ID).getBytes(UTF8), 20));
return new String(pbeCipher.doFinal(bytes),UTF8);
} catch( Exception e) {
throw new RuntimeException(e);
}
}
Couldn't find link, if I found, I'll edit my answer.
Edit: I found the source, you may have a look at all discussion on here.
I'm writing both a server and an Android client application. The Android client sends measurements to the server. In order to ensure the data integrity, a digital signature is appended to each measurement.
Since I need everything to be Gson-compatible, storing the public key itself is not possible. I'm storing the G, P, Q and Y factors instead.
Here's a snippet from the request class:
public PublicKey getPublicKey() {
try {
DSAPublicKeySpec keySpec = new DSAPublicKeySpec(publicKeyY, publicKeyP,
publicKeyQ, publicKeyG);
KeyFactory fact = KeyFactory.getInstance("DSA");
PublicKey pubKey = fact.generatePublic(keySpec); // A
return pubKey;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void setPublicKey(PublicKey publicKey) {
try {
KeyFactory fact = KeyFactory.getInstance("DSA");
DSAPublicKeySpec pub = fact.getKeySpec(publicKey, DSAPublicKeySpec.class); // B
publicKeyG = pub.getG();
publicKeyP = pub.getP();
publicKeyQ = pub.getQ();
publicKeyY = pub.getY();
} catch (Exception e) {
e.printStackTrace();
}
}
The constructor makes use of the setPublicKey-method. When I create such request on the client side and send it to the server, both result in an exception.
On the client:
java.lang.RuntimeException: not implemented yet DSA Public Key
y: 2f9286201b266f38d682e99814612f7d37c575d3a210de114bdf02092f4a835109f28a590cfc568bb6525d59b8275fe791f3ddf20e85df44fd2e8622289f6dbc27c73d31d1769feae19573df22a9ca8ef80a9f7230b0b4a2671cc03fdb2788b55b4e9a68a7a5a93a214cc5aa39ccb5155a13354870d45a38760a80fd34333073
class java.security.spec.DSAPublicKeySpec
at org.bouncycastle.jce.provider.JDKKeyFactory.engineGetKeySpec(JDKKeyFactory.java:148)
at java.security.KeyFactory.getKeySpec(KeyFactory.java:210)
Next thing in the stack trace points at the rule I marked as B
On the server:
java.lang.NullPointerException
at sun.security.provider.DSAPublicKey.<init>(DSAPublicKey.java:74)
at sun.security.provider.DSAPublicKeyImpl.<init>(DSAPublicKeyImpl.java:46)
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(DSAKeyFactory.java:86)
at java.security.KeyFactory.generatePublic(KeyFactory.java:304)
at sensserve.protocol.StartSessionRequest.getPublicKey(StartSessionRequest.java:66)
Nextly pointing to the rule A.
I absolutely have no clue what I did wrong and what these messages mean. How can I solve these? Anyone who can tell me what I'm doing wrong?
Thanks a lot.
You should be able to store the public key in Base64 encoded from and still get valid JSON. You should be able to use DSAPublicKeySpec directly without calling getKeySpec() which apparently is not implemented in Bouncy Castle (Android's JCE provider). Not sure why you are getting NPE on the server, maybe wrong format. BTW, it will probably be easier if you are dealing with a single provider, so you might want to use Bouncy Castle on the server as well.