Kotlin ECC-Verschlüsselung

Nov 11 2020

Gibt es Informationen zur elliptischen Kurvenverschlüsselung in Kotlin?

Zum Generieren von Schlüsselpaaren und zum Ver- und Entschlüsseln von Nachrichten.

Es gibt sehr wenig bis gar keine Informationen zu diesem Thema.

Ich möchte zum Beispiel die elliptische Kurve ECC P-521 implementieren.

Ist es vielleicht möglich, die Java-Version in Kotlin zu verwenden?

Und wie setzen wir das um?

Antworten

2 Topaco Nov 17 2020 at 23:03

ECC bietet ECIES an, ein hybrides Verschlüsselungsschema, das ECC-basierte asymmetrische Verschlüsselung mit symmetrischer Verschlüsselung kombiniert. Hier wird ein gemeinsames Geheimnis erzeugt, aus dem ein Schlüssel für die symmetrische Verschlüsselung der Daten abgeleitet wird. Ein MAC wird zur Authentifizierung verwendet. ECIES ist in verschiedenen Kryptostandards spezifiziert. Weitere Details finden Sie hier .

ECIES verwendet die Komponenten, die Sie in Ihrer Frage aufgeführt haben (gemeinsames Geheimnis über ECC, symmetrische Verschlüsselung, MAC zur Authentifizierung). Die spezifischen Algorithmen hängen jedoch vom verwendeten Standard oder der verwendeten Implementierung ab, sodass Sie keine direkte Kontrolle über sie haben. Wenn Ihnen dies ausreicht, ist ECIES eine gute Option.

ECIES wird zB von BouncyCastle unterstützt, das den IEEE P 1363a-Standard implementiert. Um ECIES verwenden zu können, muss daher zuerst BouncyCastle hinzugefügt werden (z. B. für Android Studio im Abschnitt Abhängigkeiten von App / Gradle), siehe auch hier :

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

Der folgende Kotlin-Code führt dann eine Verschlüsselung / Entschlüsselung mit ECIES und NIST P-521 durch:

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

getestet mit API Level 28 / Android 9 Pie.


Wenn Sie mehr Kontrolle über die verwendeten Algorithmen haben möchten, können die einzelnen Komponenten manuell implementiert werden, z

  • ECDH mit NIST P-521 zur Bestimmung des gemeinsamen Geheimnisses
  • SHA-512, um den AES-256-Schlüssel als die ersten 32 Bytes des Hash zu bestimmen (siehe auch hier für die Verwendung eines KDF wie im Kontext von ECIES)
  • AES-256 / GCM für symmetrische Verschlüsselung ( GCM ist bereits eine authentifizierte Verschlüsselung, daher ist kein expliziter MAC erforderlich)

Der folgende Kotlin-Code führt dann eine Ver- / Entschlüsselung mit diesen Komponenten durch:

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

mit:

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

erneut getestet mit API Level 28 / Android 9 Pie.