diff --git a/README.md b/README.md index 071f7ac..6554e9f 100644 --- a/README.md +++ b/README.md @@ -22,69 +22,3 @@ dependencies { } ``` -## API - -### V1 - -#### Sign the message - -```kotlin -val privateKeyHex = "0xcafebabe" -val msg = "to be signed" - -//with Ed25519 schema -val ed25519Signer: Signer = Ed25519Signer(Hex.decode(privateKeyHex)) -val ed25519Signature = ed25519Signer.signToBytes(msg) - -//with Sr25519 schema -val sr25519Signer: Signer = Sr25519Signer(Hex.decode(privateKeyHex)) -val sr25519Signature = sr25519Signer.signToBytes(msg) -``` - -#### Encrypt and decrypt raw message - -```kotlin -val data = "raw data".toByteArray() -val masterKeyHex = Hex.encode("super-secret-key".repeat(2).toByteArray()) -val encrypter = RawDataEncrypter(EncryptionConfig(masterKeyHex)) -val decrypter = RawDataDecrypter(DecryptionConfig(mapOf("" to masterKeyHex))) -val encrypted = encrypter.encrypt(data) -val decrypted = decrypted.decrypt(result) // "raw data" -``` - -#### Encrypt and decrypt JSON message - -```kotlin -val masterKeyHex = Hex.encode("super-secret-key".repeat(2).toByteArray()) -val encrypter = JsonDataEncrypter( - EncryptionConfig( - masterKeyHex, - listOf("$.k1") // JSON Paths we want to encrypt, default is "$..*" which means all fields - ) -) -val decrypter = JsonDataDecrypter( - DecryptionConfig( - mapOf("$.k1" to "0ae19ba1e42a63aefea507a19df00ffc962bc894b3fb720723d45e456f636977") // derived key for this path - ) -) -val data = """ - { - "k1": "v1", - "k2": "v2", - "k3": { - "k4": true, - "k5": ["v5", "v5"] - }, - "k6": { - "k7": { - "k8": 123 - } - } - } - """.trimIndent().toByteArray() - -val encrypted = encrypter.encrypt(data) -val decrypted = decrypted.decrypt(result) // "original json" -``` - - diff --git a/build.gradle.kts b/build.gradle.kts index d464167..33c9968 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,15 +11,13 @@ group = "com.github.cerebellum-network" repositories { mavenLocal() mavenCentral() - maven { url = uri("https://dl.bintray.com/emerald/polkaj") } } dependencies { implementation(kotlin("stdlib")) - implementation("com.rfksystems:blake2b:1.0.0") - implementation("com.google.crypto.tink:tink:1.5.0") - implementation("io.emeraldpay.polkaj:polkaj-schnorrkel:0.3.0") + api("com.goterl:lazysodium-java:5.0.1") + implementation("net.java.dev.jna:jna:5.8.0") implementation("com.github.jsurfer:jsurfer-jackson:1.6.0") implementation("com.jayway.jsonpath:json-path:2.5.0") diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/TypeHint.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/TypeHint.kt deleted file mode 100644 index 9d0e38e..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/TypeHint.kt +++ /dev/null @@ -1,6 +0,0 @@ -package network.cere.ddc.crypto.v1 - -enum class TypeHint { - JSON, - RAW -} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/common/BaseCryptoService.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/common/BaseCryptoService.kt new file mode 100644 index 0000000..2ee01ce --- /dev/null +++ b/src/main/kotlin/network/cere/ddc/crypto/v1/common/BaseCryptoService.kt @@ -0,0 +1,21 @@ +package network.cere.ddc.crypto.v1.common + +import com.goterl.lazysodium.LazySodium +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.Box +import com.goterl.lazysodium.utils.Key + +abstract class BaseCryptoService(protected val sodium: LazySodium) { + protected companion object { + const val JSON_ROOT_PATH = "$" + } + + private val emptyNonce = ByteArray(Box.NONCEBYTES) + private val encryptionMethod = AEAD.Method.XCHACHA20_POLY1305_IETF + + protected fun encrypt(message: String, key: Key) = + sodium.encrypt(message, null, emptyNonce, key, encryptionMethod) + + protected fun decrypt(cipherHex: String, key: Key) = + sodium.decrypt(cipherHex, null, emptyNonce, key, encryptionMethod) +} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/AbstractDecrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/AbstractDecrypter.kt deleted file mode 100644 index ed9f027..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/AbstractDecrypter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package network.cere.ddc.crypto.v1.decrypt - -import com.google.crypto.tink.subtle.Hex -import com.google.crypto.tink.subtle.XChaCha20Poly1305 - -abstract class AbstractDecrypter(decryptionConfig: DecryptionConfig) : Decrypter { - protected val aeadCache = decryptionConfig.pathToDecryptToDecryptionKeyHex - .mapValues { XChaCha20Poly1305(Hex.decode(it.value.removePrefix("0x"))) } -} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/Decrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/Decrypter.kt index d03ed7a..3ab8bb0 100644 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/Decrypter.kt +++ b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/Decrypter.kt @@ -1,9 +1,46 @@ package network.cere.ddc.crypto.v1.decrypt -import network.cere.ddc.crypto.v1.TypeHint +import com.fasterxml.jackson.databind.JsonNode +import com.goterl.lazysodium.LazySodium +import com.goterl.lazysodium.utils.Key +import com.jayway.jsonpath.JsonPath +import network.cere.ddc.crypto.v1.common.BaseCryptoService +import org.jsfr.json.JacksonParser +import org.jsfr.json.JsonPathListener +import org.jsfr.json.JsonSurfer +import org.jsfr.json.ParsingContext +import org.jsfr.json.provider.JacksonProvider -interface Decrypter { - val supportedDataType: TypeHint - - fun decrypt(data: ByteArray): ByteArray +class Decrypter( + sodium: LazySodium, + private val pathToDecryptToDecryptionKeyHex: Map +) : BaseCryptoService(sodium) { + fun decrypt(data: String): String { + val surfer = JsonSurfer(JacksonParser.INSTANCE, JacksonProvider.INSTANCE) + val toReplace = mutableMapOf() + val builder = surfer.configBuilder() + pathToDecryptToDecryptionKeyHex.forEach { + builder.bind(it.key, object : JsonPathListener { + override fun onValue(value: Any, context: ParsingContext) { + val node = value as JsonNode + if (node.isValueNode) { + val path = context.jsonPath + toReplace[path] = decrypt(node.textValue(), Key.fromHexString(it.value)) + } + } + }) + } + return runCatching { + builder.buildAndSurf(data) + }.fold( + { + val ctx = JsonPath.parse(data) + toReplace.forEach { (p, v) -> ctx.set(p, v) } + ctx.jsonString() + }, + { + decrypt(data, Key.fromHexString(pathToDecryptToDecryptionKeyHex[JSON_ROOT_PATH])) + } + ) + } } diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/DecryptionConfig.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/DecryptionConfig.kt deleted file mode 100644 index 5133d2c..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/DecryptionConfig.kt +++ /dev/null @@ -1,5 +0,0 @@ -package network.cere.ddc.crypto.v1.decrypt - -data class DecryptionConfig( - val pathToDecryptToDecryptionKeyHex: Map -) diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/JsonDataDecrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/JsonDataDecrypter.kt deleted file mode 100644 index 07b62e7..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/JsonDataDecrypter.kt +++ /dev/null @@ -1,36 +0,0 @@ -package network.cere.ddc.crypto.v1.decrypt - -import com.fasterxml.jackson.databind.JsonNode -import com.google.crypto.tink.subtle.Hex -import com.jayway.jsonpath.JsonPath -import network.cere.ddc.crypto.v1.TypeHint -import org.jsfr.json.JacksonParser -import org.jsfr.json.JsonPathListener -import org.jsfr.json.JsonSurfer -import org.jsfr.json.ParsingContext -import org.jsfr.json.provider.JacksonProvider - -class JsonDataDecrypter(decryptionConfig: DecryptionConfig) : AbstractDecrypter(decryptionConfig) { - override val supportedDataType: TypeHint = TypeHint.JSON - - override fun decrypt(data: ByteArray): ByteArray { - val surfer = JsonSurfer(JacksonParser.INSTANCE, JacksonProvider.INSTANCE) - val toReplace = mutableMapOf() - val builder = surfer.configBuilder() - aeadCache.forEach { - builder.bind(it.key, object : JsonPathListener { - override fun onValue(value: Any, context: ParsingContext) { - val node = value as JsonNode - if (node.isValueNode) { - val path = context.jsonPath - toReplace[path] = it.value.decrypt(Hex.decode(node.textValue()), null) - } - } - }) - } - data.inputStream().use(builder::buildAndSurf) - val ctx = data.inputStream().use(JsonPath::parse) - toReplace.forEach { (p, v) -> ctx.set(p, v.decodeToString()) } - return ctx.jsonString().toByteArray() - } -} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/RawDataDecrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/RawDataDecrypter.kt deleted file mode 100644 index 5cd229a..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/decrypt/RawDataDecrypter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package network.cere.ddc.crypto.v1.decrypt - -import network.cere.ddc.crypto.v1.TypeHint - -class RawDataDecrypter(decryptionConfig: DecryptionConfig) : AbstractDecrypter(decryptionConfig) { - override val supportedDataType: TypeHint = TypeHint.RAW - - override fun decrypt(data: ByteArray): ByteArray { - return aeadCache.values.first().decrypt(data, null) - } -} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/AbstractEncrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/AbstractEncrypter.kt deleted file mode 100644 index 83afd0e..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/AbstractEncrypter.kt +++ /dev/null @@ -1,9 +0,0 @@ -package network.cere.ddc.crypto.v1.encrypt - -import com.google.crypto.tink.subtle.Hex -import com.google.crypto.tink.subtle.XChaCha20Poly1305 - -abstract class AbstractEncrypter(protected val encryptionConfig: EncryptionConfig) : Encrypter { - protected val masterKey = Hex.decode(encryptionConfig.masterKeyHex.removePrefix("0x")) - protected val masterAead = XChaCha20Poly1305(masterKey) -} diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/Encrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/Encrypter.kt index 04530c8..248c1f0 100644 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/Encrypter.kt +++ b/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/Encrypter.kt @@ -1,9 +1,57 @@ package network.cere.ddc.crypto.v1.encrypt -import network.cere.ddc.crypto.v1.TypeHint +import com.fasterxml.jackson.databind.JsonNode +import com.goterl.lazysodium.LazySodium +import com.goterl.lazysodium.utils.Key +import com.jayway.jsonpath.JsonPath +import network.cere.ddc.crypto.v1.common.BaseCryptoService +import org.jsfr.json.JacksonParser +import org.jsfr.json.JsonPathListener +import org.jsfr.json.JsonSurfer +import org.jsfr.json.ParsingContext +import org.jsfr.json.provider.JacksonProvider -interface Encrypter { - val supportedDataType: TypeHint +class Encrypter( + sodium: LazySodium, + private val encryptionConfig: EncryptionConfig +) : BaseCryptoService(sodium) { + private val masterKey = Key.fromHexString(encryptionConfig.masterKeyHex) - fun encrypt(data: ByteArray): ByteArray + fun encrypt(data: String): Pair> { + val surfer = JsonSurfer(JacksonParser.INSTANCE, JacksonProvider.INSTANCE) + val toReplace = mutableMapOf() + val builder = surfer.configBuilder() + val pathToKey = mutableMapOf() + encryptionConfig.jsonPathsToEncrypt.forEach { + builder.bind(it, object : JsonPathListener { + override fun onValue(value: Any, context: ParsingContext) { + val node = value as JsonNode + if (node.isValueNode) { + val path = context.jsonPath + val dek = dek(path) + val encrypted = encrypt(node.asText(), Key.fromHexString(dek)) + toReplace[path] = encrypted + pathToKey[path] = dek + } + } + }) + } + return runCatching { + builder.buildAndSurf(data) + }.fold( + { + val ctx = JsonPath.parse(data) + toReplace.forEach { (p, v) -> ctx.set(p, v) } + ctx.jsonString() to pathToKey + }, + { + val dek = dek(JSON_ROOT_PATH) + encrypt(data, Key.fromHexString(dek)) to mapOf(JSON_ROOT_PATH to dek) + } + ) + } + + private fun dek(path: String): String { + return sodium.cryptoGenericHash(path, masterKey) + } } diff --git a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/JsonDataEncrypter.kt b/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/JsonDataEncrypter.kt deleted file mode 100644 index 1251cfb..0000000 --- a/src/main/kotlin/network/cere/ddc/crypto/v1/encrypt/JsonDataEncrypter.kt +++ /dev/null @@ -1,68 +0,0 @@ -package network.cere.ddc.crypto.v1.encrypt - -import com.fasterxml.jackson.databind.JsonNode -import com.google.crypto.tink.Aead -import com.google.crypto.tink.subtle.Hex -import com.google.crypto.tink.subtle.XChaCha20Poly1305 -import com.jayway.jsonpath.Configuration -import com.jayway.jsonpath.JsonPath -import com.jayway.jsonpath.Option -import com.jayway.jsonpath.spi.json.JacksonJsonProvider -import com.jayway.jsonpath.spi.json.JsonProvider -import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider -import com.jayway.jsonpath.spi.mapper.MappingProvider -import com.rfksystems.blake2b.security.Blake2b256Digest -import network.cere.ddc.crypto.v1.TypeHint -import org.jsfr.json.JacksonParser -import org.jsfr.json.JsonPathListener -import org.jsfr.json.JsonSurfer -import org.jsfr.json.ParsingContext -import org.jsfr.json.provider.JacksonProvider -import java.util.* -import java.util.concurrent.ConcurrentHashMap - -class JsonDataEncrypter(encryptionConfig: EncryptionConfig) : AbstractEncrypter(encryptionConfig) { - private val aeadCache = ConcurrentHashMap() - - init { - Configuration.setDefaults(object : Configuration.Defaults { - private val jsonProvider = JacksonJsonProvider() - private val mappingProvider = JacksonMappingProvider() - override fun jsonProvider(): JsonProvider = jsonProvider - override fun options(): Set