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 5498d3dba34896f41edd349763b45d24a2bac845..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..5fb0bf17e62f162d3f897b1c546cf24c35f0a8b3 --- /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 0000000000000000000000000000000000000000..03e74da3ac08cfea027270f36c9eec34d1c14882 --- /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 0000000000000000000000000000000000000000..ba621730c29dbd873cb745d11d9d5829f26110e0 --- /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 724fa034108694f05bfae31ae06584a3dde9bcd7..41510b5a7b45aa8d1eba405fdfbd13f1c6debe88 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 0000000000000000000000000000000000000000..60264455ec2670bc01d26c62ddc6643c3803afd6 --- /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 dfe048f19f302bcdd1364d30eff8dfa340330077..e21fbd430f90a05846e49a9ced83d01b9a0ec594 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 b559a3a9e1a15b4a4a2a0b1283e973176d814a2a..859284937c2a896d910dc44ecc564daa32782e11 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)) }