forked from sergeych/crypto2
+EncryptedKVStorage
This commit is contained in:
parent
776f4e75ff
commit
fe6190eb8d
@ -72,7 +72,7 @@ kotlin {
|
||||
implementation(project.dependencies.platform("org.kotlincrypto.hash:bom:0.5.1"))
|
||||
implementation("org.kotlincrypto.hash:sha3")
|
||||
api("com.ionspin.kotlin:bignum:0.3.9")
|
||||
api("net.sergeych:mp_bintools:0.1.11-SNAPSHOT")
|
||||
api("net.sergeych:mp_bintools:0.1.12-SNAPSHOT")
|
||||
api("net.sergeych:mp_stools:1.5.1")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import net.sergeych.bintools.KVStorage
|
||||
import net.sergeych.bintools.MemoryKVStorage
|
||||
import net.sergeych.bintools.optStored
|
||||
import net.sergeych.synctools.ProtectedOp
|
||||
import net.sergeych.synctools.invoke
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
/**
|
||||
* Encrypted variant of [KVStorage]; the storage is encrypted with the given key
|
||||
* in a given [plainStore] [KVStorage]. It is threadsafe where
|
||||
* applicable. Also, it supports in-place key change [reEncrypt].
|
||||
*
|
||||
* Keys are stored encrypted and used hashed so it is not possible to
|
||||
* retrieve them without knowing the encryption key.
|
||||
*/
|
||||
class EncryptedKVStorage(
|
||||
private val plainStore: KVStorage,
|
||||
private var encryptionKey: SymmetricKey,
|
||||
private val prefix: String = "EKVS_"
|
||||
) : KVStorage {
|
||||
private val op = ProtectedOp()
|
||||
|
||||
private val prefix2 = prefix + ":"
|
||||
|
||||
val seed: UByteArray
|
||||
|
||||
init {
|
||||
var encryptedSeed by plainStore.optStored<UByteArray>("$prefix#seed")
|
||||
seed = encryptedSeed?.let { encryptionKey.decrypt(it) }
|
||||
?: Random.nextUBytes(32).also {
|
||||
encryptedSeed = encryptionKey.encrypt(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mkkey(key: String): String =
|
||||
blake2b(key.encodeToByteArray().asUByteArray() + seed).encodeToBase64Url()
|
||||
|
||||
override val keys: Set<String>
|
||||
get() = op.invoke {
|
||||
plainStore.keys.mapNotNull {
|
||||
if (it.startsWith(prefix2))
|
||||
plainStore[it]?.let { encrypted ->
|
||||
encryptionKey.decrypt(encrypted.asUByteArray()).asByteArray().decodeToString()
|
||||
}
|
||||
else null
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
override fun get(key: String): ByteArray? = op {
|
||||
val k0 = mkkey(key)
|
||||
val k = prefix + k0
|
||||
plainStore[k]?.let { encryptionKey.decrypt(it.asUByteArray()).asByteArray() }
|
||||
?.also {
|
||||
val k2 = prefix2 + k0
|
||||
if (k2 !in plainStore)
|
||||
plainStore[k2] = encryptionKey.encrypt(key).asByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
override fun set(key: String, value: ByteArray?) {
|
||||
op {
|
||||
val k1 = mkkey(key)
|
||||
plainStore[prefix + k1] = value?.let {
|
||||
encryptionKey.encrypt(it.asUByteArray()).asByteArray()
|
||||
}
|
||||
plainStore[prefix2 + k1] = encryptionKey.encrypt(key).asByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encrypts the entire storage in-place with the given key; it is threadsafe where
|
||||
* applicable.
|
||||
*
|
||||
* This method re-encrypts every data item so it is cryptographically secure.
|
||||
*/
|
||||
fun reEncrypt(newKey: SymmetricKey) {
|
||||
op {
|
||||
val copy = MemoryKVStorage().also { it.addAll(this) }
|
||||
encryptionKey = newKey
|
||||
addAll(copy)
|
||||
}
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.bintools.ByteChunk
|
||||
import net.sergeych.bintools.toDump
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
import net.sergeych.crypto2.BinaryId
|
||||
import net.sergeych.crypto2.ByteChunk
|
||||
import net.sergeych.crypto2.initCrypto
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
@ -35,10 +35,10 @@ class BinaryIdTest {
|
||||
initCrypto()
|
||||
val x = ByteChunk.random(3)
|
||||
assertEquals(3, x.data.size)
|
||||
assertEquals(3, x.toByteArray().size)
|
||||
assertEquals(3, x.toUByteArray().size)
|
||||
assertEquals(3, x.asByteArray.size)
|
||||
assertEquals(3, x.data.size)
|
||||
println(BipackEncoder.encode(x).toDump())
|
||||
assertEquals(4, BipackEncoder.encode(x).size)
|
||||
assertContentEquals(BipackEncoder.encode(x.toByteArray()), BipackEncoder.encode(x))
|
||||
assertContentEquals(BipackEncoder.encode(x.asByteArray), BipackEncoder.encode(x))
|
||||
}
|
||||
}
|
70
src/commonTest/kotlin/StorageTest.kt
Normal file
70
src/commonTest/kotlin/StorageTest.kt
Normal file
@ -0,0 +1,70 @@
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.bintools.*
|
||||
import net.sergeych.bipack.decodeFromBipack
|
||||
import net.sergeych.crypto2.EncryptedKVStorage
|
||||
import net.sergeych.crypto2.SymmetricKey
|
||||
import net.sergeych.crypto2.initCrypto
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class StorageTest {
|
||||
|
||||
@Test
|
||||
fun testGetAndSet() = runTest {
|
||||
initCrypto()
|
||||
val plain = MemoryKVStorage()
|
||||
val key = SymmetricKey.new()
|
||||
val storage = EncryptedKVStorage(plain, key)
|
||||
|
||||
var hello by storage.optStored<String>()
|
||||
assertNull(hello)
|
||||
hello = "world"
|
||||
assertEquals("world", storage["hello"]?.decodeFromBipack<String>())
|
||||
println("plain: ${plain.keys}")
|
||||
assertEquals(setOf("hello"), storage.keys)
|
||||
var foo by storage.stored("bar")
|
||||
assertEquals("bar", foo)
|
||||
foo = "bar2"
|
||||
// plain.dump()
|
||||
// storage.dump()
|
||||
assertEquals(setOf("hello", "foo"), storage.keys)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReEncrypt() = runTest {
|
||||
initCrypto()
|
||||
fun test(x: KVStorage) {
|
||||
val foo by x.stored("1")
|
||||
val bar by x.stored("2")
|
||||
val bazz by x.stored("3")
|
||||
assertEquals("foo", foo)
|
||||
assertEquals("bar", bar)
|
||||
assertEquals("bazz", bazz)
|
||||
}
|
||||
fun setup(s: KVStorage, k: SymmetricKey): EncryptedKVStorage {
|
||||
val x = EncryptedKVStorage(s, k)
|
||||
var foo by x.stored("1")
|
||||
var bar by x.stored("2")
|
||||
var bazz by x.stored("3")
|
||||
foo = "foo"
|
||||
bar = "bar"
|
||||
bazz = "bazz"
|
||||
return x
|
||||
}
|
||||
val k1 = SymmetricKey.new()
|
||||
val k2 = SymmetricKey.new()
|
||||
val plain = MemoryKVStorage()
|
||||
val s1 = setup(plain, k1)
|
||||
test(s1)
|
||||
s1.reEncrypt(k2)
|
||||
test(s1)
|
||||
// val s2 = EncryptedKVStorage(plain, k2)
|
||||
// test(s2)
|
||||
}
|
||||
}
|
||||
|
||||
fun KVStorage.dump() {
|
||||
for (k in keys)
|
||||
println("$k: ${this[k]?.toDump()}")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user