Szyfrowanie Kotlin ECC

Nov 11 2020

Czy są jakieś informacje na temat szyfrowania krzywej eliptycznej w Kotlin?

Do generowania par kluczy oraz szyfrowania i odszyfrowywania wiadomości.

Niewiele jest informacji na ten temat.

Chcę na przykład zaimplementować krzywą eliptyczną ECC P-521.

Czy jest możliwe użycie wersji Java w Kotlinie?

Jak to realizujemy?

Odpowiedzi

2 Topaco Nov 17 2020 at 23:03

ECC oferuje ECIES, hybrydowy schemat szyfrowania, który łączy asymetryczne szyfrowanie oparte na ECC z szyfrowaniem symetrycznym. Tutaj generowany jest wspólny sekret, z którego pochodzi klucz do symetrycznego szyfrowania danych. Do uwierzytelnienia używany jest adres MAC. ECIES jest określony w różnych standardach kryptograficznych. Więcej szczegółów można znaleźć tutaj .

ECIES wykorzystuje komponenty, które podałeś w swoim pytaniu (wspólny sekret przez ECC, szyfrowanie symetryczne, MAC do uwierzytelniania). Jednak konkretne algorytmy zależą od zastosowanego standardu lub implementacji, więc nie masz nad nimi bezpośredniej kontroli. Jeśli to ci wystarczy, ECIES będzie dobrym rozwiązaniem.

ECIES jest obsługiwany m.in. przez BouncyCastle, który wdraża standard IEEE P 1363a. Aby korzystać z ECIES, należy najpierw dodać BouncyCastle (np. Dla Android Studio w sekcji zależności aplikacji / gradle), zobacz także tutaj :

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

Następujący kod Kotlin wykonuje następnie szyfrowanie / deszyfrowanie za pomocą ECIES i 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))

testowane z poziomem API 28 / Android 9 Pie.


Jeśli chcesz mieć większą kontrolę nad wykorzystywanymi algorytmami, poszczególne komponenty można zaimplementować ręcznie, np

  • ECDH z NIST P-521 w celu ustalenia wspólnego sekretu
  • SHA-512 do określenia klucza AES-256 jako pierwszych 32 bajtów skrótu (zobacz także tutaj użycie KDF, jak w kontekście ECIES)
  • AES-256 / GCM do szyfrowania symetrycznego ( GCM jest już uwierzytelnionym szyfrowaniem, więc jawny adres MAC nie jest konieczny)

Poniższy kod Kotlin wykonuje następnie szyfrowanie / deszyfrowanie za pomocą tych komponentów:

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

z:

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

ponownie przetestowany z API Level 28 / Android 9 Pie.