Riccoh
Riccoh

Reputation: 401

Kotlin ECC Encryption

Is there any information about Elliptic Curve Encryption within Kotlin?

For generating key pairs and encrypting, decrypting messages.

There is very little to non information about this topic.

I want to implement the ECC P-521 elliptic curve for example.

Is it maybe possible to use the Java version within Kotlin?

And how do we implement this?

Upvotes: 5

Views: 2808

Answers (1)

Topaco
Topaco

Reputation: 49276

ECC offers ECIES, a hybrid encryption scheme that combines ECC based asymmetric encryption with symmetric encryption. Here a shared secret is generated from which a key for the symmetric encryption of the data is derived. A MAC is used for authentication. ECIES is specified in various crypto standards. More details can be found here.

ECIES uses the components you listed in your question (shared secret via ECC, symmetric encryption, MAC for authentication). However, the specific algorithms depend on the standard or implementation used, so you have no direct control over them. If this is enough for you, ECIES would be a good option.

ECIES is supported e.g. by BouncyCastle, which implements the IEEE P 1363a standard. To use ECIES, BouncyCastle must therefore first be added (e.g. for the Android Studio in the dependencies section of app/gradle), see also here:

implementation 'org.bouncycastle:bcprov-jdk15to18:1.67'

The following Kotlin code then performs an encryption/decryption with ECIES and NIST P-521:

// Add BouncyCastle
Security.removeProvider("BC")
Security.addProvider(BouncyCastleProvider())

// Key Pair Generation
val keyPairGenerator = KeyPairGenerator.getInstance("ECDH")
keyPairGenerator.initialize(ECGenParameterSpec("secp521r1"))
val keyPair = keyPairGenerator.generateKeyPair()

// Encryption
val plaintext = "The quick brown fox jumps over the lazy dog".toByteArray(StandardCharsets.UTF_8)
val cipherEnc = Cipher.getInstance("ECIES")
cipherEnc.init(Cipher.ENCRYPT_MODE, keyPair.public) // In practice, the public key of the recipient side is used
val ciphertext = cipherEnc.doFinal(plaintext)

// Decryption
val cipherDec = Cipher.getInstance("ECIES")
cipherDec.init(Cipher.DECRYPT_MODE, keyPair.private)
val decrypted = cipherDec.doFinal(ciphertext)
println(String(decrypted, StandardCharsets.UTF_8))

tested with API level 28 / Android 9 Pie.


If you want to have more control over the algorithms used, the individual components can be implemented manually, e.g.

  • ECDH with NIST P-521 to determine the shared secret
  • SHA-512 to determine the AES-256 key as the first 32 bytes of the hash (see also here for the use of a KDF as in the context of ECIES)
  • AES-256/GCM for symmetric encryption (GCM is already authenticated encryption, so an explicit MAC is not necessary)

The following Kotlin code then performs an encryption/decryption with these components:

// Generate Keys
val keyPairA = generateKeyPair()
val keyPairB = generateKeyPair()

// Generate shared secrets
val sharedSecretA = getSharedSecret(keyPairA.private, keyPairB.public)
val sharedSecretB = getSharedSecret(keyPairB.private, keyPairA.public)

// Generate AES-keys
val aesKeyA = getAESKey(sharedSecretA)
val aesKeyB = getAESKey(sharedSecretB)

// Encryption (WLOG by A)
val plaintextA = "The quick brown fox jumps over the lazy dog".toByteArray(StandardCharsets.UTF_8)
val ciphertextA = encrypt(aesKeyA, plaintextA)

// Decryption (WLOG by B)
val plaintextB = decrypt(aesKeyB, ciphertextA)
println(String(plaintextB, StandardCharsets.UTF_8))

with:

private fun generateKeyPair(): KeyPair {
    val keyPairGenerator = KeyPairGenerator.getInstance("EC")
    keyPairGenerator.initialize(ECGenParameterSpec("secp521r1"))
    return keyPairGenerator.generateKeyPair()
}

private fun getSharedSecret(privateKey: PrivateKey, publicKey: PublicKey): ByteArray {
    val keyAgreement = KeyAgreement.getInstance("ECDH")
    keyAgreement.init(privateKey)
    keyAgreement.doPhase(publicKey, true)
    return keyAgreement.generateSecret()
}

private fun getAESKey(sharedSecret: ByteArray): ByteArray {
    val digest = MessageDigest.getInstance("SHA-512")
    return digest.digest(sharedSecret).copyOfRange(0, 32)
}

private fun encrypt(aesKey: ByteArray, plaintext: ByteArray): ByteArray {
    val secretKeySpec = SecretKeySpec(aesKey, "AES")
    val iv = ByteArray(12) // Create random IV, 12 bytes for GCM
    SecureRandom().nextBytes(iv)
    val gCMParameterSpec = GCMParameterSpec(128, iv)
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gCMParameterSpec)
    val ciphertext = cipher.doFinal(plaintext)
    val ivCiphertext = ByteArray(iv.size + ciphertext.size) // Concatenate IV and ciphertext (the MAC is implicitly appended to the ciphertext)
    System.arraycopy(iv, 0, ivCiphertext, 0, iv.size)
    System.arraycopy(ciphertext, 0, ivCiphertext, iv.size, ciphertext.size)
    return ivCiphertext
}

private fun decrypt(aesKey: ByteArray, ivCiphertext: ByteArray): ByteArray {
    val secretKeySpec = SecretKeySpec(aesKey, "AES")
    val iv = ivCiphertext.copyOfRange(0, 12) // Separate IV
    val ciphertext = ivCiphertext.copyOfRange(12, ivCiphertext.size) // Separate ciphertext (the MAC is implicitly separated from the ciphertext)
    val gCMParameterSpec = GCMParameterSpec(128, iv)
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gCMParameterSpec)
    return cipher.doFinal(ciphertext)
}

again tested with API Level 28 / Android 9 Pie.

Upvotes: 14

Related Questions