AES Encryption in Kotlin - android

The Android docs give the following snippet for how to encrypt a message in AES:
val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv
I get this error when implementing this method:
Unresolved reference: Cipher
So it appears the 'Cipher' object isn't native, however I have no way of knowing how to import it by following the Android docs. How do I set up my project to be able to use 'Cipher'?

javax.crypto.Cipher is part of the JCE and should be available. Does an import javax.crypto.Cipher not work? Then maybe something is wrong with your environment.

I'm not sure if using that Cipher is necessary, and if the solution I'm providing is the best approach, but I was able to use AES for encryption and decryption using the following code for a text input, means a String:
ENCRYPTION
// text
val aesEncrypt: AESEncrypt = AESEncrypt()
val encryptedByteArray = aesEncrypt.encrypt(text)
val baos_text = ByteArrayOutputStream()
val oosText = ObjectOutputStream(baos_text)
oosText.writeObject(encryptedByteArray)
val encryptedText = String(android.util.Base64.encode(baos_text.toByteArray(), android.util.Base64.DEFAULT))
// key
val key = aesEncrypt.mySecretKey
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(key)
val keyAsString = String(android.util.Base64.encode(baos.toByteArray(), android.util.Base64.DEFAULT))
// initialisation vector
val iv = aesEncrypt.myInitializationVector
val baosIV = ByteArrayOutputStream()
val oosIV = ObjectOutputStream(baosIV)
oosIV.writeObject(iv)
val initialisationVector = String(android.util.Base64.encode(baosIV.toByteArray(), android.util.Base64.DEFAULT))
DECRYPTION
You must save the key, initialisation vector, and the encrypted text in order to decrypt it back.
val initialisationVector = ... // get from wherever you saved it, local db, firebase...
val bytesIV = android.util.Base64.decode(iv, android.util.Base64.DEFAULT)
val oisIV = ObjectInputStream(ByteArrayInputStream(bytesIV))
val initializationVectorIV = oisIV.readObject() as ByteArray
val encryptedText = ... // get
val bytesText = android.util.Base64.decode(encryptedText, android.util.Base64.DEFAULT)
val oisText = ObjectInputStream(ByteArrayInputStream(bytesText))
val textByteArray = oisText.readObject() as ByteArray
val key = ... // get your key
val bytesKey = android.util.Base64.decode(key, android.util.Base64.DEFAULT)
val oisKey = ObjectInputStream(ByteArrayInputStream(bytesKey))
val secretKeyObj = oisKey.readObject() as SecretKey
val aesDecrypt = AESDecrypt(secretKeyObj,initializationVectorIV)
val decryptedByteArray = aesDecrypt.decrypt(textByteArray)
val textAfterDecryption = decryptedByteArray.toString(charset("UTF-8"))
EDIT
AES helper class:
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
class AESEncrypt {
var mySecretKey: SecretKey? = null
var myInitializationVector: ByteArray? = null
fun encrypt(strToEncrypt: String): ByteArray {
val plainText = strToEncrypt.toByteArray(Charsets.UTF_8)
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key = keygen.generateKey()
mySecretKey = key
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val cipherText = cipher.doFinal(plainText)
myInitializationVector = cipher.iv
return cipherText
}
}
AES decrypt helper
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
class AESDecrypt(private val mySecretKey: SecretKey?, private val initializationVector: ByteArray?) {
fun decrypt(dataToDecrypt: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
val ivSpec = IvParameterSpec(initializationVector)
cipher.init(Cipher.DECRYPT_MODE, mySecretKey, ivSpec)
val cipherText = cipher.doFinal(dataToDecrypt)
return cipherText
}
}
Do tell if you still need any help :)

Related

RSA Decrytion Fail in Android

