I'm just trying to get a basic example of the Android Key Store system to generate a symmetric key. I followed the example in the tutorial (using Kotlin) but I get an error like so:
java.security.NoSuchProviderException: no such provider: AndroidKeyStore
Below is my code where the compiler is throwing an error:
val kpg: KeyGenerator = KeyGenerator.getInstance("DES", "AndroidKeyStore")
On my Gradle, I am using compileSdkVersion and targetSdkVersion to 28. I'm also have a minSdkVersion of 25. All of which should satisfy the Android's doc on using the AndroidKeyStore (min API level 18).
If I remove the provider, everything works like planned, since I'm assuming it goes to the default provider. The same goes for the KeyPairGenerator and KeyStore classes when I try the AndroidKeyStore provider.
Am I using the wrong provider keyword? Or is there some additional setup that I'm supposed to be doing?
Thanks,
Update 1 - So I kept searching and found that you can get a list of available providers on my system. Here's my code below:
for (p in Security.getProviders()) {
//Log.d(TAG, String.format("== %s ==", p.getName()))
println(String.format("== %s ==", p.getName()))
println(String.format("%s", p.info))
// for (s in p.getServices()) {
// //Log.d(TAG, String.format("- %s", s.getAlgorithm()))
// println(String.format("- %s", s.getAlgorithm()))
// }
}
Also, the results match what's in my $JAVA_HOME/jre/lib/security.java.security file:
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=sun.security.ec.SunEC
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=apple.security.AppleProvider
So I guess my option is to add the AndroidKeyStore provider to this. I'll update this when I've tried that.
I found out that there's a bug at the moment with using the AndroidKeyStore with unit tests, which I didn't mention in my original question.
See here:
https://github.com/robolectric/robolectric/issues/1518
Related
I decided to use new EncryptedSharedPreferences from AndroidX Security library. Since the app is supporting API 21 and higher, I decided to try out this new v1.1.0-alpha02 version, since it supports API 21+
So, I succeded to make the implementation for API 23+, but for older versions where Android KeyStore is not supported, I couldn't make it right, and there are no exact instructions how the master key should be created to make it work somehow.
The code for initializing SharedPrefs:
EncryptedSharedPreferences.create(
"prefs_name",
createMasterKey(),
App.appContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
with this function for creating master key
private fun createMasterKey(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
} else {
val alias = "my_alias"
val start: Calendar = GregorianCalendar()
val end: Calendar = GregorianCalendar()
end.add(Calendar.YEAR, 30)
val spec = KeyPairGeneratorSpec.Builder(App.appContext)
.setAlias(alias)
.setSubject(X500Principal("CN=$alias"))
.setSerialNumber(BigInteger.valueOf(abs(alias.hashCode()).toLong()))
.setStartDate(start.time).setEndDate(end.time)
.build()
val kpGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
"RSA",
"AndroidKeyStore"
)
kpGenerator.initialize(spec)
val kp: KeyPair = kpGenerator.generateKeyPair()
kp.public.toString()
}
}
I found this solution somewhere out there, but it's not verified (no confirmation that it actually works), but it seems it should work.
When using this code block for API 21 and 22, the error appears on creating EncryptedSharedPreferences, and it says:
Method threw 'com.google.crypto.tink.shaded.protobuf.InvalidProtocolBufferException' exception.
Protocol message contained an invalid tag (zero).
Did someone find the solution for this implementation, or do you know why is this happening?
I think this would help a lot of people, since there is no exact explanation what should this master key contain.
Thanks in advance!
Add to manifest
android:allowBackup="false" android:fullBackupContent="false"
Because after uninstalling the application you still have backed up your crypto file which you definitely can't decrypt after installing a new version.
I can fix the InvalidProtocolBufferException in two ways, though I don't like either of them:
Use an older version of security-crypto
implementation 'androidx.security:security-crypto:1.1.0-alpha01'
Use the latest (at the time of writing) version of security-crypto, but with a forced older version of tink:
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
implementation('com.google.crypto.tink:tink-android') {
version {
strictly '1.4.0'
}
}
Here is what I have done, it seems to have fixed the error. Instead of removing the auto-backup feature, just do this:
In AndroidManifest.xml
android:allowBackup="true"
android:fullBackupContent="#xml/backup_descriptor"
In backup_descriptor.xml
<full-backup-content>
<exclude domain="sharedpref" path="keys.xml"/>
</full-backup-content>
keys.xml being the encrypted shared preference file name. Exclude all encrypted shared preference files this way.
Also, I am using
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
for now, seems like everything is working well with this setup.
UPDATE: Errors on different devices. The result isn't the solution.
I setup the android propreties "fullbackupcontent" this way in my Manifest
android:fullBackupContent="#xml/backup_descriptor"
Here is my backup_descriptor file
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- App data isn't included in user's backup unless client-side encryption is enabled. -->
<include domain="file" path="." requireFlags="clientSideEncryption" />
<!-- Exclude specific shared preferences that contain GCM registration Id -->
<!-- <exclude domain=["file" | "database" | "sharedpref" | "external" | "root"]-->
<!-- path="string" />-->
</full-backup-content>
Now my app is working again & I can keep allowing backups.
Source :
https://developer.android.com/guide/topics/data/autobackup#define-device-conditions
https://developer.android.com/guide/topics/data/autobackup#IncludingFiles
Here is a different solution that I found on google issue tracker.
As google document stated, "Note: The methods in both the EncryptedFile class and the EncryptedSharedPreferences class aren't thread-safe." You need to wrap the calling in a synchronised method.
#synchronized
fun createSharedPreferences() {
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
prefs = EncryptedSharedPreferences.create(
"file-name",
masterKeyAlias,
context,
PrefKeyEncryptionScheme.AES256_SIV,
PrefValueEncryptionScheme.AES256_GCM)
}
src. https://issuetracker.google.com/issues/147480931#comment19
Your app contains exposed Google Cloud Platform (GCP) API keys. Please see this Google Help Center article for details.
Vulnerable locations:
com.abc.Youtube_Player->onCreate
This is How my code look at the back end
public class Youtube_Player extends AppCompatActivity implements YouTubePlayer.OnInitializedListener {
// YouTube player view
public static final String GOOGLE_API_KEY = "<api key>";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_youtube__player);
// Initializing video player with developer key
mPlayerView.initialize(GOOGLE_API_KEY, this);
}
}
You have API Key in the code.
As a best practice, you should keep the secret keys in a secure system like Google Secret Manager, HashiCorp Vault, encrypted secure GCS Bucket etc.. If these option are not feasible for you, still try to put secret keys in some other property file and control access of that file.
To avoid this warning message from the console:
Leaked GCP API Keys Your app contains exposed Google Cloud Platform
(GCP) API keys. Please see this Google Help Center article for
details.
You must define the values you want to "hide" inside your gradle.properties file (if it doesn't exist, you can create it)
JORGESYS_API_KEY=key=AI9876IoaNutaEFrumoAsaAsa123An8mTRk-U
SECRET_CLIENT_API_KEY=key=AIzaSyJorgeSysIsCoOlaeB12GSET-U
SECRET_TOKEN_API_KEY=key=AIzaS12JorgeSysIsCoOlsauPrOsTaeB12GSET-U
and define the reference of these values inside app/build.gradle
android {
...
...
defaultConfig {
...
...
...
//*Defined in gradle.properties
buildConfigField "String", "JORGESYS_API_KEY", "\"$JORGESYS_API_KEY\""
buildConfigField "String", "SECRET_CLIENT_API_KEY", "\"$SECRET_CLIENT_API_KEY\""
buildConfigField "String", "SECRET_TOKEN_API_KEY", "\"$SECRET_TOKEN_API_KEY\""
}
}
When generating your project, the BuildConfig class will be generated that will contain the values and that you can assign to your application when compiling.
val myAPIKEY = BuildConfig.JORGESYS_API_KEY
These values cannot be obtained by "reverse engineering"! :-)
You have define your api key with 'public static' it means your api key access any where in the app.And chance to leak your api key.You need to change from 'public static' to private.
Complete guide for use API Keys and avoid Leaked GCP API Keys security issue in Google Play Console : https://stackoverflow.com/a/71155071/13387867
I am using AndroidKeyStore in my app to store a secret key and I am generating that key using KeyGenParameterSpec class. Here's the code:
final SecretKey secretKey;
final KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(<validityInSeconds>)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setRandomizedEncryptionRequired(false)
.build();
keyGenerator.init(keyGenParameterSpec);
secretKey = keyGenerator.generateKey();
And after that, I am trying to init the cipher with this key:
final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
Now, AndroidKeyStore seems to have a weird issue. cipher.init is failing in some cases and the weird part is, I am keeping everything same/constant except trying out different values for validityInSecs. When it fails, it fails with this error:
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
at org.spongycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:615)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:973)
at javax.crypto.Cipher.init(Cipher.java:908)
I've tried with different values for validityInSec and it fails from some (for values which are quite low) and succeeds for some (when I increase the value). Here are some data points:
10s -> fail 1800s -> success 100s -> fail 750s ->
success
Another interesting thing is that for the same value, it's failing sometimes and succeeding sometimes. Examples of these values are 750s and 625s.
I have no clue what's going on here.
Device: One plus 3t
OS: 8.0.0
Please help.
Edit:
The stack trace is a little misleading here, as it has traces from Spongycastle library.
That's bug in android framework classes. For a cipher, it tries with all the different providers one by one and if it fails, it stores the exception in a single variable. The problem is, it doesn't update that exception variable. So if the cipher failed for all the providers, the exception that it throws is from the first provider. So, it gives you this expression that only that provider was tried.
Here is the relevant code:
http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/javax/crypto/Cipher.java#2546
For my case, after debugging class-by-class over the success case, I found out that it IS trying other providers too apart from SC. Relevant screenshot:
UPDATE 2:
I tried final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");.
Now, when I pass duration as 1800s, it succeeds. And when I pass 40s, it fails with the following error:
06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:830)
at javax.crypto.Cipher.init(Cipher.java:771)
I am gonna try authenticating user just before trying to init the cipher and see if it works
Although I do wanna solve this, In my production env, my time would anyways be 1800s. Is it okay to forget that it fails at lower duration?
I tried
final Cipher cipher = Cipher.getInstance(<transformation>,"AndroidKeyStoreBCWorkaround");.
By putting the breakpoints, I figured out that this is the provider that actually works.
Now, when I passed duration as 1800s, it succeeded. And when I pass 40s, it failed with the following error:
06-28 20:06:55.462 624-624/in.zeta.android E/EncryptedStoreService: android.security.keystore.UserNotAuthenticatedException: User not authenticated
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:741)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:777)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2663)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2556)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:830)
at javax.crypto.Cipher.init(Cipher.java:771)
Relevant source code links from the stack trace:
https://android.googlesource.com/platform/frameworks/base/+/508e665/keystore/java/android/security/keystore/AndroidKeyStoreCipherSpiBase.java#215
https://android.googlesource.com/platform/frameworks/base/+/83a86c5/keystore/java/android/security/KeyStoreCryptoOperationUtils.java#42
https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#707
Following the second link, it's not going into KeyStore.OP_AUTH_NEEDED, otherwise it'd have returned null from there. So mostly, it's in LOCKED state.
(https://android.googlesource.com/platform/frameworks/base/+/master/keystore/java/android/security/KeyStore.java#58)
I understand if it needs user authentication while initing the cipher, but was curious why it's successful for the duration of 1800s. I left my app open for > 1800s and then tried with this duration, it failed with the same exception. And then I passed 18000s and it worked. So, turns out it checks if user was authenticated within past keyvalidity secs, which perfectly makes sense, is obvious and if I am not wrong, present in docs too.
So the whole confusion was there due to the wrong exception thrown by Cipher class >.<
I am gonna try authenticating user just before trying to init the cipher and see if it works
I do not know how the validityInSeconds can affect the code, but the exception does not have to do with this
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at org.spongycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
the exception is caused because the key is managed by the AndroidKeyStore but the Cipher is using the cryptographic provider spongycastle. The AES key is not extractable and spongycastle needs it to be able to encrypt
Remove spongycastle from your project or force to use AndroidKeyStore when you init the Cipher
Cipher cipher = Cipher.getInstance(algorithm, "AndroidKeyStore");
The cryptographic providers are selected in order of installation in the operating system. If a cryptographic provider is not explicitly indicated, Android will select spongycastle or AndroidKeyStore according to the one above. I do not know how validityInSeconds affects, but there may be some internal Android behavior that changes the order
My app is using Android's keystore to encrypt some data after authenticating with a fingerprint. This seems to work on most devices but I have received error reports of OnePlus2 users with the exception
android.security.KeyStoreException: Signature/MAC verification failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:632)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:217)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:473)
at javax.crypto.Cipher.doFinal(Cipher.java:1502)
My code basically does this (Written in Mono for Android):
Cipher _cipher = Cipher.GetInstance(KeyProperties.KeyAlgorithmAes + "/"
+ KeyProperties.BlockModeCbc + "/"
+ KeyProperties.EncryptionPaddingPkcs7);
KeyStore _keystore = KeyStore.GetInstance("AndroidKeyStore");
FingerprintManager _fingerprintManager = (FingerprintManager) Context.GetSystemService(Context.FingerprintService);
_keystore.Load(null);
var key = _keystore.GetKey(_keyId, null);
_cipher.Init(CipherMode.EncryptMode, key);
_cryptoObject = new FingerprintManager.CryptoObject(_cipher);
_fingerprintManager.Authenticate(_cryptoObject, _cancellationSignal, 0 /* flags */, this, null);
//OnAuthSucceeded:
var mySecret = _cipher.DoFinal(System.Text.Encoding.UTF8.GetBytes(textToEncrypt));
Is there anything wrong with the code? What does the exception mean?
First, your code looks fine.
In my experience Android fingerprint tends to have a lot of weird edge case errors across various devices.. I can't answer exactly but it sounds like a HW or implementation issue with the FP api on oneplus's part. I know XiaoMi and even Google has acknowledged various weird issues with their implementation.
Tips:
make sure you are listening only once for fingerprint. if you listen twice, you can receive the incorrect cipher object, so the encryption won't match.
update your gradle/min sdk/support libraries, all that stuff
hold on to your butt
I am developing an Android Application and I need to generate some RSA private and public keys to use for secure communication with web services. To do this I need to have the public key in a .NET compatible form.
Like:
<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>
So far I managed to to this:
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
keypair = keyGen.genKeyPair();
privateKey = keypair.getPrivate();
publicKey = keypair.getPublic();
// Get the bytes of the public and private keys
byte[] privateKeyBytes = privateKey.getEncoded();
byte[] publicKeyBytes = publicKey.getEncoded();
I've got no clue how to continue. Could you please provide some help ?
For anybody else interested, a very good tutorial can be found in here
http://www.codeproject.com/KB/security/porting_java_public_key.aspx?msg=3407475
If you need Base64 encoding/decoding, because it's not included in Android (at least in API 4) you could use the class from here: iharder.sourceforge.net/current/java/base64/
You don't show the type publicKey. If is not already, you should cast to an RSAPublicKey, then use the getPublicExponent() and getModulus() methods to extract the BigInteger. Then simply use standard Java IO, e.q. PrintStream.println() or printf() to generate the XML components.