From c5f1474d650c55e30793c5d84286d6354a9992a8 Mon Sep 17 00:00:00 2001 From: Koen <koen@revodux.com> Date: Thu, 9 Nov 2023 15:56:32 +0000 Subject: [PATCH] Encryption changes. --- .../core/EncryptionProviderTests.kt | 19 ----- .../core/PEncryptionProviderV0Tests.kt | 44 +++++++++++ .../core/PEncryptionProviderV1Tests.kt | 44 +++++++++++ .../polycentric/core/PEncryptionProvider.kt | 7 ++ ...onProvider.kt => PEncryptionProviderV0.kt} | 11 +-- .../polycentric/core/PEncryptionProviderV1.kt | 76 +++++++++++++++++++ .../futo/polycentric/core/SqlLiteDbHelper.kt | 36 ++++++++- .../com/futo/polycentric/core/SqlLiteStore.kt | 7 +- 8 files changed, 215 insertions(+), 29 deletions(-) delete mode 100644 app/src/androidTest/java/com/futo/polycentric/core/EncryptionProviderTests.kt create mode 100644 app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV0Tests.kt create mode 100644 app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV1Tests.kt create mode 100644 app/src/main/java/com/futo/polycentric/core/PEncryptionProvider.kt rename app/src/main/java/com/futo/polycentric/core/{EncryptionProvider.kt => PEncryptionProviderV0.kt} (89%) create mode 100644 app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV1.kt diff --git a/app/src/androidTest/java/com/futo/polycentric/core/EncryptionProviderTests.kt b/app/src/androidTest/java/com/futo/polycentric/core/EncryptionProviderTests.kt deleted file mode 100644 index 5498d3d..0000000 --- a/app/src/androidTest/java/com/futo/polycentric/core/EncryptionProviderTests.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.futo.polycentric.core - -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Test - -class EncryptionProviderTests { - @Test - fun roundtripString() { - val input = "test" - assertEquals(input, EncryptionProvider.instance.decrypt(EncryptionProvider.instance.encrypt(input))) - } - - @Test - fun roundtripBytes() { - val input = byteArrayOf(1, 2, 3, 10) - assertArrayEquals(input, EncryptionProvider.instance.decrypt(EncryptionProvider.instance.encrypt(input))) - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV0Tests.kt b/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV0Tests.kt new file mode 100644 index 0000000..5fb0bf1 --- /dev/null +++ b/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV0Tests.kt @@ -0,0 +1,44 @@ +package com.futo.polycentric.core + +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class PEncryptionProviderV0Tests { + @Test + fun testEncryptDecrypt() { + val encryptionProvider = PEncryptionProviderV0.instance + val plaintext = "This is a test string." + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(plaintext) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertEquals(plaintext, decrypted) + } + + + @Test + fun testEncryptDecryptBytes() { + val encryptionProvider = PEncryptionProviderV0.instance + val bytes = "This is a test string.".toByteArray(); + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(bytes) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertArrayEquals(bytes, decrypted); + } + + private fun assertArrayEquals(a: ByteArray, b: ByteArray) { + assertEquals(a.size, b.size); + for(i in 0 until a.size) { + assertEquals(a[i], b[i]); + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV1Tests.kt b/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV1Tests.kt new file mode 100644 index 0000000..03e74da --- /dev/null +++ b/app/src/androidTest/java/com/futo/polycentric/core/PEncryptionProviderV1Tests.kt @@ -0,0 +1,44 @@ +package com.futo.polycentric.core + +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class PEncryptionProviderV1Tests { + @Test + fun testEncryptDecrypt() { + val encryptionProvider = PEncryptionProviderV1.instance + val plaintext = "This is a test string." + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(plaintext) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertEquals(plaintext, decrypted) + } + + + @Test + fun testEncryptDecryptBytes() { + val encryptionProvider = PEncryptionProviderV1.instance + val bytes = "This is a test string.".toByteArray(); + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(bytes) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertArrayEquals(bytes, decrypted); + } + + private fun assertArrayEquals(a: ByteArray, b: ByteArray) { + assertEquals(a.size, b.size); + for(i in 0 until a.size) { + assertEquals(a[i], b[i]); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/polycentric/core/PEncryptionProvider.kt b/app/src/main/java/com/futo/polycentric/core/PEncryptionProvider.kt new file mode 100644 index 0000000..ba62173 --- /dev/null +++ b/app/src/main/java/com/futo/polycentric/core/PEncryptionProvider.kt @@ -0,0 +1,7 @@ +package com.futo.polycentric.core + +class PEncryptionProvider { + companion object { + val instance: PEncryptionProviderV1 = PEncryptionProviderV1.instance; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/polycentric/core/EncryptionProvider.kt b/app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV0.kt similarity index 89% rename from app/src/main/java/com/futo/polycentric/core/EncryptionProvider.kt rename to app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV0.kt index 724fa03..41510b5 100644 --- a/app/src/main/java/com/futo/polycentric/core/EncryptionProvider.kt +++ b/app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV0.kt @@ -8,7 +8,7 @@ import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.GCMParameterSpec -class EncryptionProvider { +class PEncryptionProviderV0 { private val _keyStore: KeyStore private val secretKey: Key? get() = _keyStore.getKey(KEY_ALIAS, null) @@ -30,7 +30,7 @@ class EncryptionProvider { fun encrypt(decrypted: ByteArray): ByteArray { val c: Cipher = Cipher.getInstance(AES_MODE) - c.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(128, FIXED_IV)) + c.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH, FIXED_IV)) return c.doFinal(decrypted) } @@ -40,7 +40,7 @@ class EncryptionProvider { fun decrypt(encrypted: ByteArray): ByteArray { val c = Cipher.getInstance(AES_MODE) - c.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, FIXED_IV)) + c.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH, FIXED_IV)) return c.doFinal(encrypted) } @@ -49,12 +49,13 @@ class EncryptionProvider { } companion object { - val instance: EncryptionProvider = EncryptionProvider() + val instance: PEncryptionProviderV0 = PEncryptionProviderV0() private val FIXED_IV = byteArrayOf(12, 43, 127, 2, 99, 22, 6, 78, 24, 53, 8, 101) private const val AndroidKeyStore = "AndroidKeyStore" private const val KEY_ALIAS = "PolycentricCore_Key" private const val AES_MODE = "AES/GCM/NoPadding" - private val TAG = "EncryptionProvider" + private val TAG = "PEncryptionProviderV0" + private const val TAG_LENGTH = 128 } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV1.kt b/app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV1.kt new file mode 100644 index 0000000..6026445 --- /dev/null +++ b/app/src/main/java/com/futo/polycentric/core/PEncryptionProviderV1.kt @@ -0,0 +1,76 @@ +package com.futo.polycentric.core + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import java.security.Key +import java.security.KeyStore +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.spec.GCMParameterSpec + +class PEncryptionProviderV1 { + private val _keyStore: KeyStore + private val secretKey: Key? get() = _keyStore.getKey(KEY_ALIAS, null) + + constructor() { + _keyStore = KeyStore.getInstance(AndroidKeyStore) + _keyStore.load(null) + + if (!_keyStore.containsAlias(KEY_ALIAS)) { + val keyGenerator: KeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore) + keyGenerator.init(KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setRandomizedEncryptionRequired(false) + .build()); + + keyGenerator.generateKey() + } + } + + fun encrypt(decrypted: String): String { + val encrypted = encrypt(decrypted.toByteArray()) + val encoded = Base64.encodeToString(encrypted, Base64.DEFAULT) + return encoded + } + fun encrypt(decrypted: ByteArray): ByteArray { + val ivBytes = generateIv() + val c: Cipher = Cipher.getInstance(AES_MODE); + c.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH, ivBytes)) + val encodedBytes: ByteArray = c.doFinal(decrypted) + return ivBytes + encodedBytes + } + + fun decrypt(data: String): String { + val bytes = Base64.decode(data, Base64.DEFAULT) + return String(decrypt(bytes)) + } + fun decrypt(bytes: ByteArray): ByteArray { + val encrypted = bytes.sliceArray(IntRange(IV_SIZE, bytes.size - 1)) + val ivBytes = bytes.sliceArray(IntRange(0, IV_SIZE - 1)) + + val c = Cipher.getInstance(AES_MODE) + c.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH, ivBytes)) + return c.doFinal(encrypted) + } + + private fun generateIv(): ByteArray { + val r = SecureRandom() + val ivBytes = ByteArray(IV_SIZE) + r.nextBytes(ivBytes) + return ivBytes + } + + companion object { + val instance: PEncryptionProviderV1 = PEncryptionProviderV1(); + + private const val AndroidKeyStore = "AndroidKeyStore" + private const val KEY_ALIAS = "PolycentricCore_Key" + private const val AES_MODE = "AES/GCM/NoPadding" + private const val IV_SIZE = 12 + private const val TAG_LENGTH = 128 + private val TAG = "PEncryptionProviderV1" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt b/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt index dfe048f..e21fbd4 100644 --- a/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt +++ b/app/src/main/java/com/futo/polycentric/core/SqlLiteDbHelper.kt @@ -4,6 +4,7 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.util.Log +import userpackage.Protocol class SqlLiteDbHelper : SQLiteOpenHelper { constructor(context: Context, factory: SQLiteDatabase.CursorFactory? = null) : super(context, DATABASE_NAME, factory, DATABASE_VERSION) { } @@ -17,9 +18,42 @@ class SqlLiteDbHelper : SQLiteOpenHelper { Log.w(TAG, "Upgrade from version $oldVersion to $newVersion") Log.w(TAG, "This is version 1, no DB to update") + var currentVersion = oldVersion if (oldVersion == 1 && newVersion == 2) { deleteTables(db) createTables(db) + currentVersion = 2 + } + + if (currentVersion == 2 && newVersion == 3) { + val cursor = db.rawQuery("SELECT value FROM process_secrets", arrayOf()) + val secrets = arrayListOf<ProcessSecret>() + + cursor.use { + if (it.moveToFirst()) { + do { + val value = it.getBlob(0) + if (value != null) { + val secret = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(PEncryptionProviderV0.instance.decrypt(value))) + secrets.add(secret) + } + } while (cursor.moveToNext()) + } + } + + db.beginTransaction(); + try { + for (secret in secrets) { + db.execSQL("DELETE FROM process_secrets WHERE public_key = ?", arrayOf(secret.system.publicKey.key.toBase64())) + val value = PEncryptionProviderV1.instance.encrypt(secret.toProto().toByteArray()) + db.execSQL("INSERT INTO process_secrets VALUES(?, ?)", arrayOf(secret.system.publicKey.key.toBase64(), value)) + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + currentVersion = 3 } } @@ -50,6 +84,6 @@ class SqlLiteDbHelper : SQLiteOpenHelper { companion object { private const val TAG = "SqlLiteDbHelper" private const val DATABASE_NAME = "polycentric_core.db" - private const val DATABASE_VERSION = 2 + private const val DATABASE_VERSION = 3 } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt b/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt index b559a3a..8592849 100644 --- a/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt +++ b/app/src/main/java/com/futo/polycentric/core/SqlLiteStore.kt @@ -130,8 +130,7 @@ class SqlLiteStore(private val _db: SqlLiteDbHelper) : Store() { } if (value != null) { - val result = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(EncryptionProvider.instance.decrypt(value!!))) - return result + return ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(PEncryptionProvider.instance.decrypt(value!!))) } return null @@ -146,7 +145,7 @@ class SqlLiteStore(private val _db: SqlLiteDbHelper) : Store() { do { val value = it.getBlob(0) if (value != null) { - val secret = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(EncryptionProvider.instance.decrypt(value!!))) + val secret = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(PEncryptionProvider.instance.decrypt(value))) secrets.add(secret) } } while (cursor.moveToNext()) @@ -161,7 +160,7 @@ class SqlLiteStore(private val _db: SqlLiteDbHelper) : Store() { } override fun addProcessSecret(processSecret: ProcessSecret) { - val value = EncryptionProvider.instance.encrypt(processSecret.toProto().toByteArray()) + val value = PEncryptionProvider.instance.encrypt(processSecret.toProto().toByteArray()) _db.writableDatabase.execSQL("INSERT INTO process_secrets VALUES(?, ?)", arrayOf(processSecret.system.publicKey.key.toBase64(), value)) } -- GitLab