Bouncycastle fails in android API < 21 - android

i am trying to run some spongycastle code in android:
try {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
ECGenParameterSpec spec = new ECGenParameterSpec("P-256");
KeyPairGenerator generator = KeyPairGenerator.getInstance("ECDSA", "SC");
generator.initialize(spec, new SecureRandom());
KeyPair keyPair = generator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
String publicKeyStr = publicKey.getW().getAffineX().toString() + ":" + publicKey.getW().getAffineY().toString();
Log.d(TAG, publicKeyStr);
Calendar c = Calendar.getInstance();
Date d0 = c.getTime();
c.add(Calendar.DATE, 1);
Date expiry = c.getTime();
String token = Jwts.builder()
.setIssuedAt(d0)
.setSubject("00000000-0000-0000-0000-000000000001")
.setExpiration(expiry)
.signWith(privateKey, SignatureAlgorithm.ES256).compact();
Log.d(TAG, token);
} catch (Exception e) {
Log.d(TAG, e.toString());
}
in API version 21+, it works as expected.
in API version 18, it works in debug. in release, it fails in function generator.generateKeyPair() with java.lang.IllegalArgumentException: Invalid point.
in API version 16, it fails in function Jwts.builder().signWith() with io.jsonwebtoken.security.SignatureException: Invalid Elliptic Curve PrivateKey. can't recognise key type in ECDSA based signer.
any idea what i am doing wrong?
edit: i have apparently fixed the problem by switching from spongycastle to bouncycastle.
in my build.gradle i changed this:
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.madgag.spongycastle:prov:1.58.0.0'
to this:
implementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
and in my code i got rid of this line:
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
and added these lines:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.insertProviderAt(new BouncyCastleProvider(), 1);
and i changed the provider from "SC" to "BC".
and now my code works as i would like, for all versions of the android API.
my concern now is the comment at this link...
Spongy Castle: is it obsolete?
...by the author of spongycastle:
Why might Spongy Castle not be obsolete?
...
even on post-Android 3.0 devices, device manufacturers are not above
carelessly bundling libraries, it's possible that Bouncy Castle may
still be bundled on some obscure devices.
what does that mean exactly? could it cause my code above to malfunction on certain devices?

use bouncy castle:
dependencies {
// https://mvnrepository.com/artifact/org.bouncycastle
implementation "org.bouncycastle:bcprov-jdk15on:1.60"
implementation "org.bouncycastle:bcpkix-jdk15on:1.60"
}

Related

android.security.KeyStoreException: Incompatible digest when signing with RSA

When I try to sign a hashed value in my android app that I get from outside, I get the above mentioned exception.
The code for generating the keypair is:
public static KeyPair generateKeyPair(Context context, String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
AlgorithmParameterSpec parameterSpec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build();
} else {
// Here I build the keys for older versions. This is not part of my problem
}
keyPairGenerator.initialize(parameterSpec);
return keyPairGenerator.generateKeyPair();
}
Later on I sign the hash I get from outside:
public static byte[] signHash(byte[] hashToSign) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(MY_KEY_ALIAS, null);
PrivateKey privateKey = keyEntry.getPrivateKey();
DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder();
AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(KeyProperties.DIGEST_SHA256);
DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hashToSign);
byte[] hashToEncrypt = digestInfo.getEncoded();
Cipher cipher = Cipher.getInstance("RSA/ECB/Pkcs1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); // <= the exception is thrown here
return cipher.doFinal(hashToEncrypt);
} catch (Throwable e) {
Log.e("KeyStoreWrapper", "Error while signing: ", e);
}
return "Could not sign the message.".getBytes(StandardCharsets.UTF_16LE);
}
The error I get is:
java.security.InvalidKeyException: Keystore operation failed
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1004)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at de.new_frontiers.m2fa.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
at de.new_frontiers.m2fa.bluetooth.BluetoothHandler.handleMessage(BluetoothHandler.java:93)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: android.security.KeyStoreException: Incompatible digest
at android.security.KeyStore.getKeyStoreException(KeyStore.java:944)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024) 
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53) 
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89) 
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263) 
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108) 
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612) 
at javax.crypto.Cipher.tryCombinations(Cipher.java:532) 
at javax.crypto.Cipher.getSpi(Cipher.java:437) 
at javax.crypto.Cipher.init(Cipher.java:815) 
at javax.crypto.Cipher.init(Cipher.java:774) 
at com.mycompany.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186) 
In the android documentation I see:
For signing and verification operations a digest must be specified in
the additional_params argument of begin. If the specified digest is
not in the digests associated with the key, the operation must fail
with KM_ERROR_INCOMPATIBLE_DIGEST.
But as you can see I create the keypair with KeyProperties.DIGEST_SHA256 and set the DigestInfo to the same algorithm. So I don't understand why I get the error "Incompatible digest". Can anybody shed some light on this?
Oh, for anybody who is wondering why I don't use Signature.sign(): this would need the plaintext to sign then creates a hash, a DigestInfo and then encrypts it with the private key. I already get the hash from outside and this is something I cannot change.
 
