4

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?

Riccoh
  • 359
  • 1
  • 4
  • 19
  • Can you please describe your use case in more detail? On the one hand you are asking very generally about encryption and decryption of messages (which could also mean ECIES), on the other hand specifically about ECDH, which is used for key exchange, see e.g. [here](https://stackoverflow.com/a/32318931/9014097) for differences. Also, what does 512 bit mean, are you referring to curve NIST P-521 (aka [secp521r1](https://www.secg.org/sec2-v2.pdf), pp. 11)? Is this for Android development? – Topaco Nov 16 '20 at 10:38
  • @Topaco my apology for not being clear. It is Indeed NIST P-521 my mistake. Its for android development correct. II want to use P-521 elliptic curve for the public-key calculations and AES-GCM for symmetric cipher and HMAC-SHA512 for MAC algorithm if its not to much :) But a simple implementation of the Basic ECC for Java/Kotlin will do fine. – Riccoh Nov 16 '20 at 21:05

1 Answers1

10

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.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • 2
    THANKS! I hope this topic will help many people, because it certainly helped me a lot. I can find my way from here. It works like a charm. Thank you for all the information, it makes sense now. – Riccoh Nov 17 '20 at 18:49
  • Tested on API 26, worked for me, thank you! – Someone Aug 07 '21 at 23:52