Compare commits

...

2 Commits

2 changed files with 43 additions and 7 deletions

View File

@ -15,11 +15,19 @@ import kotlin.random.nextUBytes
* *
* Keys are stored encrypted and used hashed so it is not possible to * Keys are stored encrypted and used hashed so it is not possible to
* retrieve them without knowing the encryption key. * retrieve them without knowing the encryption key.
*
* @param plainStore where to store encrypted data
* @param encryptionKey key to decrypt existing/encrypt new data. Can cause [DecryptionFailedException]
* if the key is wrong and the storage is already initialized with a new key and same [prefix]
* @param prefix prefix for keys to distinguish from other data in [plainStore]
* @param removeExisting if true, removes all existing data in [plainStore] if the [encryptionKey] can't
* decrypt existing encrypted data
*/ */
class EncryptedKVStorage( class EncryptedKVStorage(
private val plainStore: KVStorage, private val plainStore: KVStorage,
private var encryptionKey: SymmetricKey, private var encryptionKey: SymmetricKey,
private val prefix: String = "EKVS_" private val prefix: String = "EKVS_",
removeExisting: Boolean
) : KVStorage { ) : KVStorage {
private val op = ProtectedOp() private val op = ProtectedOp()
@ -29,10 +37,21 @@ class EncryptedKVStorage(
init { init {
var encryptedSeed by plainStore.optStored<UByteArray>("$prefix#seed") var encryptedSeed by plainStore.optStored<UByteArray>("$prefix#seed")
seed = encryptedSeed?.let { encryptionKey.decrypt(it) } seed = try {
?: Random.nextUBytes(32).also { encryptedSeed?.let { encryptionKey.decrypt(it) }
encryptedSeed = encryptionKey.encrypt(it) ?: Random.nextUBytes(32).also {
} encryptedSeed = encryptionKey.encrypt(it)
}
} catch (x: DecryptionFailedException) {
if (removeExisting) {
plainStore.keys.filter { it.startsWith(prefix) }.forEach {
plainStore.delete(it)
}
Random.nextUBytes(32).also {
encryptedSeed = encryptionKey.encrypt(it)
}
} else throw x
}
} }
private fun mkkey(key: String): String = private fun mkkey(key: String): String =

View File

@ -1,11 +1,13 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.* import net.sergeych.bintools.*
import net.sergeych.bipack.decodeFromBipack import net.sergeych.bipack.decodeFromBipack
import net.sergeych.crypto2.DecryptionFailedException
import net.sergeych.crypto2.EncryptedKVStorage import net.sergeych.crypto2.EncryptedKVStorage
import net.sergeych.crypto2.SymmetricKey import net.sergeych.crypto2.SymmetricKey
import net.sergeych.crypto2.initCrypto import net.sergeych.crypto2.initCrypto
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull import kotlin.test.assertNull
class StorageTest { class StorageTest {
@ -15,7 +17,7 @@ class StorageTest {
initCrypto() initCrypto()
val plain = MemoryKVStorage() val plain = MemoryKVStorage()
val key = SymmetricKey.new() val key = SymmetricKey.new()
val storage = EncryptedKVStorage(plain, key) val storage = EncryptedKVStorage(plain, key, removeExisting = false)
var hello by storage.optStored<String>() var hello by storage.optStored<String>()
assertNull(hello) assertNull(hello)
@ -42,8 +44,9 @@ class StorageTest {
assertEquals("bar", bar) assertEquals("bar", bar)
assertEquals("bazz", bazz) assertEquals("bazz", bazz)
} }
fun setup(s: KVStorage, k: SymmetricKey): EncryptedKVStorage { fun setup(s: KVStorage, k: SymmetricKey): EncryptedKVStorage {
val x = EncryptedKVStorage(s, k) val x = EncryptedKVStorage(s, k, removeExisting = false)
var foo by x.stored("1") var foo by x.stored("1")
var bar by x.stored("2") var bar by x.stored("2")
var bazz by x.stored("3") var bazz by x.stored("3")
@ -52,6 +55,7 @@ class StorageTest {
bazz = "bazz" bazz = "bazz"
return x return x
} }
val k1 = SymmetricKey.new() val k1 = SymmetricKey.new()
val k2 = SymmetricKey.new() val k2 = SymmetricKey.new()
val plain = MemoryKVStorage() val plain = MemoryKVStorage()
@ -62,8 +66,21 @@ class StorageTest {
// val s2 = EncryptedKVStorage(plain, k2) // val s2 = EncryptedKVStorage(plain, k2)
// test(s2) // test(s2)
} }
@Test
fun testDeleteExisting() = runTest {
initCrypto()
val plain = MemoryKVStorage()
val c1 = EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = false) // 1
c1.write("hello", "world")
assertFailsWith<DecryptionFailedException> {
EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = false) // 2
}
EncryptedKVStorage(plain, SymmetricKey.new(), removeExisting = true) // 2
}
} }
@Suppress("unused")
fun KVStorage.dump() { fun KVStorage.dump() {
for (k in keys) for (k in keys)
println("$k: ${this[k]?.toDump()}") println("$k: ${this[k]?.toDump()}")