what is the best practice to create only one keypair for RSA encryption in all the android app cycle ?
I want to create the public key and the private key only once then use it whenever i want.
Yes you should create it one time with your alias key and next time before create new keystore object first you should check the same alias is already exist in keystore or not. If the alias is not exist in keystore then you should create new object. You can also check with the below code.
class AndroidKeyStore {
companion object {
private val CIPHER_TYPE = "RSA/ECB/PKCS1Padding"
private val CIPHER_PROVIDER = "AndroidOpenSSL"
private var keyStore: KeyStore? = null
const val KEY_ALIAS = "Keyalaisasfd"
}
init {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore?.load(null)
generateKey()
} catch (ex: Exception) {
}
}
#Throws(Exception::class)
private fun generateKey() {
// Create new key if needed
if (keyStore != null) {
if (!keyStore!!.containsAlias(KEY_ALIAS)) {
val kpg: KeyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build()
kpg.initialize(parameterSpec)
kpg.generateKeyPair()
}
}
}
}
Related
so in my app i encrypt my data with AES key and encrypt that key with an RSA pair.
i then save that pair to the KeyStore.
when retrieving the private key from the KeyStore i need to format it to PKCS8
how ever i cannot format the private key because you cannot access private key encoded from key store.
is there a way to generate the pair in PKCS8 format? or maybe a workaround in order to format the private key without accessing the encoded?
generating the keys:
fun generateRSAKey(context: Context): KeyPair {
val keyPairGenerator = KeyPairGenerator.getInstance(
AppConst.RSA, "AndroidKeyStore"
)
val spec:AlgorithmParameterSpec
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M) {
spec = KeyGenParameterSpec.Builder(
"privateKey",
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setKeySize(1024)
.build()
}
else
{
val start: Calendar = Calendar.getInstance()
val end: Calendar = Calendar.getInstance()
end.add(Calendar.YEAR, 100)
spec = KeyPairGeneratorSpec.Builder(context)
.setAlias("privateKey")
.setSubject(X500Principal("CN=Sample Name, O=Android Authority"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.time)
.setEndDate(end.time)
.build()
}
keyPairGenerator.initialize(spec)
return keyPairGenerator.generateKeyPair()
}
getting the keys:
val keyStore=EncryptionUtil.getKeyStoreInstance()
keyStore.load(null)
val entry: KeyStore.Entry = keyStore.getEntry("privateKey", null)
val privateKey: PrivateKey = (entry as KeyStore.PrivateKeyEntry).privateKey
val decryptedAES = EncryptionUtil.decryptRSA(
android.util.Base64.decode(
aesKey,
android.util.Base64.DEFAULT
), KeyFactory.getInstance("RSA").generatePrivate(PKCS8EncodedKeySpec(privateKey.encoded))
)
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 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()
}
}
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)
}