pto.0.3.1.source-code.SymmetricEncryption.kt Maven / Gradle / Ivy
The newest version!
package se.wollan.crypto
import java.nio.ByteBuffer
import javax.crypto.AEADBadTagException
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.random.Random
internal interface SymmetricEncryptor {
fun encrypt(plaintext: ByteArray, encryptionKey: SecretKey): ByteArray
/**
* @throws BadSecretKeyException on invalid secret key
*/
fun decrypt(ciphermessage: ByteArray, decryptionKey: SecretKey): ByteArray
}
class BadSecretKeyException : Exception()
// https://gist.github.com/patrickfav/7e28d4eb4bf500f7ee8012c4a0cf7bbf
// Same settings as https://mprimi.github.io/portable-secret/creator/ but without padding
internal class AesGcmEncryptor(private val random: Random) : SymmetricEncryptor {
companion object {
private const val IV_LEN_BYTES = 16
private const val AUTH_TAG_LEN_BYTES = 16
private const val ALG = "AES"
private const val TRANSFORMATION = "AES/GCM/NoPadding"
}
override fun encrypt(plaintext: ByteArray, encryptionKey: SecretKey): ByteArray {
val secretKeySpec = SecretKeySpec(encryptionKey.value, ALG)
val iv = random.nextBytes(IV_LEN_BYTES) //NEVER REUSE THIS IV WITH SAME KEY
val cipher = Cipher.getInstance(TRANSFORMATION)
val paramSpec = GCMParameterSpec(AUTH_TAG_LEN_BYTES * 8, iv)
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, paramSpec)
val ciphertext = cipher.doFinal(plaintext)
// save IV as first bytes
val ciphermessage = ByteBuffer.allocate(iv.size + ciphertext.size)
ciphermessage.put(iv)
ciphermessage.put(ciphertext)
return ciphermessage.array()
}
override fun decrypt(ciphermessage: ByteArray, decryptionKey: SecretKey): ByteArray {
val secretKeySpec = SecretKeySpec(decryptionKey.value, ALG)
val cipher = Cipher.getInstance(TRANSFORMATION)
// first bytes are IV
val paramSpec = GCMParameterSpec(AUTH_TAG_LEN_BYTES * 8, ciphermessage, 0, IV_LEN_BYTES)
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, paramSpec)
try {
// use everything else as ciphertext
return cipher.doFinal(ciphermessage, IV_LEN_BYTES, ciphermessage.size - IV_LEN_BYTES)
} catch (_: AEADBadTagException) {
throw BadSecretKeyException()
}
}
}