I am facing some problem in decryption RSA in android Using Kotlin
I dont know what is going wrong , Need some Help
Error Reads As input should be less than 128bytes and its occurring in decryption Function
Code -
val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(1024)
val keyPair = keyGen.generateKeyPair()
val privateKey = keyPair.private
val publicKey = keyPair.public
val encoder: java.util.Base64.Encoder = java.util.Base64.getEncoder()
val m = encoder.encodeToString(privateKey.encoded)
val l = encoder.encodeToString(publicKey.encoded)
Log.d("keys","$m andddddddddddddd $l")
val xyz = "abcdefgh"
fun encryption(data: String): String {
var encoded = ""
var encrypted: ByteArray? = null
val publicBytes: ByteArray? = Base64.decode(l, Base64.DEFAULT)
val keySpec: java.security.spec.X509EncodedKeySpec = java.security.spec.X509EncodedKeySpec(publicBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val pubKey = keyFactory.generatePublic(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.ENCRYPT_MODE, pubKey)
encrypted = cipher.doFinal(data.toByteArray())
Log.d("test","$encrypted")
encoded = Base64.encodeToString(encrypted, Base64.DEFAULT)
Log.d("final", encoded)
return encoded
}
val o = encryption(xyz)
val nom = o.length
Log.d("leng","$nom")
fun decryption(data: String) : String{
var decoded = ""
var decrypted: ByteArray? = null
val privateBytes: ByteArray? = Base64.decode(m, Base64.DEFAULT)
val keySpec:PKCS8EncodedKeySpec = PKCS8EncodedKeySpec(privateBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val prvKey = keyFactory.generatePrivate(keySpec)
val cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
cipher.init(Cipher.DECRYPT_MODE, prvKey)
decrypted = cipher.doFinal(o.toByteArray())
decoded = Base64.encodeToString(decrypted, Base64.DEFAULT)
Log.d("finald", "$decoded")
return decoded
}
decryption(o)

AES Encrypt/Decrypt SQL DB issue on decryption with Initialization Vector

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

KeyStoreException: Signature/MAC verification failed when trying to decrypt

I am trying to create a simple Kotlin object that wraps access to the app's shared preferences by encrypting content before saving it.
Encrypting seems to work OK but when I try to decrypt, I get an javax.crypto.AEADBadTagException which stems from an android.security.KeyStoreException with a message of "Signature/MAC verification failed".
I have tried debugging to see what's the underlying issue but I can't find anything. No search has given me any clue. I seem to follow a few guides to the letter without success.
private val context: Context?
get() = this.application?.applicationContext
private var application: Application? = null
private val transformation = "AES/GCM/NoPadding"
private val androidKeyStore = "AndroidKeyStore"
private val ivPrefix = "_iv"
private val keyStore by lazy { this.createKeyStore() }
private fun createKeyStore(): KeyStore {
val keyStore = KeyStore.getInstance(this.androidKeyStore)
keyStore.load(null)
return keyStore
}
private fun createSecretKey(alias: String): SecretKey {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, this.androidKeyStore)
keyGenerator.init(
KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
return keyGenerator.generateKey()
}
private fun getSecretKey(alias: String): SecretKey {
return if (this.keyStore.containsAlias(alias)) {
(this.keyStore.getEntry(alias, null) as KeyStore.SecretKeyEntry).secretKey
} else {
this.createSecretKey(alias)
}
}
private fun removeSecretKey(alias: String) {
this.keyStore.deleteEntry(alias)
}
private fun encryptText(alias: String, textToEncrypt: String): String {
val cipher = Cipher.getInstance(this.transformation)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(alias))
val ivString = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
this.storeInSharedPrefs(alias + this.ivPrefix, ivString)
val byteArray = cipher.doFinal(textToEncrypt.toByteArray(charset("UTF-8")))
return String(byteArray)
}
private fun decryptText(alias: String, textToDecrypt: String): String? {
val ivString = this.retrieveFromSharedPrefs(alias + this.ivPrefix) ?: return null
val iv = Base64.decode(ivString, Base64.DEFAULT)
val spec = GCMParameterSpec(iv.count() * 8, iv)
val cipher = Cipher.getInstance(this.transformation)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(alias), spec)
try {
val byteArray = cipher.doFinal(textToDecrypt.toByteArray(charset("UTF-8")))
return String(byteArray)
} catch (e: Exception) {
e.printStackTrace()
return null
}
}
private fun storeInSharedPrefs(key: String, value: String) {
this.context?.let {
PreferenceManager.getDefaultSharedPreferences(it).edit()?.putString(key, value)?.apply()
}
}
private fun retrieveFromSharedPrefs(key: String): String? {
val validContext = this.context ?: return null
return PreferenceManager.getDefaultSharedPreferences(validContext).getString(key, null)
}
Can anyone point me in the right direction ?
I had similar issue. It was all about android:allowBackup="true".
Issue
This issue will occur while uninstalling the app and then re-installing it again. KeyStore will get cleared on uninstall but the preferences not getting removed, so will end up trying to decrypt with a new key thus exception thrown.
Solution
Try disabling android:allowBackup as follows:
<application android:allowBackup="false" ... >
I encountered the same exception/issue 'android.security.KeyStoreException: Signature/MAC verification failed' on Cipher encryption 'AES/GCM/NoPadding'.
On my end, what helped to resolve this issue is to create a byte array holder first, with size that is obtained by calling Cipher.getOutputSize(int inputLen), then calling the doFinal overload Cipher.doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset) to set the ciphertext in your byte array holder.
private var iv: ByteArray? = null
fun doEncryptionOperation() {
val keyStore = KeyStore.getInstance(PROVIDER_ANDROID_KEYSTORE).apply {
load(null)
}
// Assumption: key with alias 'secret_key' has already been stored
val entry = keyStore.getEntry("secret_key", null)
val secretKeyEntry = entry as KeyStore.SecretKeyEntry
val key secretKeyEntry.secretKey
val plainText = "Sample plain text"
val cipherText = encryptSymmetric(key, plainText.toByteArray())
val decrypted = decryptSymmetric(key, cipherText)
val decryptedStr = String(decrypted)
val same = decryptedStr == plainText
Log.d("SampleTag", "Is plaintext same from decrypted text? $same")
}
fun encryptSymmetric(key: SecretKey, plainText: ByteArray): ByteArray? {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
iv = cipher.iv
val len = plainText.size
val outLen = cipher.getOutputSize(len) // get expected cipher output size
val result = ByteArray(outLen) // create byte array with outLen
cipher.doFinal(plainText, 0, len, result,0) // doFinal passing plaintext data and result array
return result
}
fun decryptSymmetric(key: SecretKey, cipherText: ByteArray): ByteArray? {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val tagLen = 128 // default GCM tag length
cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(tagLen,iv))
cipher.update(input.data)
val result = cipher.doFinal()
return result
}
Additionally, using AEAD, don't forget to call Cipher.updateAAD() in ENCRYPT_MODE, and set the same AEAD tag in the DECRYPT_MODE. Otherwise, you will encounter the same javax.crypto.AEADBadTagException.
When you change your authentiation tag length from iv.count() to 128 it will work.
I had a similar problem. I had an application where the admin and an ordinary user could log in and both of them had a remember me option. So, when the user previously pressed the remember me option, the program needs to fetch the encrypted password, decrypt it, and put it in the input field.
I was storing both encrypted passwords with their initialization vectors in the SharedPreferences file but when I was trying to decrypt them via Cipher (The secret key was stored in the AndroidKeyStore with the same alias for the secret key) it was decrypting one password but was giving me the same error as yours when I was decrypting another password.
Then, I used 2 different aliases for these 2 passwords when I was encrypting and decrypting them and the error is gone.
Github gist: Code example

