Chiffrement Kotlin ECC

Nov 11 2020

Existe-t-il des informations sur le chiffrement de courbe elliptique dans Kotlin?

Pour générer des paires de clés et chiffrer, déchiffrer des messages.

Il y a très peu ou pas d'informations sur ce sujet.

Je veux implémenter la courbe elliptique ECC P-521 par exemple.

Est-il possible d'utiliser la version Java dans Kotlin?

Et comment mettre en œuvre cela?

Réponses

2 Topaco Nov 17 2020 at 23:03

ECC propose ECIES, un système de cryptage hybride qui combine un cryptage asymétrique basé sur ECC avec un cryptage symétrique. Ici, un secret partagé est généré à partir duquel une clé pour le cryptage symétrique des données est dérivée. Un MAC est utilisé pour l'authentification. ECIES est spécifié dans diverses normes cryptographiques. Plus de détails peuvent être trouvés ici .

ECIES utilise les composants que vous avez listés dans votre question (secret partagé via ECC, cryptage symétrique, MAC pour l'authentification). Cependant, les algorithmes spécifiques dépendent de la norme ou de l'implémentation utilisée, vous n'avez donc aucun contrôle direct sur eux. Si cela vous suffit, ECIES serait une bonne option.

ECIES est pris en charge par exemple par BouncyCastle, qui implémente la norme IEEE P 1363a. Pour utiliser ECIES, BouncyCastle doit donc d'abord être ajouté (par exemple pour Android Studio dans la section dépendances de app / gradle), voir aussi ici :

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

Le code Kotlin suivant effectue ensuite un cryptage / décryptage avec ECIES et 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))

testé avec le niveau d'API 28 / Android 9 Pie.


Si vous souhaitez avoir plus de contrôle sur les algorithmes utilisés, les composants individuels peuvent être implémentés manuellement, par ex.

  • ECDH avec NIST P-521 pour déterminer le secret partagé
  • SHA-512 pour déterminer la clé AES-256 comme les 32 premiers octets du hachage (voir aussi ici pour l'utilisation d'un KDF comme dans le contexte d'ECIES)
  • AES-256 / GCM pour le cryptage symétrique ( GCM est déjà un cryptage authentifié, donc un MAC explicite n'est pas nécessaire)

Le code Kotlin suivant effectue ensuite un cryptage / décryptage avec ces composants:

// 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))

avec:

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)
}

à nouveau testé avec API niveau 28 / Android 9 Pie.