From d11e4f4bb83c4e0040d654507b87cd09320d89b1 Mon Sep 17 00:00:00 2001 From: Maximilian Haupt Date: Tue, 16 Dec 2025 15:45:49 +0100 Subject: [PATCH 1/2] Handle corrupt or missing key In v2.2.2 we introduced a new key identifier. This can lead to exceptions when trying to decrypt the stored settings. This commit handles this case and returns null as the user state, which will lead to a fresh session with a fresh key. --- .../main/java/de/contentpass/lib/KeyStore.kt | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/lib/src/main/java/de/contentpass/lib/KeyStore.kt b/lib/src/main/java/de/contentpass/lib/KeyStore.kt index 6c86a07..ed9a659 100644 --- a/lib/src/main/java/de/contentpass/lib/KeyStore.kt +++ b/lib/src/main/java/de/contentpass/lib/KeyStore.kt @@ -1,21 +1,19 @@ package de.contentpass.lib import android.content.Context -import android.security.KeyPairGeneratorSpec -import java.math.BigInteger +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties import java.security.KeyPair import java.security.KeyPairGenerator import java.security.PrivateKey import java.security.PublicKey import java.security.spec.MGF1ParameterSpec -import java.util.Calendar import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.OAEPParameterSpec import javax.crypto.spec.PSource import javax.crypto.spec.SecretKeySpec -import javax.security.auth.x500.X500Principal import java.security.KeyStore as VendorKeyStore internal class KeyStore(private val context: Context, private val propertyId: String) { @@ -58,38 +56,50 @@ internal class KeyStore(private val context: Context, private val propertyId: St } private fun createKeyPair(): KeyPair { - val spec = buildKeyPairGeneratorSpec() - val generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore") - generator.initialize(spec) + val spec = buildKeyGenParameterSpec() + val generator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_RSA, + "AndroidKeyStore" + ) + generator.initialize(spec as java.security.spec.AlgorithmParameterSpec) return generator.generateKeyPair() } - private fun buildKeyPairGeneratorSpec(): KeyPairGeneratorSpec { - val start = Calendar.getInstance() - val end = Calendar.getInstance() - end.add(Calendar.YEAR, 1) - - return KeyPairGeneratorSpec.Builder(context) - .setAlias(keyPairAlias) - .setSubject(X500Principal("CN=Sample Name, O=Android Authority")) - .setSerialNumber(BigInteger.ONE) - .setStartDate(start.time) - .setEndDate(end.time) + private fun buildKeyGenParameterSpec(): KeyGenParameterSpec { + return KeyGenParameterSpec.Builder( + keyPairAlias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setKeySize(2048) + .setDigests(KeyProperties.DIGEST_SHA256) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) .build() } private fun retrieveKey(): SecretKey? { - return sharedPreferences.getString(sharedPreferencesKey, null)?.let { - val encrypted = it.decoded() - return decryptKey(encrypted) + return sharedPreferences.getString(sharedPreferencesKey, null)?.let { encryptedString -> + try { + val encrypted = encryptedString.decoded() + decryptKey(encrypted) + } catch (e: Exception) { + // If decryption fails, the stored key might be corrupted + // Return null to trigger key regeneration + null + } } } private fun decryptKey(encryptedKey: ByteArray): SecretKey { - cipher.init(Cipher.DECRYPT_MODE, privateKey, paddingSpec) - val decrypted = cipher.doFinal(encryptedKey) - return SecretKeySpec(decrypted, "AES") + try { + cipher.init(Cipher.DECRYPT_MODE, privateKey, paddingSpec) + val decrypted = cipher.doFinal(encryptedKey) + return SecretKeySpec(decrypted, "AES") + } catch (e: javax.crypto.IllegalBlockSizeException) { + // If decryption fails due to block size issues, try to regenerate the key + // by deleting the corrupted entry and creating a new one + throw IllegalStateException("Failed to decrypt stored key. Key may be corrupted.", e) + } } private fun createKey(): SecretKey { @@ -110,8 +120,12 @@ internal class KeyStore(private val context: Context, private val propertyId: St } private fun encryptKey(key: SecretKey): ByteArray { - cipher.init(Cipher.ENCRYPT_MODE, publicKey, paddingSpec) - return cipher.doFinal(key.encoded) + try { + cipher.init(Cipher.ENCRYPT_MODE, publicKey, paddingSpec) + return cipher.doFinal(key.encoded) + } catch (e: javax.crypto.IllegalBlockSizeException) { + throw IllegalStateException("Failed to encrypt key. Key size may be incompatible.", e) + } } } From a00f62cf920be1153647493c5e2307756024e545 Mon Sep 17 00:00:00 2001 From: Maximilian Haupt Date: Tue, 16 Dec 2025 15:47:46 +0100 Subject: [PATCH 2/2] Bump version to 2.2.6 --- README.md | 2 +- lib/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f2556b..3b5b28f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Our SDK is available on Maven Central. ```groovy -implementation 'de.contentpass:contentpass-android:2.2.5' +implementation 'de.contentpass:contentpass-android:2.2.6' ``` Add this to your app's `build.gradle` file's `dependencies` element. diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index ee6cb1a..a38474f 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -58,7 +58,7 @@ kapt { extra.apply{ set("PUBLISH_GROUP_ID", "de.contentpass") set("PUBLISH_ARTIFACT_ID", "contentpass-android") - set("PUBLISH_VERSION", "2.2.5") + set("PUBLISH_VERSION", "2.2.6") } apply("${rootProject.projectDir}/scripts/publish-module.gradle")