BadPaddingException: pad block corrupted while trying to decode

I'm facing with BadPaddingException while I'm trying to decode InputStream. I was able to encode/decode OutputStream/InputStream while I'm using jackson but when I tried to do with Okio it throws BadPaddingException.
getEncodeStream() and getDecodeStream() methods was working fine with Jackson but it seems that its working different with Okio.
Output of encoded file = FxGOXOwOWzBGMa7+u+E3TvNTOjFv/vKKsSt+Q1+QsedtluVa6sULFhOImRO+pYQp43h/HsrssNm0
UpxcC2cvbM4+ix9nH5YUfCK0NJjzT2iR9tJG8tXTrLSCz/B/6WEQ
#Test
fun main() {
val password = "password"
val file1 = File("src/test/resources/okioTest")
if(!file1.exists())
file1.createNewFile()
//create output stream
val outputStream = FileOutputStream(file1)
val encodeStream = getEncodeStream(password, outputStream)
val sink = Okio.buffer(Okio.sink(encodeStream))
sink.write("something to test something to test something to test something to test something to test something to test".toByteArray())
sink.emit()
val inputStream = FileInputStream(file1)
val decodeStream = getDecodeStream(password, inputStream)
val source = Okio.buffer(Okio.source(decodeStream))
val result = source.readUtf8()
Timber.d(result)
}
fun getEncodeStream(keyString: String, stream: OutputStream): OutputStream {
val keySpec = getKey(keyString)
// IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
val iv = ByteArray(16)
Arrays.fill(iv, 0x00.toByte())
val ivParameterSpec = IvParameterSpec(iv)
// Cipher is not thread safe
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec)
val base64stream = Base64OutputStream(stream, Base64.DEFAULT)
// Log.d("jacek", "Encrypted: $stringToEncode -> $encrypedValue")
return CipherOutputStream(base64stream, cipher)
}
fun getDecodeStream(password: String, stream: InputStream): InputStream {
val key = getKey(password)
val iv = ByteArray(16)
Arrays.fill(iv, 0x00.toByte())
val ivParameterSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec)
val base64stream = Base64InputStream(stream, Base64.DEFAULT)
return CipherInputStream(base64stream, cipher)
}
#Throws(UnsupportedEncodingException::class)
fun getKey(password: String): SecretKeySpec {
// You can change it to 128 if you wish
val keyLength = 256
val keyBytes = ByteArray(keyLength / 8)
// explicitly fill with zeros
Arrays.fill(keyBytes, 0x0.toByte())
// if password is shorter then key length, it will be zero-padded
// to key length
val passwordBytes = password.toByteArray(charset("UTF-8"))
val length = if (passwordBytes.size < keyBytes.size) passwordBytes.size else keyBytes.size
System.arraycopy(passwordBytes, 0, keyBytes, 0, length)
return SecretKeySpec(keyBytes, "AES")
}

