Correctly managing Shared Secrets

This commit is contained in:
Vitor Pamplona
2023-07-29 15:29:51 -04:00
parent 2fdb4e47b0
commit 16a7fdffcf
5 changed files with 118 additions and 15 deletions

View File

@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.model.hexToByteArray
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.service.CryptoUtils
import com.vitorpamplona.amethyst.service.KeyPair
@@ -10,13 +11,24 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CryptoUtilsTest {
@Test()
@Test
fun testSharedSecretCompatibilityWithCoracle() {
val privateKey = "f410f88bcec6cbfda04d6a273c7b1dd8bba144cd45b71e87109cfa11dd7ed561"
val publicKey = "765cd7cf91d3ad07423d114d5a39c61d52b2cdbc18ba055ddbbeec71fbe2aa2f"
val key = CryptoUtils.getSharedSecretXChaCha(privateKey = privateKey.hexToByteArray(), pubKey = publicKey.hexToByteArray())
assertEquals("577c966f499dddd8e8dcc34e8f352e283cc177e53ae372794947e0b8ede7cfd8", key.toHexKey())
}
@Test
fun testSharedSecret() {
val sender = KeyPair()
val receiver = KeyPair()
val sharedSecret1 = CryptoUtils.getSharedSecret(sender.privKey!!, receiver.pubKey)
val sharedSecret2 = CryptoUtils.getSharedSecret(receiver.privKey!!, sender.pubKey)
val sharedSecret1 = CryptoUtils.getSharedSecretXChaCha(sender.privKey!!, receiver.pubKey)
val sharedSecret2 = CryptoUtils.getSharedSecretXChaCha(receiver.privKey!!, sender.pubKey)
assertEquals(sharedSecret1.toHexKey(), sharedSecret2.toHexKey())
@@ -73,7 +85,7 @@ class CryptoUtilsTest {
val privateKey = CryptoUtils.privkeyCreate()
val publicKey = CryptoUtils.pubkeyCreate(privateKey)
val sharedSecret = CryptoUtils.getSharedSecret(privateKey, publicKey)
val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privateKey, publicKey)
val encrypted = CryptoUtils.encryptXChaCha(msg, sharedSecret)
val decrypted = CryptoUtils.decryptXChaCha(encrypted, sharedSecret)

View File

@@ -34,6 +34,18 @@ class GiftWrapEventTest {
sender.privKey!!
)
events.forEach {
if (it.recipientPubKey() == sender.pubKey.toHexKey()) {
println(sender.privKey!!.toHexKey())
println(it.toJson())
}
if (it.recipientPubKey() == receiver.pubKey.toHexKey()) {
println(receiver.privKey!!.toHexKey())
println(it.toJson())
}
}
// Simulate Receiver
val eventsReceiverGets = events.filter { it.isTaggedUser(receiver.pubKey.toHexKey()) }
eventsReceiverGets.forEach {
@@ -220,6 +232,8 @@ class GiftWrapEventTest {
assertEquals("Hi There!", unwrappedGossipToReceiverByReceiver?.content)
assertEquals("Hi There!", unwrappedGossipToSenderBySender?.content)
} else {
fail()
}
}
@@ -386,6 +400,82 @@ class GiftWrapEventTest {
assertEquals("Hi There!", unwrappedGossipToReceiverAByReceiverA?.content)
assertEquals("Hi There!", unwrappedGossipToReceiverBByReceiverB?.content)
assertEquals("Hi There!", unwrappedGossipToSenderBySender?.content)
} else {
fail()
}
}
@Test
fun testCaseFromAmethyst1() {
val json = """
{
"content":"{\"ciphertext\":\"2kQ019KOsZ53G1nZEzYjmnqRNSSdkJfxpZIomM8NxNx+0v9W9ypQugUrimqgsTyhW91o03g/RO0+C64TgTARicPeBJx2qqPPCMUa1FAyLl7mB+G9JGTlZkLX5D0k/HGBKjRtQSmw+hHzPV2nHMU3JO9AFwzbcGkMRa+I6bnGn7C4fOWgdNuRZSHJ1k7LUUDE4TdCra2fh9eeguEgzEUmBlqyJASnGZjdbGjo62Fa0jZLGjVf+1Q8uO9YXnKbKrjIC5Eds+zRbRiLPRVeCdtEy9rvdCsgHNuhX0oWk21TzUy8QREUko3+VdaCm1twEBzBxBkCreWCH175vB5gfip9GVJoRqnDC3lCpqJJ1knB24m0sVbByRCIkQpznLsWbXspyEWmWA0WhaTn0OQWg6P9exQscAq8IBILp8otxlPPp4/UUhDEmhB8IFhlieMVSpBVHQka/uaHBy1oL/HzEkI/siza86LiJv95kcTfqG3LfJwJaIGoshFZTxuOF7JQcXobzEpodtP9RImbmZIfTcrL29BH3oCAH8o9P5CHeyC+RUK0iX5Uf0guAskrrVGy18BbBceGzAKP09so+kJ56RH3CKaPX0PVMqBiwDISdRAdSSwXkJIl9oCPYqB5O7rRlPhx53M2+Qej77hSwvvYLOlp0HirSRko1a8jrhhVT7npTdfO6QBWjC3Cm1nJUYymFqqqrnb6w2GmlcMW20rpcvUd/x3sQF9CPl22H6XDgcUxtfdgFLmoMUnKkbj5EZUKJXPF6eUOHu/C+YFf/7p/bKT2QloQY7so0Fw5+NTzNnrsuOuk7LJ2yMonpAyjHZ+KR9od/QLzB5QDCqtXisiJM6A1L8agyF6lkZVGEZX7fBhWtI/qMzukvFWBCLb8luPVC+sgCzuVTbgAvGIDagqIhxyVd+k9IduteVrB+2QI4LBsIALWcFhOjwegxkwSFGcTeU5aFfxyjJcLlW5DPuJeb5k3O6gUN1chlb+by5vrUmglE5wGrmcL8TDNay71S50/GKVXVg8/hb8lT2XYTV4h9wMRjbv330b90SZreNGxBJNIYA1UjyYlXAmI\",\"nonce\":\"K99zmxMrz3WaijlFY1KPfF1Z7yKmoTQa\",\"v\":1}",
"created_at":1690658663,
"id":"91fd8ea5741ec4bd6633b6500c1466551a4dcc823a32a670481ab35e6f1a3738",
"kind":1059,
"pubkey":"6dd4b660790805c77fafe3fabf6fb89e3f18b17246af1b74f1b005759add2776",
"sig":"93f4ae7782b03d17815d2c67e23cf6d4c0764bac865d0cbd74cf580882632581fc060395a86cad078bea0e33b78fe6dfcd3d04099fe6b51bc6e87c470f508374",
"tags":[
[
"p",
"4b99b998c76a47f7284e5158092fb4f0795ee05fb04136f8be8b727ad2e5357b"
]
]
}
""".trimIndent()
val privateKey = "15c4facfb7aafe31d2fb45183c830a95773f8cb37012e35ff5f306c9b97a8cfe".hexToByteArray()
val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent
wrap.checkSignature()
assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey())
val event = wrap.unwrap(privateKey)
assertNotNull(event)
if (event is SealedGossipEvent) {
val innerData = event.unseal(privateKey)
assertEquals("Hola, que tal?", innerData?.content)
} else {
fail("Inner event is not a Sealed Gossip")
}
}
@Test
fun testCaseFromAmethyst2() {
val json = """
{
"content":"{\"ciphertext\":\"Z9nuRGldDimFioFx0eoaiRjHWnMxWjgbvDFfn8S84sSrLr+/TZGT/HSlmU4LeisGsvdEps6c7nROQ43Jnri9eDtCwM9X/picSISdfI256wgh+17xHxBNQUY0+mUnttfrbt1IGPlMFAiRPH4CJavXL586ufoybRLFuI0bn9rnPKnqrpocUmYYXfwa5wbIdNeHN0MhQ+t63dwyk8v0zROfUM9UFVqA7Uj28T4jOyxSxi0vrlPPNhPrVqx1F8cTro3aeelkpVJOuEa483BrojqqougYt3bNDWzyHZBk8KMGT27/U8trzbsbiutG/JP0KpckuX/OOQnuC32HxduED9yElHP/thPgD5Tzk1KruexdX78zzGQxTz4288JPAlb0dHLiHCzGo7jcMMDc0W60yRmBIHupVjX677cPXNLb7CZZlBteuzMevcGpDpSAdvft/hzGgdAV3zn3Xsz9V0EYO35dqjburmw17akZ4Zg91bbnjEef15HR4UKHFRTLG5PZLAQSOmJPgpm8FTmL3MxFYRjtOMd+eyFJnhVJKtjqQGDDaiXMNRhubJePltq8oN1ITSfyzxPmpDDimEjySe4PV2kOeOBXEvCq8l/FXOJjtgBpDTWosKr/seSBLxg/VFxdbeJvvq2gUPmEg/vjGkAGKykCuHscTFfnAQRxbNOCtrnHt4ViVLUjDthWZraxfQSXi8bN9eOHPC4/22ehe+itNxfLla6d+qAnaIb22IOjg/stL77HAnv+1X0lvl1swg3dpa4iRWch07+vMZoO5xrNauyC1IaIud7Vd4Xmu/BIk/8n81PBbjd9xbU5Or0dvY3vFb5MoahJn/WdO+eGb2471I2Me+1wRXYBKZpJBJBD6lR6jW248RtKwY1+Tzf+lmw8tIjK26COeSbIFuPZy7y43pY/rYnLydvNNuwHm7RLCDamRxz5d4+mWpaur5Po3XI8BH5/o3Aa2I7G6cS6C6tMxTkqpNjU0hPFD4ngPED2E8PAm95Ut7ZckjXYMSGvj4jXqW61l3RbeG87/U9TtGyOhgE6mFLyitRpzr6qpBz3vatpSuWmqs7bsWGL\",\"nonce\":\"2BmW1spqvh+C63dj9SiW2GVpcPOl1/HQ\",\"v\":1}",
"created_at":1690658663,
"id":"6b37ccbd385668e0ef7d2bc2e666c77090c81a0abdf30b66d8af8cd71791679d",
"kind":1059,
"pubkey":"49af42b938061529473c68b3a19bb0247063adbd6752f5bd29e75cfa2f4dc80c",
"sig":"dacd327dabe19014a8e88e1e705a79ea0333cd4104b52ae7bcfd3fcf8cee68733a36cb38e1f9af225a836c2f38a8fcbf9faf07ca75d34419f037f1efbb2df52f",
"tags":[
[
"p",
"c98a847f6bff5c9917e9de5f641039ca21080d07dfa6434cb109b7587af64aad"
]
]
}
""".trimIndent()
val privateKey = "f2f8c4b9bba181c83c25eeea336fd214a088a383363dc302e622e1ae9851093e".hexToByteArray()
val wrap = Event.fromJson(json, Client.lenient) as GiftWrapEvent
wrap.checkSignature()
assertEquals(CryptoUtils.pubkeyCreate(privateKey).toHexKey(), wrap.recipientPubKey())
val event = wrap.unwrap(privateKey)
assertNotNull(event)
if (event is SealedGossipEvent) {
val innerData = event.unseal(privateKey)
assertEquals("Hola, que tal?", innerData?.content)
} else {
fail("Inner event is not a Sealed Gossip")
}
}
@@ -423,7 +513,7 @@ class GiftWrapEventTest {
if (event is SealedGossipEvent) {
val innerData = event.unseal(privateKey)
assertEquals("Hi", innerData?.content)
assertEquals(null, innerData?.content)
} else {
println(event?.toJson())
fail()

View File

@@ -65,7 +65,7 @@ object CryptoUtils {
}
fun encryptXChaCha(msg: String, privateKey: ByteArray, pubKey: ByteArray): EncryptedInfo {
val sharedSecret = getSharedSecret(privateKey, pubKey)
val sharedSecret = getSharedSecretXChaCha(privateKey, pubKey)
return encryptXChaCha(msg, sharedSecret)
}
@@ -94,7 +94,7 @@ object CryptoUtils {
}
fun decryptXChaCha(encryptedInfo: EncryptedInfo, privateKey: ByteArray, pubKey: ByteArray): String {
val sharedSecret = getSharedSecret(privateKey, pubKey)
val sharedSecret = getSharedSecretXChaCha(privateKey, pubKey)
return decryptXChaCha(encryptedInfo, sharedSecret)
}
@@ -132,6 +132,12 @@ object CryptoUtils {
*/
fun getSharedSecret(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
secp256k1.pubKeyTweakMul(Hex.decode("02") + pubKey, privateKey).copyOfRange(1, 33)
/**
* @return 32B shared secret
*/
fun getSharedSecretXChaCha(privateKey: ByteArray, pubKey: ByteArray): ByteArray =
sha256(secp256k1.pubKeyTweakMul(Hex.decode("02") + pubKey, privateKey).copyOfRange(1, 33))
}
fun Int.toByteArray(): ByteArray {

View File

@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
fun String.isUTF16Char(pos: Int): Boolean {
println("Test $pos ${Character.charCount(this.codePointAt(pos))}")
return Character.charCount(this.codePointAt(pos)) == 2
}

View File

@@ -30,7 +30,7 @@ class GiftWrapEvent(
}
fun unwrap(privKey: ByteArray) = try {
plainContent(privKey)?.let { println("$it"); fromJson(it, Client.lenient) }
plainContent(privKey)?.let { fromJson(it, Client.lenient) }
} catch (e: Exception) {
Log.e("UnwrapError", "Couldn't Decrypt the content", e)
null
@@ -40,17 +40,13 @@ class GiftWrapEvent(
if (content.isBlank()) return null
return try {
val sharedSecret = CryptoUtils.getSharedSecret(privKey, pubKey.hexToByteArray())
val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privKey, pubKey.hexToByteArray())
val toDecrypt = gson.fromJson<EncryptedInfo>(
content,
EncryptedInfo::class.java
)
println(toDecrypt.ciphertext)
println(toDecrypt.nonce)
println(toDecrypt.v)
return CryptoUtils.decryptXChaCha(toDecrypt, sharedSecret)
} catch (e: Exception) {
Log.w("GeneralList", "Error decrypting the message ${e.message}")
@@ -69,7 +65,7 @@ class GiftWrapEvent(
createdAt: Long = TimeUtils.now()
): GiftWrapEvent {
val privateKey = CryptoUtils.privkeyCreate() // GiftWrap is always a random key
val sharedSecret = CryptoUtils.getSharedSecret(privateKey, recipientPubKey.hexToByteArray())
val sharedSecret = CryptoUtils.getSharedSecretXChaCha(privateKey, recipientPubKey.hexToByteArray())
val content = gson.toJson(
CryptoUtils.encryptXChaCha(