Encryption with private key using RSA/ECB/PKCS1Padding is available on AndroidKeyStore from Android 18, so you should be able to perform a valid digital signature with the received hash.
I guess the problem is setting the key usage to sign and not for encryption (that is really what you want the key for). Try this:
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
Check also that the hashToSign is really SHA-256 (32 bytes)
pedrofbs answer helped me to get things right in the end. I had already changed the purpose for the key to the value mentioned in my comment to his answer: KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|Key‌​Properties.PURPOSE_D‌​ECRYPT, but forgot to call .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1). So a big thank you, pedrofs, for spotting this!
Unfortunately it still did not work. Fiddling around with different settings on the key I realized that I hadn't protected the device I use for testing with a password, pin or something else. So adding .setUserAuthenticationRequired(false) to the KeyGenParameterSpec did the trick. I know that this is not safe and has to be changed but at the moment I'm just doing a proof of concept, so that's fine. :-)
In case somebody else stumbles upon this problem: here is the whole working code for the key generation:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(LOGON_KEY_ALIAS, KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setUserAuthenticationRequired(false)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
}

Android KeyStore - keys not always persisted

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!

Android Key Store using (between different apps)

Would appreciate any hint what I am missing in below code?
void addKeyToKeyStore() {
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
cal.add(Calendar.YEAR, 1);
Date end = cal.getTime();
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(new KeyPairGeneratorSpec.Builder(getApplicationContext())
.setAlias("MY_ALIAS")
.setStartDate(now)
.setEndDate(end)
.setSerialNumber(BigInteger.valueOf(1))
.setSubject(new X500Principal("CN=MY_ALIAS"))
.build());
}
void ListTheKeys() {
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
final Enumeration<String> aliases = ks.aliases();
while(aliases.hasMoreElements()) {
Log.d(TAG, "Key Alias = "+aliases.nextElement());
}
String tempstr= "Entry for MY_ALIAS is " + (ks.isKeyEntry(key)? "exist" : "not exist");
Log.d(TAG, tempstr)
}
If same app calls addKeyToKeyStore() and then ListTheKeys() - the entry is exist. It also works if app restarted or phone rebooted.
However if app1 calls addKeyToKeyStore() but app2 then calls ListTheKeys() - no entry that was added.
Have tried set same sharedUserID in AndroidManifest(s) - no effect.
I need to be able to access KeyStore from different apps.
Thanks in advance!
Never mind. I have figured out - a bit messed up with sharedUserID. If the sharedUserID is declared properly - everything works.
Issue can be closed.

Android Keystore Provider - How to store an API key

In my android app, I receive an API token that I get from an http request
I did some research, and, If it seems the best way to store this key, is with the Android Keystore Provider.
I had a look at the documentation https://developer.android.com/training/articles/keystore.html#UsingAndroidKeyStore
But since i'm new with Android and programing in general, I need some help to put that in place.
I'm not sure in which variable I should save the key :
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
cal.add(Calendar.YEAR, 1);
Date end = cal.getTime();
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpg.initialize(new KeyPairGeneratorSpec.Builder(getApplicationContext())
.setAlias(alias)
.setStartDate(now)
.setEndDate(end)
.setSerialNumber(BigInteger.valueOf(1))
.setSubject(new X500Principal("CN=test1"))
.build());
KeyPair kp = kpg.generateKeyPair();
Then how should i retrieve the key, using (I think) the following code :
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();
Thank you for your help.
I would assume that your API token is only valid for a certain period of time. Just store it in a SharedPreferences for your application. If it isn't there, or it has expired, just get another one.

Android 4.3 KeyStore - chain == null while trying to retrieve keys

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

Categories

Resources