
Today we will look into Android Keystore. We will look at what is an Android Keystore. Why should you use it or even consider using it? We will also see how it will help you in storing sensitive data inside the android system’s secure storage.
Quick Navigation
Introduction to the Android Keystore
The first thing you should understand about the Keystore is that it is secure, very secure. It is secure because it is backed by something known as akeychain. The termkeychain is mostly used in desktop*inux-based OS.
But that’s not all, there is some other mechanism known as theAndroid Keystore provider which can be used if the developer doesn’t want to rely on akeychain-based approach. One thing you should keep in mind is that both are equally secure it’s just a matter of preference.
Now for the Keystore to be secure we must consider the two things.
- The key or confidential data must never enter the application’s processes.
- There might be dedicated hardware where all the manipulation of the secure data happens. But this is optional as legacy devices might not have this.
If you understood the gist of the above paragraphs. Then we are headed in the right direction. Let us jump into the code directly.
In this tutorial, I have skipped UI creation and similar basic things. We will only look into the actual logic of Android Keystore.
Step #1: Create models to hold data for encryption and decryption
Please follow the steps exactly or you can even copy-paste the final source code after each section.
#1First, we create a new file namedSecret.kt
and add the code below
sealedclassSecret(privateval alias: String,privatevaldata: String,privateval iv: String)
Code language:Kotlin(kotlin)
Let us look at what is being done in the class.
- The
alias
variable will hold the name of the secret data we will be stored in the Keystore. - The variable nameddata will contain the actual data, either encrypted or decrypted.
- The variable named
iv
is the piece of random information that is required for encryption and decryption.
You will get a better idea of each of them when you finally run the code yourself.
#2We create additional classes to hold the encryption and decryption data respectively
In the same file add the following blocks of code to create the new classes based off of the sealed class.
dataclassEncryptionSecret(val alias: String,valdata: String)
Code language:Kotlin(kotlin)
The class above will hold the raw data. In other words, it will hold the data for encryption.
dataclassDecryptionSecret(val alias: String,valdata: String,val iv: String)
Code language:Kotlin(kotlin)
The class above will hold the encrypted data. In other words the data for decryption.
If you followed the steps I mentioned. Then you will end up with the final code that looks something like this:
// file name -> Secret.ktsealedclassSecret(privateval alias: String,privatevaldata: String,privateval iv: String)sealedclassSecret(privateval alias: String,privatevaldata: String,privateval iv: String)dataclassDecryptionSecret(val alias: String,valdata: String,val iv: String)
Code language:Kotlin(kotlin)
Step #2: Create a Keystore helper class
Now that we have completed the creation of the necessary classes to hold our data. The next thing that we should do is create a class which will contain all the logic.
You should create a file namedKeystoreHelper.kt
and copy the code below
object KeystoreHelper {privateconstval KEY_SIZE =128privateconstval PROVIDER ="AndroidKeyStore"privateconstval TRANSFORMATION ="AES/GCM/NoPadding"privateval NULL =nullprivateconstval EMPTY =""privateconstval NEW_LINE ="\n"funencrypt(encryptionSecret:EncryptionSecret): DecryptionSecret {val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PROVIDER)val kps = KeyGenParameterSpec.Builder( encryptionSecret.alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ).setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build() kg.init(kps)val sk = kg.generateKey()val c = Cipher.getInstance(TRANSFORMATION) c.init(Cipher.ENCRYPT_MODE, sk)val eb = c.doFinal(encryptionSecret.data.toByteArray())val ctB64 = Base64.encodeToString(eb, Base64.NO_PADDING)val ivB64 = Base64.encodeToString(c.iv, Base64.NO_PADDING)return DecryptionSecret(data = ctB64.replace(NEW_LINE, EMPTY), iv = ivB64.replace(NEW_LINE, EMPTY), alias = encryptionSecret.alias ) }fundecrypt(decryptionSecret:DecryptionSecret): EncryptionSecret {val iv = Base64.decode(decryptionSecret.iv, Base64.NO_PADDING)val ct = Base64.decode(decryptionSecret.data, Base64.NO_PADDING)val ks = KeyStore.getInstance(PROVIDER) ks.load(NULL)val ske: KeyStore.SecretKeyEntry = ks.getEntry(decryptionSecret.alias, NULL)as KeyStore.SecretKeyEntryval c = Cipher.getInstance(TRANSFORMATION)val gps: AlgorithmParameterSpec = GCMParameterSpec(KEY_SIZE, iv) c.init(Cipher.DECRYPT_MODE, ske.secretKey, gps)val decryptedBytes = String(c.doFinal(ct))return EncryptionSecret( alias = decryptionSecret.alias,data = decryptedBytes ) }}
Code language:Kotlin(kotlin)
If you look the code above closely you will see that we are passing theDecryptionSecret
andEncryptionSecret
fordecryption
andencryption
respectively.
That’s all you have to do for the storing and retrieving the secret from the keystone.
Step #3: Using the Android Keystore to store and retrieve secret data
Now that we have finally completed all the heavy lifting. We created all the necessaryclasses
that is required for us toencrypt
anddecrypt
our secret data. Let us see how we can use it in our application.
#1 To encrypt the data
// prepare the data for encryptionval encryptionSecret = EncryptionSecret(alias ="my_secret_key",data ="ABC-1920")// pass the data for encryption we will receive the encrypted data as output// you may save it somewhere like backend or shared preferencesval encryptedData: DecryptionSecret = KeystoreHelper.encrypt(encryptionSecret)
Code language:Kotlin(kotlin)
Let us understand what is happening in the code above.
- First we create an encryption secret that has
alias
anddata
that we want to encrypt - Then we invoke the method
KeystoreHelper.encrypt
with the data that we created earlier - Store the information somewhere you should store all the three information
The data that might be obtained after encryption will look something like this:
alias =my_secret_keydata =wyI5f6i5ZaJLsGQaLXeR7rpcE4Sjrj3fiv =gLjtA7en9Sm9gf1y
#2 To decrypt the data
Now we have to pass in the encrypted data above and invoked the decrypt method.
Let us prepare theDecryptionSecret
by passing in the values above.
// prepare the data for decryptionval toDecrypt = DecryptionSecret( alias = "my_secret_key", data = "wyI5f6i5ZaJLsGQaLXeR7rpcE4Sjrj3f", iv = "gLjtA7en9Sm9gf1y")// pass the above data to begin the process of decryptionval decryptedData = KeystoreHelper.decrypt(toDecrypt)
The variable nameddecryptedData will have the output of your actual unencrypted data. In this case it will give out information as:
alias =my_secret_keydata =ABC-1920
So, in our case, the ABC-1920 is the secret key that we encrypted.
But wait, we can take it even a step further. Let us automate the saving and storing of encrypted data.
Step #4: Saving the encrypted output to shared preferences for automation
Yes, let us go one step further and save the encrypted data automatically using shared preferences. This will not only remove the burden of having to save everything manually.
It will not only make the whole process flawless. But also help you to focus on only invoking just two methods.
Create a file namedKeystorePreference.kt
and add the code below to that file.
object KeystorePreference {privateconstval SP_NAME ="__encrypted_data"privatelateinitvar application: Applicationprivateval sp: SharedPreferencesby lazy { application.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE) }funinit(application:Application) {this.application = application }funget(alias:String): String? {val json: String = sp.getString(alias,null) ?:returnnull// decrypt the dataval obj = JSONObject(json)val decryptionSecret = DecryptionSecret( alias = obj.getString("alias"), iv = obj.getString("iv"),data = obj.getString("data"), )return KeystoreHelper.decrypt(decryptionSecret).data }@Throwsfunsave(key:String, value:String): DecryptionSecret {// check if the key is already present// we do not want to override itif (sp.contains(key))throw IllegalAccessException("The key already exist.")val input = EncryptionSecret( alias = key,data = value )val encrypted = KeystoreHelper.encrypt(input)// save the encrypted data to shared preferencesval json = JSONObject().apply { put("alias", encrypted.alias) put("data", encrypted.data) put("iv", encrypted.iv) } sp.edit().putString(key, json.toString()).apply()return encrypted }privatefunget(encryptionSecret:EncryptionSecret): String {return KeystoreHelper.encrypt(encryptionSecret).data }funremove(key:String) { sp.edit().remove(key).apply() }}
Code language:Kotlin(kotlin)
If you see the code above, you will realize that I have done nothing new. I just created a shared preference object and used it alongsideStep #3
.
Now the first thing you should do is invoke theKeystorePreference.init(application)
, where the application is theapplication
instance. If you miss doing that then, your application will crash.
To save a secret data all you have to do is invoke the method like this:
KeystorePreference.save("api_key","this is a really secret data")
Code language:Kotlin(kotlin)
And you can retrieve it like this:
valdata = KeystorePreference.get("api_key")
Code language:Kotlin(kotlin)
This is all that you have to do everything else will be managed under the hood automatically. And of course, there is something you should remember.
You will not be able to decrypt the data if you uninstall the application and reinstall it again.
It is because the keys for encryption and decryption is managed byAndroid OS.
So, when you uninstall the application the keys will be deleted by the system.
This concludes our Android Keystore tutorial. Keep learning, keep coding.
Also Read:
Android Fingerprint Authentication Tutorial: How to Guide
Android Fragments vs Activities
Retrofit File Upload using FileProvider ( for Content URIs )
Parcelable in Android: How to transfer data in-between ?
Advertisement
Trending Posts
- How to use DeepSeek Locally with Ollama in Ubuntu 24.04
- TRAE: The AI IDE That Codes With You
- Top 6 Command Line Music Players for Linux
- Avoid Headless Detection in Puppeteer
- Implementing In-app Update in Android
- Using Coil image loader for Android
- Android Text to Speech (TTS): A simple guide
- Android Keystore: Store Sensitive Data in Android
- Android Fingerprint Authentication Tutorial: How to Guide
- Constructor Overloading in Java | with Examples