I have an Android app that uses Realm with encryption. We see very few cases of users getting an IllegalBlockSizeException during startup, when we setup the Realm encryption.
We create the key with the functions below:
private fun getOrGenerateKey() : ByteArray {
val keyPair = getOrGenerateKeyPair()
val prefs = applicationContext.getSharedPreferences(RealmPrefs, Context.MODE_PRIVATE)
val cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding") // We need an algorithm supported on all android API levels
// Try reading the encrypted AES key from the preferences
val key = prefs.getString(RealmPrefsAES, null);
if(key == null) {
// No key was stored, generate a new byte array, encrypt then store:
val keyBytes = ByteArray(64)
SecureRandom().nextBytes(keyBytes)
cipher.init(Cipher.ENCRYPT_MODE, keyPair.public)
val encryptedBytes = cipher.doFinal(keyBytes)
prefs.edit()
.putString(RealmPrefsAES, Base64.encodeToString(encryptedBytes, Base64.URL_SAFE))
.apply()
return keyBytes;
}
cipher.init(Cipher.DECRYPT_MODE, keyPair.private)
return cipher.doFinal(Base64.decode(key.trim(), Base64.URL_SAFE))
}
#SuppressLint("InlinedApi")
private fun getOrGenerateKeyPair() : KeyPair {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null);
val keyEntry = keyStore.getEntry(RealmKeyAlias, null) as KeyStore.PrivateKeyEntry?
if(keyEntry != null) {
return KeyPair(
keyEntry.certificate.publicKey,
keyEntry.privateKey
)
}
// If we got here, there was no key and we need to generate a new one. That is done differently in the API levels:
return if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")
kpg.initialize(KeyGenParameterSpec.Builder(
RealmKeyAlias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build())
kpg.generateKeyPair()
} else {
val start = Calendar.getInstance()
val end = Calendar.getInstance()
end.add(Calendar.YEAR, 100)
#Suppress("DEPRECATION") // Deprecation handled by SDK version test
val spec = KeyPairGeneratorSpec.Builder(applicationContext)
.setAlias(RealmKeyAlias)
.setSubject(X500Principal("CN=$RealmKeyAlias"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.time)
.setEndDate(end.time)
.build()
val kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore")
kpg.initialize(spec)
kpg.generateKeyPair()
}
}
Related
I am trying to store encrypted data in a SQL database using AES with Initialization Vector (IV). I am able to do this with the following class:
class Encrypted (wordE : String) {
val keyGenerator: KeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,"AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
fun genKey(){
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
}
fun getKey(): SecretKey {
genKey()
val keystore = KeyStore.getInstance("AndroidKeyStore")
keystore.load(null)
val secretKeyEntry = keystore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey
}
fun encryptData(data: String): Pair<ByteArray, ByteArray> {
val cipher = Cipher.getInstance("AES/CBC/NoPadding")
var temp = data
while (temp.toByteArray(Charsets.UTF_8).size % 16 != 0)
temp += "\u0020"
cipher.init(Cipher.ENCRYPT_MODE, getKey())
val ivBytes = cipher.iv
val encryptedBytes = cipher.doFinal(temp.toByteArray(Charsets.UTF_8))
return Pair(ivBytes, encryptedBytes)
}
val pair = encryptData(wordE)
val encrypted = pair.second.toString(Charsets.UTF_8)
val iv = pair.first
}
but I have some problems to decrypt the data from the database.
For decryption I have implemented another class:
class Decrypted (dataD: String, ivD :String) {
fun getKey(): SecretKey {
val keystore = KeyStore.getInstance("AndroidKeyStore")
keystore.load(null)
val secretKeyEntry = keystore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
return secretKeyEntry.secretKey
}
fun decryptData(data: String, iv : String) : String {
val spec = IvParameterSpec(iv.toByteArray())
val decipher = Cipher.getInstance("AES/CBC/NoPadding")
decipher.init(Cipher.DECRYPT_MODE, getKey(), spec)
val encryptedData: ByteArray = data.toByteArray()
return decipher.doFinal(encryptedData).toString().trim()
}
val decryptedData = decryptData(dataD, ivD)
}
I understood that IV must be the same as the one generated during encryption, so I also stored IV in the database as .
From my logcat I get an "InvocationTargetException" to "Invalid IV" message.
I can see that when I call the Decrypted class enter for decryption:
dataEncrypted: /�,#�j3�RqLrY�
iv_stored: [B#5a36422
I had this problem when trying to decrypt an encrypted string. I had read a lot and also search for answers from Stack Overflow, but nothing works. You can see code I attach, it's not complex but the problem makes me confused. I'm using Kotlin, and yes, it's for Android.
private fun generateKeyPair() {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
if (keyStore.containsAlias(keyAlias)) return
val keyGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE)
val builder = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(4096)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setRandomizedEncryptionRequired(true)
.setUserAuthenticationRequired(false)
keyGenerator.initialize(builder.build(), SecureRandom())
keyGenerator.generateKeyPair()
}
fun encryptApplicationKey(
data: String
) {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey())
val encryptResult = cipher.doFinal(data.toByteArray())
val encryptResultString = Base64.encodeToString(encryptResult, Base64.DEFAULT)
text_view_info.text = encryptResultString
}
fun decryptApplicationKey(data: String) {
val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey()) //it throw Caused by: android.security.KeyStoreException: Incompatible padding mode
val encryptedData = Base64.decode(data, Base64.DEFAULT)
val decryptResult = cipher.doFinal(encryptedData)
val decryptResultString = String(decryptResult)
text_view_info.text = decryptResultString
}
fun getPublicKey(): PublicKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
val publicKey = keyStore.getCertificate(keyAlias).publicKey
val unrestrictedPublicKey: PublicKey =
KeyFactory.getInstance(publicKey.algorithm).generatePublic(
X509EncodedKeySpec(publicKey.encoded)
)
return unrestrictedPublicKey
}
fun getPrivateKey(): PrivateKey {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null)
return keyStore.getKey(keyAlias, null) as PrivateKey
}
When I debugged the getPrivateKey() function, it's return AndroidKeyStoreRSAPrivateKey object. I don't know if the problem related to the usage of PKCS1Padding, but when I'm using OAEP padding, there's no crash when run it.
Does the device affect the result? I have commented where the crash happens.
I'm trying to generate a RSA key pair for this pruposes:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT or PURPOSE_SIGN or PURPOSE_VERIFY
And this is my key generation code:
val generator = KeyPairGenerator.getInstance(
KEY_ALGORITHM,
ANDROID_KEY_STORE
)
generator?.initialize(
KeyGenParameterSpec.Builder(
alias,
purposes
)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build()
)
generator?.generateKeyPair()
However when I use this purposes the decrypt does not work and throws an exception:
InvalidKeyException: "keystore operation failed. Incompatible purpose."
But when I try just encryption and decryption, keystore does encrypt and also decrypt perfectly. this is the purposes that I use for:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT
These are the methods for encryption and decryption:
private fun decrypt(cipherText: String, alias: String): String? {
return try {
val privateKeyEntry = getGeneratedPrivateKey(alias)
val output = Cipher.getInstance(
"$KEY_ALGORITHM_RSA/$BLOCK_MODE_ECB/$ENCRYPTION_PADDING_RSA_PKCS1"
// ANDROID_OPEN_SSL
)
output.init(Cipher.DECRYPT_MODE, privateKeyEntry?.privateKey)
val inputStream = ByteArrayInputStream(
android.util.Base64.decode(
cipherText,
android.util.Base64.NO_WRAP
)
)
val res = String(CipherInputStream(inputStream, output).readBytes(), Charsets.UTF_8)
res
} catch (e: Exception) {
e.printStackTrace()
null
}
}
private fun encrypt(plainText: String, alias: String): String? {
return try {
val publicKey = getGeneratedPublicKey(alias) ?: setupKeyPair(
alias,
PURPOSE_ENCRYPT or PURPOSE_DECRYPT
)?.public
val cipher = Cipher.getInstance(
"$KEY_ALGORITHM_RSA/$BLOCK_MODE_ECB/$ENCRYPTION_PADDING_RSA_PKCS1"
// ANDROID_OPEN_SSL
)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
val outputStream = ByteArrayOutputStream()
val cipherOutputStream = CipherOutputStream(outputStream, cipher)
cipherOutputStream.write(plainText.toByteArray(charset("UTF-8")))
cipherOutputStream.close()
val encryptedText = outputStream.toByteArray()
outputStream.close()
val res = android.util.Base64.encodeToString(encryptedText, android.util.Base64.NO_WRAP)
res
} catch (e: Exception) {
e.printStackTrace()
null
}
}
So what is the problem? How can I make a multipurpose keypair in Android keystore?
Probably you have your own custom variables for purpose keys
You should use KeyProperties in this line:
val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT or PURPOSE_SIGN or PURPOSE_VERIFY
like this:
val purposes = KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
I'm trying to encrypt a arbitrary String using a KeyPair generated by an instance of java.security.KeyPairGenerator. Unfortunately after encrypting and decrypting the String with the generated KeyPair the result is incorrect.
here is how I go about doing this:
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
fun encryptUsingKey(publicKey: PublicKey, bytes: ByteArray): ByteArray {
val inCipher = Cipher.getInstance("RSA/NONE/NoPadding")
inCipher.init(Cipher.ENCRYPT_MODE, publicKey)
return inCipher.doFinal(bytes)
}
fun decryptUsingKey(privateKey: PrivateKey, bytes: ByteArray): ByteArray {
val inCipher = Cipher.getInstance("RSA/NONE/NoPadding")
inCipher.init(Cipher.DECRYPT_MODE, privateKey)
return inCipher.doFinal(bytes)
}
fun getKey(): KeyStore.Entry {
val containsAlias = ks.containsAlias(alias)
if (!containsAlias) {
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec =
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setRandomizedEncryptionRequired(false)
.build()
kpg.initialize(parameterSpec)
val kp = kpg.generateKeyPair()
}
return ks.getEntry(alias, null)
}
My encryption/decryption test looks like this:
fun testEncryptionDecryption() {
val entry = getKey()
if (entry is KeyStore.PrivateKeyEntry) {
val privateKey = entry.privateKey
val certificate = entry.certificate
val publicKey = certificate.publicKey
val testKey = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
val encrypted = service.encryptUsingKey(publicKey, Base64.decodeFromString(testKey))
val decrypted = service.decryptUsingKey(privateKey, encrypted)
assertEquals(testKey, Base64.encodeToString(decrypted))
}
}
Unfortunately the result looks like this:
org.junit.ComparisonFailure: expected:<[0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF]> but was:<[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBdNdt-Oeu_PQAQgxBQ]>
Can someone enlighten me to what's going on here? Where do all these A's come from? Am I using the keys incorrectly?
As suspected it was incorrect configuration. The following works:
val inCipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec =
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT
)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setDigests(KeyProperties.DIGEST_SHA1)
.build()
kpg.initialize(parameterSpec)
I want to secure the password used for Greendao db with SQLCipher. I am using a keystore to genrate a key and using that key encrypting a password getting from the server. I want to use encrypted data as a password for database.
The problem is cipher every time generating different encrypted data. (I know it is working as expected). Is there any way to generate the encrypted data similar every time? I am using following code for encryption: -
private fun generateSecureKey() {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(BuildConfig.APPLICATION_ID,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
}
private fun getSecureKeyFromKeyStore(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
return if (keyStore.containsAlias(BuildConfig.APPLICATION_ID)) {
val secretKeyEntry = keyStore.getEntry(BuildConfig.APPLICATION_ID, null) as KeyStore.SecretKeyEntry
secretKeyEntry.secretKey
} else {
generateSecureKey()
val secretKeyEntry = keyStore.getEntry(BuildConfig.APPLICATION_ID, null) as KeyStore.SecretKeyEntry
secretKeyEntry.secretKey
}
}
private fun encryptData(secureData: String): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getSecureKeyFromKeyStore())
val bytes = cipher.doFinal(secureData.toByteArray())
return Base64.encodeToString(bytes, Base64.DEFAULT)
}