How to construct private key from generated previously ECDSA both encoded key pair?

Having generated the private key like this:
fun getKeyPair(): Pair<ByteArray, ByteArray> {
Security.addProvider(provider)
val generator = KeyPairGenerator.getInstance("ECDSA")
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1")
generator.initialize(ecSpec)
val keyPair = generator.generateKeyPair()
val publicKey = keyPair.public as ECPublicKey
val privateKey = keyPair.private
return Pair(publicKey.q.getEncoded(true), privateKey.getEncoded())
}
The public key can be reconstructed again like this:
Security.addProvider(...spongy castle provider)
val ecSpecs = ECNamedCurveTable.getParameterSpec("secp256r1")
val q = ecSpecs.curve.decodePoint(publicKeyEncoded)
val pubSpec = ECPublicKeySpec(q, ecSpecs)
val keyFactory = KeyFactory.getInstance("ECDSA")
val generatedPublic = keyFactory.generatePublic(pubSpec)
How it is possible to reconstruct private key from bytes also along with this?
UPDATE:
This code works well in actual app but it doesnt in JUnit testing:
val keyFactory = KeyFactory.getInstance("ECDSA")
val privSpec = PKCS8EncodedKeySpec(privateEncoded)
val generatedPrivate = keyFactory.generatePrivate(privSpec)
In JUnit test I am getting this error:
java.security.spec.InvalidKeySpecException: encoded key spec not recognised
My private key as encoded bytes has 150 bytes size.
Since the key is encoded using the standard Key.getEncoded(), the following standard solution should work:
val keyFactory = KeyFactory.getInstance("EC")
val privSpec = PKCS8EncodedKeySpec(privateEncoded)
val generatedPrivate = keyFactory.generatePrivate(privSpec)
The encoded key should contain all the required information to rebuild the private key without specifying additional parameters like you need to do for the reduced public key.

Categories

Resources