Crittografia Kotlin ECC

Nov 11 2020

Ci sono informazioni sulla crittografia della curva ellittica all'interno di Kotlin?

Per generare coppie di chiavi e crittografare, decrittografare i messaggi.

Ci sono pochissime informazioni su questo argomento.

Ad esempio, voglio implementare la curva ellittica ECC P-521.

È forse possibile utilizzare la versione Java all'interno di Kotlin?

E come lo implementiamo?

Risposte

2 Topaco Nov 17 2020 at 23:03

ECC offre ECIES, uno schema di crittografia ibrido che combina la crittografia asimmetrica basata su ECC con la crittografia simmetrica. Qui viene generato un segreto condiviso da cui deriva una chiave per la crittografia simmetrica dei dati. Per l'autenticazione viene utilizzato un MAC. ECIES è specificato in vari standard crittografici. Maggiori dettagli possono essere trovati qui .

ECIES utilizza i componenti che hai elencato nella tua domanda (segreto condiviso tramite ECC, crittografia simmetrica, MAC per l'autenticazione). Tuttavia, gli algoritmi specifici dipendono dallo standard o dall'implementazione utilizzata, quindi non hai alcun controllo diretto su di essi. Se questo è abbastanza per te, ECIES sarebbe una buona opzione.

ECIES è supportato ad esempio da BouncyCastle, che implementa lo standard IEEE P 1363a. Per utilizzare ECIES, bisogna quindi prima aggiungere BouncyCastle (es. Per Android Studio nella sezione dipendenze di app / gradle), vedere anche qui :

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

Il seguente codice Kotlin esegue quindi una crittografia / decrittografia con 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))

testato con API livello 28 / Android 9 Pie.


Se si desidera avere un maggiore controllo sugli algoritmi utilizzati, i singoli componenti possono essere implementati manualmente, ad es

  • ECDH con NIST P-521 per determinare il segreto condiviso
  • SHA-512 per determinare la chiave AES-256 come i primi 32 byte dell'hash (vedere anche qui per l'uso di un KDF come nel contesto di ECIES)
  • AES-256 / GCM per la crittografia simmetrica ( GCM è già una crittografia autenticata, quindi non è necessario un MAC esplicito)

Il seguente codice Kotlin esegue quindi una crittografia / decrittografia con questi componenti:

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

nuovamente testato con API Level 28 / Android 9 Pie.