@@ -11,37 +11,26 @@ import javax.crypto.spec.GCMParameterSpec
1111import android.os.Build
1212import java.security.KeyStore.PasswordProtection
1313import androidx.annotation.VisibleForTesting
14- import java.security.SecureRandom
15- import javax.crypto.spec.IvParameterSpec
1614import android.annotation.TargetApi
1715
1816class IterableDataEncryptor {
1917 companion object {
2018 private const val TAG = " IterableDataEncryptor"
2119 private const val ANDROID_KEYSTORE = " AndroidKeyStore"
22- private const val TRANSFORMATION_MODERN = " AES/GCM/NoPadding"
23- private const val TRANSFORMATION_LEGACY = " AES/CBC/PKCS5Padding"
20+ private const val TRANSFORMATION = " AES/GCM/NoPadding"
2421 private const val ITERABLE_KEY_ALIAS = " iterable_encryption_key"
2522 private const val GCM_TAG_LENGTH = 128
26- private const val GCM_IV_LENGTH = 12
27- private const val CBC_IV_LENGTH = 16
28- private val TEST_KEYSTORE_PASSWORD = " test_password" .toCharArray()
23+ private val FALLBACK_KEYSTORE_PASSWORD = " test_password" .toCharArray()
2924
3025 private val keyStore: KeyStore by lazy {
31- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .JELLY_BEAN_MR2 ) {
32- try {
33- KeyStore .getInstance(ANDROID_KEYSTORE ).apply {
34- load(null )
35- }
36- } catch (e: Exception ) {
37- IterableLogger .e(TAG , " Failed to initialize AndroidKeyStore" , e)
38- KeyStore .getInstance(" PKCS12" ).apply {
39- load(null , TEST_KEYSTORE_PASSWORD )
40- }
26+ try {
27+ KeyStore .getInstance(ANDROID_KEYSTORE ).apply {
28+ load(null )
4129 }
42- } else {
30+ } catch (e: Exception ) {
31+ IterableLogger .e(TAG , " Failed to initialize AndroidKeyStore" , e)
4332 KeyStore .getInstance(" PKCS12" ).apply {
44- load(null , TEST_KEYSTORE_PASSWORD )
33+ load(null , FALLBACK_KEYSTORE_PASSWORD )
4534 }
4635 }
4736 }
@@ -83,8 +72,8 @@ class IterableDataEncryptor {
8372 ITERABLE_KEY_ALIAS ,
8473 KeyProperties .PURPOSE_ENCRYPT or KeyProperties .PURPOSE_DECRYPT
8574 )
86- .setBlockModes(KeyProperties .BLOCK_MODE_GCM , KeyProperties . BLOCK_MODE_CBC )
87- .setEncryptionPaddings(KeyProperties .ENCRYPTION_PADDING_NONE , KeyProperties . ENCRYPTION_PADDING_PKCS7 )
75+ .setBlockModes(KeyProperties .BLOCK_MODE_GCM )
76+ .setEncryptionPaddings(KeyProperties .ENCRYPTION_PADDING_NONE )
8877 .build()
8978
9079 keyGenerator.init (keySpec)
@@ -106,7 +95,7 @@ class IterableDataEncryptor {
10695
10796 val keyEntry = KeyStore .SecretKeyEntry (secretKey)
10897 val protParam = if (keyStore.type == " PKCS12" ) {
109- PasswordProtection (TEST_KEYSTORE_PASSWORD )
98+ PasswordProtection (FALLBACK_KEYSTORE_PASSWORD )
11099 } else {
111100 null
112101 }
@@ -115,7 +104,7 @@ class IterableDataEncryptor {
115104
116105 private fun getKey (): SecretKey {
117106 val protParam = if (keyStore.type == " PKCS12" ) {
118- PasswordProtection (TEST_KEYSTORE_PASSWORD )
107+ PasswordProtection (FALLBACK_KEYSTORE_PASSWORD )
119108 } else {
120109 null
121110 }
@@ -127,18 +116,17 @@ class IterableDataEncryptor {
127116
128117 try {
129118 val data = value.toByteArray(Charsets .UTF_8 )
130- val encryptedData = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .KITKAT ) {
131- encryptModern(data)
132- } else {
133- encryptLegacy(data)
134- }
135119
136- // Combine isModern flag, IV length, IV, and encrypted data
137- val combined = ByteArray (1 + 1 + encryptedData.iv.size + encryptedData.data.size)
138- combined[0 ] = if (encryptedData.isModernEncryption) 1 else 0
139- combined[1 ] = encryptedData.iv.size.toByte() // Store IV length
140- System .arraycopy(encryptedData.iv, 0 , combined, 2 , encryptedData.iv.size)
141- System .arraycopy(encryptedData.data, 0 , combined, 2 + encryptedData.iv.size, encryptedData.data.size)
120+ val cipher = Cipher .getInstance(TRANSFORMATION )
121+ cipher.init (Cipher .ENCRYPT_MODE , getKey())
122+ val iv = cipher.iv
123+ val encrypted = cipher.doFinal(data)
124+
125+ val combined = ByteArray (1 + 1 + iv.size + encrypted.size)
126+ combined[0 ] = 1 // GCM flag (kept for format compatibility)
127+ combined[1 ] = iv.size.toByte()
128+ System .arraycopy(iv, 0 , combined, 2 , iv.size)
129+ System .arraycopy(encrypted, 0 , combined, 2 + iv.size, encrypted.size)
142130
143131 return Base64 .encodeToString(combined, Base64 .NO_WRAP )
144132 } catch (e: Exception ) {
@@ -152,91 +140,25 @@ class IterableDataEncryptor {
152140
153141 try {
154142 val combined = Base64 .decode(value, Base64 .NO_WRAP )
155-
156- // Extract components
157- val isModern = combined[0 ] == 1 .toByte()
143+
158144 val ivLength = combined[1 ].toInt()
159145 val iv = combined.copyOfRange(2 , 2 + ivLength)
160146 val encrypted = combined.copyOfRange(2 + ivLength, combined.size)
161147
162- val encryptedData = EncryptedData (encrypted, iv, isModern)
163-
164- // If it's modern encryption and we're on an old device, fail fast
165- if (isModern && Build .VERSION .SDK_INT < Build .VERSION_CODES .KITKAT ) {
166- throw DecryptionException (" Modern encryption cannot be decrypted on legacy devices" )
167- }
168-
169- // Use the appropriate decryption method
170- val decrypted = if (isModern) {
171- decryptModern(encryptedData)
172- } else {
173- decryptLegacy(encryptedData)
174- }
148+ val cipher = Cipher .getInstance(TRANSFORMATION )
149+ val spec = GCMParameterSpec (GCM_TAG_LENGTH , iv)
150+ cipher.init (Cipher .DECRYPT_MODE , getKey(), spec)
151+ val decrypted = cipher.doFinal(encrypted)
175152
176153 return String (decrypted, Charsets .UTF_8 )
177154 } catch (e: DecryptionException ) {
178- // Re-throw DecryptionException directly
179155 throw e
180156 } catch (e: Exception ) {
181157 IterableLogger .e(TAG , " Decryption failed" , e)
182158 throw DecryptionException (" Failed to decrypt data" , e)
183159 }
184160 }
185161
186- @TargetApi(Build .VERSION_CODES .KITKAT )
187- private fun encryptModern (data : ByteArray ): EncryptedData {
188- if (Build .VERSION .SDK_INT < Build .VERSION_CODES .KITKAT ) {
189- return encryptLegacy(data)
190- }
191-
192- val cipher = Cipher .getInstance(TRANSFORMATION_MODERN )
193- cipher.init (Cipher .ENCRYPT_MODE , getKey())
194- val iv = cipher.iv
195- val encrypted = cipher.doFinal(data)
196- return EncryptedData (encrypted, iv, true )
197- }
198-
199- private fun encryptLegacy (data : ByteArray ): EncryptedData {
200- val cipher = Cipher .getInstance(TRANSFORMATION_LEGACY )
201- val iv = generateIV(isModern = false )
202- val spec = IvParameterSpec (iv)
203- cipher.init (Cipher .ENCRYPT_MODE , getKey(), spec)
204- val encrypted = cipher.doFinal(data)
205- return EncryptedData (encrypted, iv, false )
206- }
207-
208- @TargetApi(Build .VERSION_CODES .KITKAT )
209- private fun decryptModern (encryptedData : EncryptedData ): ByteArray {
210- if (Build .VERSION .SDK_INT < Build .VERSION_CODES .KITKAT ) {
211- throw DecryptionException (" Cannot decrypt modern encryption on legacy device" )
212- }
213-
214- val cipher = Cipher .getInstance(TRANSFORMATION_MODERN )
215- val spec = GCMParameterSpec (GCM_TAG_LENGTH , encryptedData.iv)
216- cipher.init (Cipher .DECRYPT_MODE , getKey(), spec)
217- return cipher.doFinal(encryptedData.data)
218- }
219-
220- private fun decryptLegacy (encryptedData : EncryptedData ): ByteArray {
221- val cipher = Cipher .getInstance(TRANSFORMATION_LEGACY )
222- val spec = IvParameterSpec (encryptedData.iv)
223- cipher.init (Cipher .DECRYPT_MODE , getKey(), spec)
224- return cipher.doFinal(encryptedData.data)
225- }
226-
227- private fun generateIV (isModern : Boolean = false): ByteArray {
228- val length = if (isModern) GCM_IV_LENGTH else CBC_IV_LENGTH
229- val iv = ByteArray (length)
230- SecureRandom ().nextBytes(iv)
231- return iv
232- }
233-
234- data class EncryptedData (
235- val data : ByteArray ,
236- val iv : ByteArray ,
237- val isModernEncryption : Boolean
238- )
239-
240162 class DecryptionException (message : String , cause : Throwable ? = null ) : Exception(message, cause)
241163
242164 fun resetKeys () {
0 commit comments