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
This commit is contained in:
2021-12-17 12:15:54 +01:00
parent 10ba4cf31b
commit c46c664cdb
4 changed files with 179 additions and 136 deletions

View File

@ -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<String> = 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<String> = 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
}