cryptostring.Cryptostringer.kt Maven / Gradle / Ivy
package se.wollan.crypto.cryptostring
import se.wollan.crypto.SymmetricEncryptor
import se.wollan.crypto.fromBase64
import se.wollan.crypto.keyvault.KeyVault
import se.wollan.crypto.toBase64
// It's a crypto-string if it starts with this character
private const val CRYPTO_BOM: Char = '\u2060' // https://en.wikipedia.org/wiki/Word_joiner - \u200B could also be used
// The rest of the crypto-string is always base64 of a byte array where the first byte defines the encoding of the rest
private const val ENC_AES_GCM: Byte = 0x01
interface Cryptostringer {
// noop if already cryptostring
// KeyVaultIsLockedException
suspend fun encode(plaintext: String): String
// noop if already plaintext
// KeyVaultIsLockedException
suspend fun decode(cryptostring: String): String
}
internal class CryptostringerImpl(
private val encryptor: SymmetricEncryptor,
private val keyVault: KeyVault
) : Cryptostringer {
override suspend fun encode(plaintext: String): String {
if (plaintext.startsWith(CRYPTO_BOM))
return plaintext
val secretKey = keyVault.getSecretKey()
val ciphermessage = encryptor.encrypt(plaintext.toByteArray(Charsets.UTF_8), secretKey)
val fullMessage = ByteArray(ciphermessage.size + 1)
fullMessage[0] = ENC_AES_GCM
ciphermessage.copyInto(fullMessage, 1, 0, ciphermessage.size)
return CRYPTO_BOM + fullMessage.toBase64()
}
override suspend fun decode(cryptostring: String): String {
if (!cryptostring.startsWith(CRYPTO_BOM))
return cryptostring
val fullMessage = cryptostring.substring(1).fromBase64()
check(fullMessage.isNotEmpty() && fullMessage[0] == ENC_AES_GCM) { "not a valid crypto-string encoding!" }
val ciphermessage = ByteArray(fullMessage.size - 1)
fullMessage.copyInto(ciphermessage, 0, 1, fullMessage.size)
val secretKey = keyVault.getSecretKey()
val plainbytes = encryptor.decrypt(ciphermessage, secretKey)
val plaintext = plainbytes.toString(Charsets.UTF_8)
return plaintext
}
}