diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 296be4b..7a94ed5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ mavenPublishGradlePlugin = "0.25.3" moshi = "1.15.0" okHttp = "5.0.0-alpha.11" okIo = "3.4.0" +secureRandom = "0.3.1" slf4j = "2.0.7" versionCatalogUpdateGradlePlugin = "0.8.1" versionsGradlePlugin = "0.47.0" @@ -32,6 +33,7 @@ kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } okIo = { module = "com.squareup.okio:okio", version.ref = "okIo" } +secureRandom = { module = "org.kotlincrypto:secure-random", version.ref = "secureRandom" } slf4jSimple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } turbine = "app.cash.turbine:turbine:1.0.0" kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } diff --git a/lib/api/lib.api b/lib/api/lib.api index a294a99..51f46d9 100644 --- a/lib/api/lib.api +++ b/lib/api/lib.api @@ -169,7 +169,6 @@ public final class app/cash/nostrino/crypto/SecKey : app/cash/nostrino/crypto/Ke public final fun copy (Lokio/ByteString;)Lapp/cash/nostrino/crypto/SecKey; public static synthetic fun copy$default (Lapp/cash/nostrino/crypto/SecKey;Lokio/ByteString;ILjava/lang/Object;)Lapp/cash/nostrino/crypto/SecKey; public fun encoded ()Ljava/lang/String; - public final fun encrypt (Lapp/cash/nostrino/crypto/PubKey;Ljava/lang/String;)Lapp/cash/nostrino/crypto/CipherText; public fun equals (Ljava/lang/Object;)Z public final fun getKey ()Lokio/ByteString; public final fun getNsec ()Ljava/lang/String; @@ -359,6 +358,10 @@ public final class app/cash/nostrino/model/EncryptedDm : app/cash/nostrino/model public final class app/cash/nostrino/model/EncryptedDm$Companion { } +public final class app/cash/nostrino/model/EncryptedDmKt { + public static final fun encrypt (Lapp/cash/nostrino/crypto/SecKey;Lapp/cash/nostrino/crypto/PubKey;Ljava/lang/String;)Lapp/cash/nostrino/crypto/CipherText; +} + public final class app/cash/nostrino/model/Event { public static final field Companion Lapp/cash/nostrino/model/Event$Companion; public fun (Lokio/ByteString;Lokio/ByteString;Ljava/time/Instant;ILjava/util/List;Ljava/lang/String;Lokio/ByteString;)V diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 2b6650a..e7b7db6 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { implementation(libs.acinqSecp256k1) implementation(libs.kotlinxCoroutines) implementation(libs.okIo) + implementation(libs.secureRandom) } } diff --git a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/Key.kt b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/Key.kt similarity index 100% rename from lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/Key.kt rename to lib/src/commonMain/kotlin/app/cash/nostrino/crypto/Key.kt diff --git a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/PubKey.kt b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/PubKey.kt similarity index 100% rename from lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/PubKey.kt rename to lib/src/commonMain/kotlin/app/cash/nostrino/crypto/PubKey.kt diff --git a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKey.kt b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKey.kt similarity index 79% rename from lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKey.kt rename to lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKey.kt index e1c3cbb..a23a62f 100644 --- a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKey.kt +++ b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKey.kt @@ -21,10 +21,6 @@ import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Secp256k1 import okio.ByteString import okio.ByteString.Companion.toByteString -import java.security.SecureRandom -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec /** * Nostr secret key. @@ -58,15 +54,7 @@ data class SecKey(val key: ByteString) : Key { ).copyOfRange(1, 33) /** Generate cipher text of the provided plain text, intended for the provided pub key */ - fun encrypt(to: PubKey, plainText: String): CipherText { - val random = SecureRandom() - val iv = ByteArray(16) - random.nextBytes(iv) - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecretWith(to), "AES"), IvParameterSpec(iv)) - val encrypted = cipher.doFinal(plainText.toByteArray()) - return CipherText(encrypted.toByteString(), iv.toByteString()) - } + companion object { /** Create secret key from nip-19 bech32 encoded string */ diff --git a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt similarity index 93% rename from lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt rename to lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt index 3f3d984..07ad7d8 100644 --- a/lib/src/jvmMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt +++ b/lib/src/commonMain/kotlin/app/cash/nostrino/crypto/SecKeyGenerator.kt @@ -18,14 +18,14 @@ package app.cash.nostrino.crypto import okio.ByteString.Companion.toByteString -import java.security.SecureRandom +import org.kotlincrypto.SecureRandom class SecKeyGenerator { /** Generate a new secret key */ tailrec fun generate(): SecKey { val bs = ByteArray(32) - SecureRandom().nextBytes(bs) + SecureRandom().nextBytesCopyTo(bs) val sec = bs.toByteString() return if (sec.hex() > MAX_SEC) generate() else SecKey(sec) } diff --git a/lib/src/jvmMain/kotlin/app/cash/nostrino/model/EncryptedDm.kt b/lib/src/jvmMain/kotlin/app/cash/nostrino/model/EncryptedDm.kt index 09de880..1a5ec3b 100644 --- a/lib/src/jvmMain/kotlin/app/cash/nostrino/model/EncryptedDm.kt +++ b/lib/src/jvmMain/kotlin/app/cash/nostrino/model/EncryptedDm.kt @@ -19,6 +19,11 @@ package app.cash.nostrino.model import app.cash.nostrino.crypto.CipherText import app.cash.nostrino.crypto.PubKey import app.cash.nostrino.crypto.SecKey +import okio.ByteString.Companion.toByteString +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec /** * An encrypted direct message. Event kind 4, as defined in @@ -43,3 +48,13 @@ data class EncryptedDm( const val kind = 4 } } + +fun SecKey.encrypt(to: PubKey, plainText: String): CipherText { + val random = SecureRandom() + val iv = ByteArray(16) + random.nextBytes(iv) + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(sharedSecretWith(to), "AES"), IvParameterSpec(iv)) + val encrypted = cipher.doFinal(plainText.toByteArray()) + return CipherText(encrypted.toByteString(), iv.toByteString()) +}