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:
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user