I am trying to implement biometric authentication for encryption and decryption of a JWToken. The token is received and encrypted after user authenticated at the server. The encryption of the token should be permitted without biometric user authorization at the device. Only decryption operations should require biometric device authorization. This is why I use an asymmetric key for this. The token I receive however is around 1000 bytes and hence cannot be encrypted with asymmetric key. This is why I create a symmetric key for decryption and encryption operations and wrap and unwrap this key with the authorization protected asymmetric key. However, trying to wrap the symmetric key with the asymmetric public key throws InvalidKeyException with message: javax.crypto.BadPaddingException. Trying to wrap the symmetric key with the asymmetric private key also throws InvalidKeyException this time with the brief message: Failed to unwrap key. When i setUserAuthenticationRequired to false the asymmetric private key works just fine for wrapping. What am I doing wrong here?
class EncryptionService(
private val wrappedKeyStoreSource: WrappedKeyStore2
) {
companion object {
const val MASTER_KEY = "master_key"
const val ALGORITHM_AES = "AES"
const val ENCRYPTION_KEY = "encryption_key"
const val TRANSFORMATION_ASYMMETRIC = "RSA/ECB/PKCS1Padding"
const val TRANSFORMATION_SYMMETRIC = "AES/CBC/PKCS7Padding"
}
private val keyStore: KeyStore = createAndroidKeyStore()
init {
createDefaultSymmetricKey()
}
fun encrypt(data: String): String =
encryptWithSymmetricKey(data)
private fun createDefaultSymmetricKey() {
val symmetricKey = generateSymmetricKey()
val masterKey = createAsymmetricKey(MASTER_KEY)
val cipher: Cipher = Cipher.getInstance(TRANSFORMATION_ASYMMETRIC)
cipher.init(Cipher.WRAP_MODE, masterKey.public)
val encryptedSymmetricKey = cipher.wrap(symmetricKey)
wrappedKeyStoreSource.saveKey(ENCRYPTION_KEY, Base64.encodeToString(encryptedSymmetricKey, Base64.DEFAULT) )
}
//encrypt without user authorization
private fun encryptWithSymmetricKey(data: String): String {
val masterKey = getAsymmetricKeyPair(MASTER_KEY)
val encryptionKey = wrappedKeyStoreSource.getKey(ENCRYPTION_KEY)
val unwrapCipher: Cipher = Cipher.getInstance(TRANSFORMATION_ASYMMETRIC)
unwrapCipher.init(Cipher.UNWRAP_MODE, masterKey?.public)
val encryptedKeyData = Base64.decode(encryptionKey, Base64.DEFAULT)
//this line throws InvalidKeyException
//unwrap with public key throws InvalidKeyException with message: javax.crypto.BadPaddingException.
//unwrap with private key throws InvalidKeyException if setUserAuthenticationRequired on this
// key is set to true with message: Failed to unwrap key (maybe due to UserNotAuthenticatedException?)
val symmetricKey = unwrapCipher.unwrap(encryptedKeyData, ALGORITHM_AES, Cipher.PUBLIC_KEY) as SecretKey
val encryptCipher: Cipher = Cipher.getInstance(TRANSFORMATION_SYMMETRIC)
encryptCipher.init(Cipher.ENCRYPT_MODE, symmetricKey)
val encryptedString = encryptCipher.doFinal(data.toByteArray())
return Base64.encodeToString(encryptedString, Base64.DEFAULT)
}
private fun createAsymmetricKey(alias: String): KeyPair {
val generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore")
val builder = KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
generator.initialize(builder.build())
return generator.generateKeyPair()
}
private fun generateSymmetricKey(): SecretKey {
val keyGenerator = KeyGenerator.getInstance("AES")
return keyGenerator.generateKey()
}
private fun getAsymmetricKeyPair(alias: String): KeyPair? {
val privateKey = keyStore.getKey(alias, null) as PrivateKey?
val publicKey = keyStore.getCertificate(alias)?.publicKey
return if (privateKey != null && publicKey != null) {
KeyPair(publicKey, privateKey)
} else {
null
}
}
private fun createAndroidKeyStore(): KeyStore {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
return keyStore
}
}
stackTrace: java.security.InvalidKeyException: javax.crypto.BadPaddingException:
error:0400006b:RSA routines:OPENSSL_internal:BLOCK_TYPE_IS_NOT_01
at com.android.org.conscrypt.OpenSSLCipherRSA.engineUnwrap(OpenSSLCipherRSA.java:373)
at javax.crypto.Cipher.unwrap(Cipher.java:2440)
at com.libencryption.data.EncryptionService.encryptWithSymmetricKey(EncryptionService.kt:58)
at com.libencryption.data.EncryptionService.encrypt(EncryptionService.kt:37)
when I switch to unwrap with private key as suggested below like so
unwrapCipher.init(Cipher.UNWRAP_MODE, masterKey?.private)
val encryptedKeyData = Base64.decode(encryptionKey, Base64.DEFAULT)
val symmetricKey = unwrapCipher.unwrap(encryptedKeyData, ALGORITHM_AES, Cipher.SECRET_KEY) as SecretKey
i get the following stacktrace
stackTrace: java.security.InvalidKeyException: Failed to unwrap key
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineUnwrap(AndroidKeyStoreCipherSpiBase.java:682)
at javax.crypto.Cipher.unwrap(Cipher.java:2440)
at com.libencryption.data.EncryptionService.encryptWithSymmetricKey(EncryptionService.kt:58)
at com.libencryption.data.EncryptionService.encrypt(EncryptionService.kt:37)
which I assume is caused because i setUserAuthenticationRequired to true since when I set it to false everything encrypts just fine. However as you can see from the stackstrace that exception is not logged. Is there any other reason why that fails like that with setUserAuthenticationRequired to true?