Criptografia Kotlin ECC

Nov 11 2020

Existe alguma informação sobre a criptografia de curva elíptica no Kotlin?

Para gerar pares de chaves e criptografar e descriptografar mensagens.

Há muito pouca ou nenhuma informação sobre este tópico.

Desejo implementar a curva elíptica ECC P-521, por exemplo.

É possível usar a versão Java dentro do Kotlin?

E como podemos implementar isso?

Respostas

2 Topaco Nov 17 2020 at 23:03

ECC oferece ECIES, um esquema de criptografia híbrido que combina criptografia assimétrica baseada em ECC com criptografia simétrica. Aqui, um segredo compartilhado é gerado a partir do qual uma chave para a criptografia simétrica dos dados é derivada. Um MAC é usado para autenticação. ECIES é especificado em vários padrões de criptografia. Mais detalhes podem ser encontrados aqui .

ECIES usa os componentes que você listou em sua pergunta (segredo compartilhado via ECC, criptografia simétrica, MAC para autenticação). No entanto, os algoritmos específicos dependem do padrão ou implementação usada, portanto, você não tem controle direto sobre eles. Se isso for suficiente para você, ECIES seria uma boa opção.

ECIES é suportado, por exemplo, pelo BouncyCastle, que implementa o padrão IEEE P 1363a. Para usar ECIES, BouncyCastle deve primeiro ser adicionado (por exemplo, para o Android Studio na seção de dependências do app / gradle), veja também aqui :

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

O seguinte código Kotlin realiza uma criptografia / descriptografia com ECIES e 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))

testado com API de nível 28 / Android 9 Pie.


Se você quiser ter mais controle sobre os algoritmos usados, os componentes individuais podem ser implementados manualmente, por exemplo

  • ECDH com NIST P-521 para determinar o segredo compartilhado
  • SHA-512 para determinar a chave AES-256 como os primeiros 32 bytes do hash (veja também aqui o uso de um KDF no contexto de ECIES)
  • AES-256 / GCM para criptografia simétrica ( GCM já é criptografia autenticada, portanto, um MAC explícito não é necessário)

O código Kotlin a seguir executa uma criptografia / descriptografia com estes 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))

com:

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

novamente testado com API Nível 28 / Android 9 Pie.