From c46c664cdb5d25f6a79ad126726e9ed173d5a497 Mon Sep 17 00:00:00 2001 From: raphael Date: Fri, 17 Dec 2021 12:15:54 +0100 Subject: [PATCH] restful push and pop with cryptography - integration test pushing and poping a message and comparing decrypted strings passes - integration test comparing encrypted and decrypted string passes --- .../example/bump/ExampleInstrumentedTest.kt | 108 ------------------ .../com/example/bump/MessageProcessorTest.kt | 39 +++++++ .../java/com/example/bump/RestCryptTest.kt | 69 +++++++++++ .../java/com/example/bump/MessageProcessor.kt | 99 +++++++++++----- 4 files changed, 179 insertions(+), 136 deletions(-) delete mode 100644 app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt create mode 100644 app/src/androidTest/java/com/example/bump/MessageProcessorTest.kt create mode 100644 app/src/androidTest/java/com/example/bump/RestCryptTest.kt diff --git a/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt deleted file mode 100644 index 5bc4f78..0000000 --- a/app/src/androidTest/java/com/example/bump/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.maenle.bump - -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.example.bump.MessageProcessor -import com.example.bump.RestSingleton - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.maenle.bump", appContext.packageName) - } -} - -@RunWith(AndroidJUnit4::class) -class MessageProcessorTest { - @Test - fun decryptMessages() { - val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" - val messageRaw = "M1dEAxKZ5HUHCJoRkgGOvAABhqCAAAAAAGG2eKTSlKXWLDQx5B_wssZsNwsanzQID2UyUm4KKuKYKgfwH5MG2N-qzt6K4mg3pfZmWPaiDB9PiqlX236k6zo9Yvvq" - val data = decryptMessage(code, messageRaw) - assertEquals(data, "hello") - } - - companion object { - @JvmStatic - fun decryptMessage(code: String, messageRaw: String): String { - val message = MessageProcessor(code) - val data = message.decrypt(messageRaw) - return data - } - } -} - -@RunWith(AndroidJUnit4::class) -class RestTest{ - @Test - fun getPeek() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val sender = "dydoes-unknowledgeable-indiscretion-househusbands" - RestSingleton.getInstance(context).peek(sender) { i -> assertEquals(i, "hello") } - - } -} -class IntegrationTest{ - @Test - fun pushPopAndDecrypt() { - val testMessage = "Hi There Sir" - val sender = "dydoes-unknowledgeable-indiscretion-househusbands" - val context = InstrumentationRegistry.getInstrumentation().targetContext - val lock = CountDownLatch(1); - - fun messageTester(encrypted: String) { - val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" - Log.d("TEST", encrypted) - val message = MessageProcessor(code) - val data = message.decrypt(encrypted) - Log.d("TEST", "data") - assertEquals(data, testMessage) - lock.countDown() - } - - RestSingleton.getInstance(context).push(sender, testMessage) { - RestSingleton.getInstance(context).pop(sender) { i -> messageTester(i) } - } - Log.d("TEST", "waiting") - lock.await(20000, TimeUnit.MILLISECONDS) - Log.d("TEST", "done") - - } - - @Test - fun popAndDectypt() { - val testMessage = "Hi There Sir" - val sender = "raphael" - val context = InstrumentationRegistry.getInstrumentation().targetContext - val lock = CountDownLatch(1); - - - fun messageTester(encrypted: String) { - val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" - val message = MessageProcessor(code) - val data = message.decrypt(encrypted) - assertEquals(data, testMessage) - lock.countDown() - } - - RestSingleton.getInstance(context).pop(sender) { i -> messageTester(i) } - lock.await(20000, TimeUnit.MILLISECONDS) - - } -} diff --git a/app/src/androidTest/java/com/example/bump/MessageProcessorTest.kt b/app/src/androidTest/java/com/example/bump/MessageProcessorTest.kt new file mode 100644 index 0000000..15f9e35 --- /dev/null +++ b/app/src/androidTest/java/com/example/bump/MessageProcessorTest.kt @@ -0,0 +1,39 @@ +package com.example.bump + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MessageProcessorTest { + + @Test + fun decryptMessage() { + val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" + // val messageRaw = "M1dEAxKZ5HUHCJoRkgGOvAABhqCAAAAAAGG2eKTSlKXWLDQx5B_wssZsNwsanzQID2UyUm4KKuKYKgfwH5MG2N-qzt6K4mg3pfZmWPaiDB9PiqlX236k6zo9Yvvq" + val messageRaw = "M1dEAxKZ5HUHCJoRkgGOvAABhqCAAAAAAGG8afPPk380EzwcbGzNoTr_I4y6YT8hnUYcToinlgsVkaUx5K-JicdS5epZenOX4u8vVhhMvR0ebeWm_mgp6LZvTw8S" + val data = decryptMessage(code, messageRaw) + Assert.assertEquals(data, "hello") + } + + @Test + fun encryptAndDecryptMessage() { + val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" + val message = MessageProcessor(code) + + val test = getRandomString(32) + val encrypted = message.encrypt(test) + val decrypted = decryptMessage(code, encrypted) + Assert.assertEquals(test, decrypted) + } + + companion object { + @JvmStatic + fun decryptMessage(code: String, messageData: String): String { + val message = MessageProcessor(code) + val data = message.decrypt(messageData) + return data + } + } +} diff --git a/app/src/androidTest/java/com/example/bump/RestCryptTest.kt b/app/src/androidTest/java/com/example/bump/RestCryptTest.kt new file mode 100644 index 0000000..b0f54cf --- /dev/null +++ b/app/src/androidTest/java/com/example/bump/RestCryptTest.kt @@ -0,0 +1,69 @@ +package com.example.bump + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.json.JSONObject + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.maenle.bump", appContext.packageName) + } +} + + +class RestCryptTest{ + @Test + fun pushPopAndDecrypt() { + val code = "dydoes-unknowledgeable-indiscretion-househusbands-pot-walloper-indiscretion-discophorous-transcriptions-dydoes-poodle-faker-transcriptions-budlike" + val testMessage = getRandomString(32) + val context = InstrumentationRegistry.getInstrumentation().targetContext + var encrypted = "" + + val lock = CountDownLatch(1) + + + fun messageTester(messageEncrypted: String) { + val message = MessageProcessor(code) + + val messageData: String = JSONObject(messageEncrypted).get("data").toString() + assertEquals(messageData, encrypted) + val data = message.decrypt(messageData) + assertEquals(data, testMessage) + lock.countDown() + } + + val message = MessageProcessor(code) + encrypted = message.encrypt(testMessage) + RestSingleton.getInstance(context).push(message.sender, encrypted) { + RestSingleton.getInstance(context).pop(message.sender) { i -> messageTester(i) } + } + + lock.await(200000, TimeUnit.MILLISECONDS) + + + } + +} + +fun getRandomString(length: Int) : String { + val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + (' ') + ('_') + ('-') + return (1..length) + .map { allowedChars.random() } + .joinToString("") +} diff --git a/app/src/main/java/com/example/bump/MessageProcessor.kt b/app/src/main/java/com/example/bump/MessageProcessor.kt index d928cfc..5bd1133 100644 --- a/app/src/main/java/com/example/bump/MessageProcessor.kt +++ b/app/src/main/java/com/example/bump/MessageProcessor.kt @@ -9,11 +9,12 @@ import com.macasaet.fernet.StringValidator import com.macasaet.fernet.Validator import com.maenle.bump.MainActivity import java.math.BigInteger +import java.security.SecureRandom import java.time.Duration import java.time.temporal.TemporalAmount -class MessageProcessor(code: String) { - private var sender: String +class MessageProcessor(code: String, private val salt: ByteArray? = null) { + var sender: String private var password: String init { @@ -28,30 +29,61 @@ class MessageProcessor(code: String) { return code.split("-").size >= KEY_LENGTH + SENDER_LENGTH } + fun encrypt(decrypted: String): String { + val message = MessageEncrypt(decrypted) + salt?.let { message.updateSalt(salt) } + return message.encryptWith(password) + } fun decrypt(messageRaw: String): String { - val message = Message(messageRaw) - val fernetKey: Key = message.deriveMessageKeyFromPassword(password) - return message.validateAndDecrypt(fernetKey) + val message = MessageDecrypt(messageRaw) + return message.validateAndDecryptWith(password) } - private class Message(messageRaw : String) { - lateinit var salt : ByteArray - var iterations: Int = 100_00 - lateinit var token : Token + private class MessageEncrypt(val messageDecrypted: String) : Message() { + fun encryptWith(password: String): String { + val fernetKey = deriveMessageKeyFromPassword(password) + val token = Token.generate(fernetKey, messageDecrypted) + val tokenString = Base64.getUrlDecoder().decode(token.serialise()) - init { - splitMessage(messageRaw) + val iterationBuffer = ByteArray(4) + for (i in 0..3) iterationBuffer[i] = (iterations shr (i*8)).toByte() + iterationBuffer.reverse() + + val data = salt + iterationBuffer + tokenString + return String(Base64.getUrlEncoder().encode(data)) } + } - private fun splitMessage(message : String) { + + private class MessageDecrypt(messageRaw: String) : Message() { + init { + super.splitMessage(messageRaw) + } + fun validateAndDecryptWith(password: String, timeToLive: Long = TIME_TO_LIVE): String { + val fernetKey = deriveMessageKeyFromPassword(password) + val validator: Validator = object : StringValidator { + override fun getTimeToLive(): TemporalAmount { + return Duration.ofHours(timeToLive) + } + } + return token.validateAndDecrypt(fernetKey, validator) + } + } + + + abstract class Message(var salt: ByteArray = freshSalt, var iterations: Int = ITERATIONS) { + lateinit var token: Token + + protected fun splitMessage(message : String) { val decodedMessage : ByteArray = Base64.getUrlDecoder().decode(message) + + salt = decodedMessage.copyOfRange(0, SALT_LENGTH) val iterationsDecoded = decodedMessage.copyOfRange(SALT_LENGTH, SALT_LENGTH+ ITERATIONS_LENGTH) val tokenString = String(Base64.getUrlEncoder().encode(decodedMessage.copyOfRange(20, decodedMessage.size))) iterations = BigInteger(iterationsDecoded).toInt() - salt = decodedMessage.copyOfRange(0, SALT_LENGTH) token = Token.fromString(tokenString) } @@ -75,7 +107,24 @@ class MessageProcessor(code: String) { return result } - fun deriveMessageKeyFromPassword(password: String): Key{ + fun updateSalt(newSalt: ByteArray) { + + if(newSalt.size == SALT_LENGTH) { + salt = newSalt + } + } + + companion object { + val freshSalt: ByteArray + get() + { + val salt = ByteArray(16) + SecureRandom().nextBytes(salt) + return salt + } + } + + fun deriveMessageKeyFromPassword(password: String): Key { val derivedKeyLength = 256 val spec = PBEKeySpec(password.toCharArray(), salt, iterations, derivedKeyLength) val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") @@ -84,25 +133,19 @@ class MessageProcessor(code: String) { return Key(saltedKey) } - - fun validateAndDecrypt(fernetKey: Key, timeToLive: Long = 24*365*1000): String { - - val validator: Validator = object : StringValidator { - override fun getTimeToLive(): TemporalAmount { - return Duration.ofHours(timeToLive) - } - } - return token.validateAndDecrypt(fernetKey, validator) - } } @Suppress("unused") companion object { private val TAG = MainActivity::class.java.simpleName - const val KEY_LENGTH = 8 - const val SENDER_LENGTH = 4 - const val SALT_LENGTH = 16 - const val ITERATIONS_LENGTH = 4 + const val KEY_LENGTH: Int = 8 + const val SENDER_LENGTH: Int = 4 + const val SALT_LENGTH: Int = 16 + const val ITERATIONS_LENGTH: Int = 4 + + const val ITERATIONS: Int = 100_000 + const val TIME_TO_LIVE: Long = 1000 * 365 * 24 + }