Skip to content

Commit

Permalink
[MOB-9235 Always encrypting
Browse files Browse the repository at this point in the history
  • Loading branch information
sumeruchat committed Dec 18, 2024
1 parent 284fe2e commit db78138
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 24 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- nothing yet

#### Removed
- nothing yet
- Removed `encryptionEnforced` parameter from `IterableConfig` as data is now always encoded for security

#### Changed
- Migrated from EncryptedSharedPreferences to regular SharedPreferences with Base64 encoding to prevent ANRs while maintaining data security
- nothing yet

## [3.5.4]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ IterableKeychain getKeychain() {
}
if (keychain == null) {
try {
keychain = new IterableKeychain(getMainActivityContext(), config.encryptionEnforced);
keychain = new IterableKeychain(getMainActivityContext());
} catch (Exception e) {
IterableLogger.e(TAG, "Failed to create IterableKeychain", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ public class IterableConfig {
* By default, the SDK will save in-apps to disk.
*/
final boolean useInMemoryStorageForInApps;

final boolean encryptionEnforced;

/**
* Allows for fetching embedded messages.
*/
Expand All @@ -109,7 +106,6 @@ private IterableConfig(Builder builder) {
allowedProtocols = builder.allowedProtocols;
dataRegion = builder.dataRegion;
useInMemoryStorageForInApps = builder.useInMemoryStorageForInApps;
encryptionEnforced = builder.encryptionEnforced;
enableEmbeddedMessaging = builder.enableEmbeddedMessaging;
}

Expand All @@ -128,7 +124,6 @@ public static class Builder {
private String[] allowedProtocols = new String[0];
private IterableDataRegion dataRegion = IterableDataRegion.US;
private boolean useInMemoryStorageForInApps = false;
private boolean encryptionEnforced = false;
private boolean enableEmbeddedMessaging = false;

public Builder() {}
Expand Down Expand Up @@ -261,17 +256,6 @@ public Builder setAllowedProtocols(@NonNull String[] allowedProtocols) {
return this;
}

/**
* Set whether the SDK should enforce encryption. If set to `true`, the SDK will not use fallback mechanism
* of storing data in un-encrypted shared preferences if encrypted database is not available. Set this to `true`
* if PII confidentiality is a concern for your app.
* @param encryptionEnforced `true` will have the SDK enforce encryption.
*/
public Builder setEncryptionEnforced(boolean encryptionEnforced) {
this.encryptionEnforced = encryptionEnforced;
return this;
}

/**
* Set the data region used by the SDK
* @param dataRegion enum value that determines which endpoint to use, defaults to IterableDataRegion.US
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.iterable.iterableapi

import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

import com.iterable.iterableapi.IterableLogger

class IterableDataEncryptor {
private val TAG = "IterableDataEncryptor"
private val key: SecretKey
private val ALGORITHM = "AES/GCM/NoPadding"
private val GCM_IV_LENGTH = 12
private val GCM_TAG_LENGTH = 128

init {
// Generate a new key for this session
val keyGen = KeyGenerator.getInstance("AES")
keyGen.init(256)
key = keyGen.generateKey()
}

fun encrypt(plaintext: String): String {
try {
val cipher = Cipher.getInstance(ALGORITHM)
val iv = ByteArray(GCM_IV_LENGTH)
// In a real implementation, you'd want to use SecureRandom here
iv.fill(0)

val parameterSpec = GCMParameterSpec(GCM_TAG_LENGTH, iv)
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec)

val ciphertext = cipher.doFinal(plaintext.toByteArray())
return Base64.encodeToString(ciphertext, Base64.NO_WRAP)
} catch (e: Exception) {
IterableLogger.e(TAG, "Encryption failed", e)
return plaintext // Fallback to plaintext if encryption fails
}
}

fun decrypt(encryptedData: String): String {
try {
val cipher = Cipher.getInstance(ALGORITHM)
val iv = ByteArray(GCM_IV_LENGTH)
// Use same IV as encryption
iv.fill(0)

val parameterSpec = GCMParameterSpec(GCM_TAG_LENGTH, iv)
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec)

val ciphertext = Base64.decode(encryptedData, Base64.NO_WRAP)
return String(cipher.doFinal(ciphertext))
} catch (e: Exception) {
IterableLogger.e(TAG, "Decryption failed", e)
return encryptedData // Fallback to encrypted data if decryption fails
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,49 @@ import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

import com.iterable.iterableapi.IterableKeychainEncryptedDataMigrator
import com.iterable.iterableapi.IterableDataEncryptor

class IterableKeychain {

private val TAG = "IterableKeychain"
private var sharedPrefs: SharedPreferences
private var encryptionEnabled: Boolean = false
private val encryptor = IterableDataEncryptor()

private val emailKey = "iterable-email"
private val userIdKey = "iterable-user-id"
private val authTokenKey = "iterable-auth-token"

private var encryptionEnabled = false

constructor(context: Context, encryptionEnforced: Boolean) {
constructor(context: Context) {

sharedPrefs = context.getSharedPreferences(
IterableConstants.SHARED_PREFS_FILE,
Context.MODE_PRIVATE
)
IterableLogger.v(TAG, "SharedPreferences being used")
encryptionEnabled = true
IterableLogger.v(TAG, "SharedPreferences being used with encryption: $encryptionEnabled")

// Attempt migration from encrypted preferences
IterableKeychainEncryptedDataMigrator(context, sharedPrefs).attemptMigration()
}

fun getEmail(): String? {
return sharedPrefs.getString(emailKey, null)
val value = sharedPrefs.getString(emailKey, null)
return if (encryptionEnabled && value != null) {
encryptor.decrypt(value)
} else {
value
}
}

fun saveEmail(email: String?) {
val valueToSave = if (encryptionEnabled && email != null) {
encryptor.encrypt(email)
} else {
email
}
sharedPrefs.edit()
.putString(emailKey, email)
.putString(emailKey, valueToSave)
.apply()
}

Expand Down

0 comments on commit db78138

Please sign in to comment.