ApiCleCrypto.kt
80 lignes · 2989 octets
package fr.ebii.card2vcf.sync import org.bouncycastle.crypto.generators.Argon2BytesGenerator import org.bouncycastle.crypto.modes.XChaCha20Poly1305 import org.bouncycastle.crypto.params.AEADParameters import org.bouncycastle.crypto.params.Argon2Parameters import org.bouncycastle.crypto.params.KeyParameter import java.util.Base64 class ApiCleCryptoException(message: String, cause: Throwable? = null) : Exception(message, cause) object ApiCleCrypto { private const val NONCE_LEN = 24 private const val TAG_LEN = 16 private const val KEY_LEN = 32 private const val SALT_LEN = 16 private const val ARGON2_M_KB = 19456 private const val ARGON2_T = 2 private const val ARGON2_P = 1 private const val MAC_BITS = 128 fun decryptApiKey(password: String, saltB64: String, cipherB64: String): String { val salt = decodeB64(saltB64, "sel") if (salt.size != SALT_LEN) { throw ApiCleCryptoException("sel invalide") } val blob = decodeB64(cipherB64, "cle_chiffree") if (blob.size < NONCE_LEN + TAG_LEN) { throw ApiCleCryptoException("blob trop court") } val key = deriveKey(password, salt) val nonce = blob.copyOfRange(0, NONCE_LEN) val ciphertext = blob.copyOfRange(NONCE_LEN, blob.size) val plaintext = try { decryptXChaCha20Poly1305(key, nonce, ciphertext) } catch (e: Exception) { throw ApiCleCryptoException("échec déchiffrement clé API", e) } return plaintext.toString(Charsets.UTF_8) } private fun deriveKey(password: String, salt: ByteArray): ByteArray { val params = Argon2Parameters.Builder(Argon2Parameters.ARGON2_id) .withVersion(Argon2Parameters.ARGON2_VERSION_13) .withIterations(ARGON2_T) .withMemoryAsKB(ARGON2_M_KB) .withParallelism(ARGON2_P) .withSalt(salt) .build() val generator = Argon2BytesGenerator() generator.init(params) val key = ByteArray(KEY_LEN) generator.generateBytes(password.toByteArray(Charsets.UTF_8), key) return key } private fun decryptXChaCha20Poly1305(key: ByteArray, nonce24: ByteArray, ciphertext: ByteArray): ByteArray { require(nonce24.size == NONCE_LEN) val cipher = XChaCha20Poly1305() cipher.init( false, AEADParameters(KeyParameter(key), MAC_BITS, nonce24, null), ) val output = ByteArray(cipher.getOutputSize(ciphertext.size)) var len = cipher.processBytes(ciphertext, 0, ciphertext.size, output, 0) len += cipher.doFinal(output, len) return output.copyOf(len) } private fun decodeB64(value: String, label: String): ByteArray = try { Base64.getDecoder().decode(value) } catch (e: IllegalArgumentException) { throw ApiCleCryptoException("$label base64 invalide", e) } }
GitRust