Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/androidTest/java/com/afkanerd/.DS_Store
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public void testCanStoreAndEncrypt() throws NoSuchAlgorithmException, NoSuchProv
// .build());
//
// KeyPair keyPair = kpg.generateKeyPair();
PublicKey publicKey = SecurityRSA.generateKeyPair(keystoreAlias, 2048);
KeyPair keyPair = KeystoreHelpers.getKeyPairFromKeystore(keystoreAlias);

SecretKey secretKey = SecurityAES.generateSecretKey(256);
byte[] cipherText = SecurityRSA.encrypt(keyPair.getPublic(), secretKey.getEncoded());
byte[] plainText = SecurityRSA.decrypt(keyPair.getPrivate(), cipherText);
assertArrayEquals(secretKey.getEncoded(), plainText);
// PublicKey publicKey = SecurityRSA.generateKeyPair(keystoreAlias, 2048);
// KeyPair keyPair = KeystoreHelpers.getKeyPairFromKeystore(keystoreAlias);
//
// SecretKey secretKey = SecurityAES.generateSecretKey(256);
// byte[] cipherText = SecurityRSA.encrypt(keyPair.getPublic(), secretKey.getEncoded());
// byte[] plainText = SecurityRSA.decrypt(keyPair.getPrivate(), cipherText);
// assertArrayEquals(secretKey.getEncoded(), plainText);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
package com.afkanerd.smswithoutborders.libsignal_doubleratchet

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.test.filters.SmallTest
import junit.framework.TestCase.assertEquals
import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.Signature

@SmallTest
class SecurityX25519Test {
@Test
fun keystoreEd25519() {
val keystoreAlias = "keystoreAlias"
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
keystoreAlias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
build()
}

kpg.initialize(parameterSpec)
val kp = kpg.generateKeyPair()

val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
val entry: KeyStore.Entry = ks.getEntry(keystoreAlias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
throw Exception("No instance of keystore")
}

val data = "Hello world".encodeToByteArray()
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
initSign(entry.privateKey)
update(data)
sign()
}

}

