Skip to content
alex [dot] kramer [at] g_m_a_i_l [dot] com edited this page Oct 12, 2018 · 6 revisions

Cryptography for serialized objects (AES ECB)

Use ECB for short payloads. Use CBC with an IV (initialization vector) for longer payloads.

import com.google.gson.Gson
import org.junit.Assert.assertEquals
import org.junit.Test
import java.util.*
import javax.crypto.*
import javax.crypto.spec.SecretKeySpec

class EncryptionService {
    companion object {
        private val KEY_ALGORITHM = "AES"
        private val CIPHER_ALGORITHM = "AES/ECB/PKCS5PADDING"
        private val base64Encoder = Base64.getEncoder()
        private val base64Decoder = Base64.getDecoder()

        fun generateAesKey(): SecretKey {
            val keyGen = KeyGenerator.getInstance(KEY_ALGORITHM)
            keyGen.init(256)
            val secretKey = keyGen.generateKey()
            return secretKey
        }

        fun encodeAesKeyBase64(key: SecretKey): String {
            return base64Encoder.encodeToString(key.encoded)
        }

        fun decodeAesKeyBase64(keyEncodedAsBase64: String): SecretKey {
            val keyData = base64Decoder.decode(keyEncodedAsBase64)
            return SecretKeySpec(keyData, 0, keyData.size, KEY_ALGORITHM)
        }

        fun encryptToBase64Ciphertext(key: SecretKey, stringToEncrypt: String): String {
            val aesCipher = Cipher.getInstance(CIPHER_ALGORITHM)
            aesCipher.init(Cipher.ENCRYPT_MODE, key)
            val stringToEncryptBytes = stringToEncrypt.toByteArray()
            val ciphertextBytes = aesCipher.doFinal(stringToEncryptBytes)
            return base64Encoder.encodeToString(ciphertextBytes)
        }

        fun decrypt(key: SecretKey, base64Ciphertext: String): String {
            val cipher = Cipher.getInstance(CIPHER_ALGORITHM)
            cipher.init(Cipher.DECRYPT_MODE, key)
            val ciphertext = base64Decoder.decode(base64Ciphertext)
            val decryptedBytes = cipher.doFinal(ciphertext)
            return String(decryptedBytes)
        }
    }
}

class EncryptionTest {
    data class Thingy(
        val field1: String,
        val field2: Double,
        val field3: Long
    )

    @Test
    fun `encrypt all the things`() {
        val key = EncryptionService.generateAesKey()

        // Thingy to serialize and encrypt
        val thingy = Thingy("eleventeen", 3.14159, 42)

        // Serialize thingy
        val thingyJson = Gson().toJson(thingy)

        // Encrypt serialized thingy
        val thingyJsonCiphertextBase64 = EncryptionService.encryptToBase64Ciphertext(key, thingyJson)

        // Encode key (for distribution/storage)
        val encodedKeyBase64 = EncryptionService.encodeAesKeyBase64(key)

        // Decode key
        val decodedKey = EncryptionService.decodeAesKeyBase64(encodedKeyBase64)

        // Decrypt thingy
        val decryptedThingyJson = EncryptionService.decrypt(decodedKey, userDataJsonCiphertextBase64)

        // Deserialize thingy
        val deserializedThingy = Gson().fromJson(decryptedThingyJson, Thingy::class.java)

        // Sanity checks
        assertEquals(key, decodedKey)
        assertEquals(decryptedThingyJson, thingyJson)
        assertEquals(thingy, deserializedThingy)

        println(deserializedThingy)
    }
}
Clone this wiki locally