Cifrado Kotlin ECC

Nov 11 2020

¿Hay alguna información sobre el cifrado de curvas elípticas dentro de Kotlin?

Para generar pares de claves y cifrar, descifrar mensajes.

Hay muy poca o ninguna información sobre este tema.

Quiero implementar la curva elíptica ECC P-521, por ejemplo.

¿Es posible usar la versión de Java dentro de Kotlin?

¿Y cómo implementamos esto?

Respuestas

2 Topaco Nov 17 2020 at 23:03

ECC ofrece ECIES, un esquema de cifrado híbrido que combina el cifrado asimétrico basado en ECC con el cifrado simétrico. Aquí se genera un secreto compartido del que se deriva una clave para el cifrado simétrico de los datos. Se utiliza una MAC para la autenticación. ECIES se especifica en varios estándares de cifrado. Puede encontrar más detalles aquí .

ECIES utiliza los componentes que enumeró en su pregunta (secreto compartido a través de ECC, cifrado simétrico, MAC para autenticación). Sin embargo, los algoritmos específicos dependen del estándar o la implementación utilizada, por lo que no tiene control directo sobre ellos. Si esto es suficiente para usted, ECIES sería una buena opción.

ECIES es compatible, por ejemplo, con BouncyCastle, que implementa el estándar IEEE P 1363a. Por lo tanto, para usar ECIES, primero debe agregarse BouncyCastle (por ejemplo, para Android Studio en la sección de dependencias de la aplicación / gradle), consulte también aquí :

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

El siguiente código de Kotlin luego realiza un cifrado / descifrado con ECIES y 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))

probado con API nivel 28 / Android 9 Pie.


Si desea tener más control sobre los algoritmos utilizados, los componentes individuales se pueden implementar manualmente, p. Ej.

  • ECDH con NIST P-521 para determinar el secreto compartido
  • SHA-512 para determinar la clave AES-256 como los primeros 32 bytes del hash (ver también aquí el uso de un KDF como en el contexto de ECIES)
  • AES-256 / GCM para cifrado simétrico ( GCM ya es cifrado autenticado, por lo que no es necesario un MAC explícito)

El siguiente código de Kotlin luego realiza un cifrado / descifrado con estos componentes:

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

con:

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

nuevamente probado con API Level 28 / Android 9 Pie.