@Test
fun sharedSecret() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.core.util.component1
import androidx.core.util.component2
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.CryptoHelpers
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.SecurityCurve25519
import org.junit.Assert.assertArrayEquals
import org.junit.Test
Expand All @@ -14,6 +15,108 @@ import java.security.SecureRandom
class RatchetsTest {
var context: Context =
InstrumentationRegistry.getInstrumentation().targetContext
@Test
fun completeRatchetHETest() {
val aliceEphemeralKeyPair = SecurityCurve25519()
val aliceEphemeralHeaderKeyPair = SecurityCurve25519()
val aliceEphemeralNextHeaderKeyPair = SecurityCurve25519()

val bobStaticKeyPair = SecurityCurve25519()
val bobEphemeralKeyPair = SecurityCurve25519()
val bobEphemeralHeaderKeyPair = SecurityCurve25519()
val bobEphemeralNextHeaderKeyPair = SecurityCurve25519()

val aliceNonce = CryptoHelpers.generateRandomBytes(16)
val bobNonce = CryptoHelpers.generateRandomBytes(16)

val (aliceSk, aliceSkH, aliceSkNh) = SecurityCurve25519(aliceEphemeralKeyPair.privateKey)
.agreeWithAuthAndNonce(
authenticationPublicKey = bobStaticKeyPair.generateKey(),
authenticationPrivateKey = null,
headerPrivateKey = aliceEphemeralHeaderKeyPair.privateKey,
nextHeaderPrivateKey = aliceEphemeralNextHeaderKeyPair.privateKey,
publicKey = bobEphemeralKeyPair.generateKey(),
headerPublicKey = bobEphemeralHeaderKeyPair.generateKey(),
nextHeaderPublicKey = bobEphemeralNextHeaderKeyPair.generateKey(),
salt = "RelaySMS v1".encodeToByteArray(),
nonce1 = aliceNonce,
nonce2 = bobNonce,
info = "RelaySMS C2S DR v1".encodeToByteArray()
)

val (bobSk, bobSkH, bobSkNh) = SecurityCurve25519(bobEphemeralKeyPair.privateKey)
.agreeWithAuthAndNonce(
authenticationPublicKey = null,
authenticationPrivateKey = bobStaticKeyPair.privateKey,
headerPrivateKey = bobEphemeralHeaderKeyPair.privateKey,
nextHeaderPrivateKey = bobEphemeralNextHeaderKeyPair.privateKey,
publicKey = aliceEphemeralKeyPair.generateKey(),
headerPublicKey = aliceEphemeralHeaderKeyPair.generateKey(),
nextHeaderPublicKey = aliceEphemeralNextHeaderKeyPair.generateKey(),
salt = "RelaySMS v1".encodeToByteArray(),
nonce1 = aliceNonce,
nonce2 = bobNonce,
info = "RelaySMS C2S DR v1".encodeToByteArray()
)

assertArrayEquals(aliceSk, bobSk)
assertArrayEquals(aliceSkH, bobSkH)
assertArrayEquals(aliceSkNh, bobSkNh)

val aliceState = States()
RatchetsHE.ratchetInitAlice(
state = aliceState,
SK = aliceSk,
bobDhPublicKey = bobEphemeralKeyPair.generateKey(),
sharedHka = aliceSkH,
sharedNhkb = aliceSkNh
)

val bobState = States()
RatchetsHE.ratchetInitBob(
state = bobState,
SK = bobSk,
bobDhPublicKeypair = bobEphemeralKeyPair.getKeypair(),
sharedHka = bobSkH,
sharedNhkb = bobSkNh
)

val originalText = SecureRandom.getSeed(32);
val (encHeader, aliceCipherText) = RatchetsHE.ratchetEncrypt(
aliceState,
originalText,
bobStaticKeyPair.generateKey()
)

var encHeader1: ByteArray? = null
var aliceCipherText1: ByteArray? = null
for(i in 1..10) {
val (encHeader2, aliceCipherText2) = RatchetsHE.ratchetEncrypt(
aliceState,
originalText,
bobStaticKeyPair.generateKey()
)
encHeader1 = encHeader2
aliceCipherText1 = aliceCipherText2
}

val bobPlainText = RatchetsHE.ratchetDecrypt(
state = bobState,
encHeader = encHeader,
cipherText = aliceCipherText,
AD = bobStaticKeyPair.generateKey()
)

val bobPlainText1 = RatchetsHE.ratchetDecrypt(
state = bobState,
encHeader = encHeader1!!,
cipherText = aliceCipherText1!!,
AD = bobStaticKeyPair.generateKey()
)

assertArrayEquals(originalText, bobPlainText)
assertArrayEquals(originalText, bobPlainText1)
}

@Test
fun completeRatchetTest() {
Expand Down Expand Up @@ -48,7 +151,7 @@ class RatchetsTest {

val bobPlainText1 = Ratchets.ratchetDecrypt(bobState, header1, aliceCipherText1,
bob.generateKey())
println(bobState.serializedStates)
println(bobState.serialize())

assertArrayEquals(originalText, bobPlainText)
assertArrayEquals(originalText, bobPlainText1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.afkanerd.smswithoutborders.libsignal_doubleratchet.libsignal

import androidx.test.filters.SmallTest
import junit.framework.TestCase.assertEquals
import kotlinx.serialization.json.Json
import org.junit.Test
import java.security.SecureRandom

Expand All @@ -12,11 +13,8 @@ class StateTest {
val state = States()
state.DHs = android.util.Pair(SecureRandom.getSeed(32),
SecureRandom.getSeed(32))
val serializedStates = state.serializedStates
println("Encoded values: $serializedStates")
val state1 = States(serializedStates)
println(state1.serializedStates)

assertEquals(state, state1)
val serializedStates = Json.encodeToString(state)
val deserializedStates = Json.decodeFromString<States>(serializedStates)
assertEquals(state, deserializedStates)
}
}
Binary file added src/main/java/com/afkanerd/.DS_Store
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.afkanerd.smswithoutborders.libsignal_doubleratchet;

import android.util.Base64;

import com.google.common.primitives.Bytes;

import java.security.GeneralSecurityException;
Expand Down Expand Up @@ -30,7 +28,7 @@ public static byte[] getCipherMacParameters(String ALGO, byte[] mk) throws Gener

public static Mac buildVerificationHash(byte[] authKey, byte[] AD, byte[] cipherText) throws GeneralSecurityException {
Mac mac = CryptoHelpers.HMAC256(authKey);
byte[] updatedParams = Bytes.concat(AD, cipherText);
byte[] updatedParams = (AD == null) ? cipherText : Bytes.concat(AD, cipherText);
mac.update(updatedParams);
return mac;
}
Expand Down Expand Up @@ -85,9 +83,7 @@ public static Mac HMAC256(byte[] data) throws GeneralSecurityException {

public static byte[] generateRandomBytes(int length) {
SecureRandom random = new SecureRandom();
byte[] bytes = new

byte[length];
byte[] bytes = new byte[length];
random.nextBytes(bytes);
return bytes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ object EncryptionController {
android.util.Pair(keypair.second, keypair.first)
)
}
else state = States(String(currentState))
else state = States.deserialize(String(currentState))

val keypair = context.getKeypairValues(address)
var decryptedText: String?
Expand All @@ -215,7 +215,7 @@ object EncryptionController {
keypair.first
))
context.saveBinaryDataEncrypted(keystore,
state.serializedStates.encodeToByteArray())
state.serialize().encodeToByteArray())
} catch(e: Exception) {
throw e
}
Expand Down Expand Up @@ -253,7 +253,7 @@ object EncryptionController {
val sk = context.calculateSharedSecret(address, publicKeyBytes)
Ratchets.ratchetInitAlice(state, sk, publicKeyBytes)
}
else state = States(String(currentState))
else state = States.deserialize(String(currentState))

val ratchetOutput = Ratchets.ratchetEncrypt(state,
text.encodeToByteArray(), publicKeyBytes)
Expand All @@ -264,7 +264,7 @@ object EncryptionController {
ratchetOutput.second
)
context.saveBinaryDataEncrypted(keystore,
state.serializedStates.encodeToByteArray())
state.serialize().encodeToByteArray())
Base64.encodeToString(message, Base64.DEFAULT)
} catch(e: Exception) {
throw e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,109 @@ class SecurityCurve25519(val privateKey: ByteArray = Curve25519.generateRandomKe
return Curve25519.publicKey(this.privateKey)
}

fun calculateSharedSecret(publicKey: ByteArray): ByteArray {
private fun agreeWithAuthAndNonceImpl(
authenticationPublicKey: ByteArray?,
authenticationPrivateKey: ByteArray?,
publicKey: ByteArray,
salt: ByteArray,
info: ByteArray,
handshakeSalt: ByteArray,
privateKey: ByteArray? = null,
): ByteArray {
val privateKey = privateKey ?: this.privateKey
val dh1 = if(authenticationPrivateKey == null)
Curve25519.sharedSecret(privateKey, authenticationPublicKey)
else
Curve25519.sharedSecret(authenticationPrivateKey, publicKey)
val dh2 = Curve25519.sharedSecret(privateKey, publicKey)
var ck = CryptoHelpers.HKDF(
"HMACSHA256",
handshakeSalt,
salt,
info,
32,
1
)[0]
ck = CryptoHelpers.HKDF(
"HMACSHA256",
dh1,
ck,
info,
32,
1
)[0]
return CryptoHelpers.HKDF(
"HMACSHA256",
dh2,
ck,
info,
32,
1
)[0]
}

fun agreeWithAuthAndNonce(
authenticationPublicKey: ByteArray?,
authenticationPrivateKey: ByteArray?,
headerPrivateKey: ByteArray,
nextHeaderPrivateKey: ByteArray,
publicKey: ByteArray,
headerPublicKey: ByteArray,
nextHeaderPublicKey: ByteArray,
salt: ByteArray,
nonce1: ByteArray,
nonce2: ByteArray,
info: ByteArray,
): Triple<ByteArray, ByteArray, ByteArray> {
val handshakeSalt = nonce1 + nonce2
val headerInfo = "RelaySMS C2S DRHE v1".encodeToByteArray()

val rootKey = agreeWithAuthAndNonceImpl(
authenticationPublicKey = authenticationPublicKey,
authenticationPrivateKey = authenticationPrivateKey,
publicKey = publicKey,
salt = salt,
info = info,
handshakeSalt = handshakeSalt,
)

val headerKey = agreeWithAuthAndNonceImpl(
authenticationPublicKey = authenticationPublicKey,
authenticationPrivateKey = authenticationPrivateKey,
publicKey = headerPublicKey,
salt = salt,
info = headerInfo,
handshakeSalt = handshakeSalt,
privateKey = headerPrivateKey
)

val nextHeaderKey = agreeWithAuthAndNonceImpl(
authenticationPublicKey = authenticationPublicKey,
authenticationPrivateKey = authenticationPrivateKey,
publicKey = nextHeaderPublicKey,
salt = salt,
info = headerInfo,
handshakeSalt = handshakeSalt,
privateKey = nextHeaderPrivateKey
)

return Triple(rootKey, headerKey, nextHeaderKey)
}

fun calculateSharedSecret(
publicKey: ByteArray,
salt: ByteArray? = null,
info: ByteArray? = "x25591_key_exchange".encodeToByteArray(),
): ByteArray {
val sharedKey = Curve25519.sharedSecret(this.privateKey, publicKey)
return CryptoHelpers.HKDF("HMACSHA256", sharedKey, null,
"x25591_key_exchange".encodeToByteArray(), 32, 1)[0]
return CryptoHelpers.HKDF(
"HMACSHA256",
sharedKey,
salt,
info,
32,
1
)[0]
}

fun getKeypair(): android.util.Pair<ByteArray, ByteArray> {
Expand Down
Loading