Fix/Update #1
| @ -2,14 +2,14 @@ group 'fr.pointcheval.native_crypto_android' | ||||
| version '1.0-SNAPSHOT' | ||||
| 
 | ||||
| buildscript { | ||||
|     ext.kotlin_version = '1.3.50' | ||||
|     ext.kotlin_version = '1.6.21' | ||||
|     repositories { | ||||
|         google() | ||||
|         mavenCentral() | ||||
|     } | ||||
| 
 | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:4.1.0' | ||||
|         classpath 'com.android.tools.build:gradle:4.1.3' | ||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,6 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| abstract class Cipher { | ||||
|     abstract fun encrypt(data: ByteArray, key: ByteArray) : ByteArray?; | ||||
|     abstract fun decrypt(data: ByteArray, key: ByteArray) : ByteArray?; | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import java.security.MessageDigest | ||||
| 
 | ||||
| object Hash { | ||||
|     fun digest(data: ByteArray?, algorithm: HashAlgorithm): ByteArray { | ||||
|         val func : String = when (algorithm) { | ||||
|             HashAlgorithm.SHA256 -> "SHA-256" | ||||
|             HashAlgorithm.SHA384 -> "SHA-384" | ||||
|             HashAlgorithm.SHA512 -> "SHA-512" | ||||
|         } | ||||
|         val md = MessageDigest.getInstance(func) | ||||
|         return md.digest(data) | ||||
|     } | ||||
| 
 | ||||
|     fun digest(data: ByteArray?, algorithm: String): ByteArray { | ||||
|         val func : HashAlgorithm = when (algorithm) { | ||||
|             "sha256" -> HashAlgorithm.SHA256 | ||||
|             "sha384" -> HashAlgorithm.SHA384 | ||||
|             "sha512" -> HashAlgorithm.SHA512 | ||||
|             else -> HashAlgorithm.SHA256 | ||||
|         } | ||||
|         return digest(data, func) | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| enum class HashAlgorithm(val length : Int) { | ||||
|     SHA256(256), | ||||
|     SHA384(384), | ||||
|     SHA512(512); | ||||
| 
 | ||||
|     fun hmac(): String { | ||||
|         return when (this) { | ||||
|             HashAlgorithm.SHA256 -> "HmacSHA256" | ||||
|             HashAlgorithm.SHA384 -> "HmacSHA384" | ||||
|             HashAlgorithm.SHA512 -> "HmacSHA512" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import java.security.SecureRandom | ||||
| import javax.crypto.SecretKeyFactory | ||||
| import javax.crypto.spec.PBEKeySpec | ||||
| 
 | ||||
| object Key { | ||||
|      fun fromSecureRandom(bitsCount: Int) : ByteArray { | ||||
|          val bytes = ByteArray(bitsCount / 8) | ||||
|          SecureRandom.getInstanceStrong().nextBytes(bytes) | ||||
|          return bytes | ||||
|      } | ||||
| 
 | ||||
|     fun fromPBKDF2(password: String, salt: String, keyBytesCount: Int, iterations: Int, algorithm: String): ByteArray { | ||||
|         val availableHashAlgorithm: Map<String, String> = mapOf( | ||||
|             "sha256" to "PBKDF2WithHmacSHA256", | ||||
|             "sha384" to "PBKDF2withHmacSHA384", | ||||
|             "sha512" to "PBKDF2withHmacSHA512" | ||||
|         ) | ||||
|         val spec = PBEKeySpec(password.toCharArray(), salt.toByteArray(), iterations, keyBytesCount * 8) | ||||
|         val skf: SecretKeyFactory = SecretKeyFactory.getInstance(availableHashAlgorithm[algorithm]) | ||||
|         return skf.generateSecret(spec).encoded | ||||
|     } | ||||
| } | ||||
| @ -1,109 +1,127 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import androidx.annotation.NonNull | ||||
| import fr.pointcheval.native_crypto_android.ciphers.AESCipher | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.kdf.Pbkdf2 | ||||
| import fr.pointcheval.native_crypto_android.keys.SecretKey | ||||
| import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm | ||||
| import fr.pointcheval.native_crypto_android.utils.Constants | ||||
| import fr.pointcheval.native_crypto_android.utils.HashAlgorithm | ||||
| import fr.pointcheval.native_crypto_android.utils.Task | ||||
| import io.flutter.embedding.engine.plugins.FlutterPlugin | ||||
| import io.flutter.plugin.common.MethodCall | ||||
| import io.flutter.plugin.common.MethodChannel | ||||
| import io.flutter.plugin.common.MethodChannel.MethodCallHandler | ||||
| import io.flutter.plugin.common.MethodChannel.Result | ||||
| import java.util.* | ||||
| 
 | ||||
| /** NativeCryptoAndroidPlugin */ | ||||
| class NativeCryptoAndroidPlugin: FlutterPlugin, MethodCallHandler { | ||||
|   /// The MethodChannel that will the communication between Flutter and native Android | ||||
|   /// | ||||
|   /// This local reference serves to register the plugin with the Flutter Engine and unregister it | ||||
|   /// when the Flutter Engine is detached from the Activity | ||||
|   private lateinit var channel : MethodChannel | ||||
| class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { | ||||
|     /// The MethodChannel that will the communication between Flutter and native Android | ||||
|     /// | ||||
|     /// This local reference serves to register the plugin with the Flutter Engine and unregister it | ||||
|     /// when the Flutter Engine is detached from the Activity | ||||
|     private lateinit var channel: MethodChannel | ||||
|     private val name = "plugins.hugop.cl/native_crypto" | ||||
| 
 | ||||
|   override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { | ||||
|     channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "plugins.hugop.cl/native_crypto") | ||||
|     channel.setMethodCallHandler(this) | ||||
|   } | ||||
| 
 | ||||
|   override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { | ||||
|     when (call.method) { | ||||
|       "digest" -> { | ||||
|         if (!call.hasArgument("data")) result.error("DATA_NULL", null, null); | ||||
|         if (!call.hasArgument("algorithm")) result.error("ALGORITHM_NULL", null, null); | ||||
| 
 | ||||
|         val data : ByteArray = call.argument<ByteArray>("data")!! | ||||
|         val algorithm : String = call.argument<String>("algorithm")!! | ||||
| 
 | ||||
|         result.success(Hash.digest(data, algorithm)) | ||||
|       } | ||||
|       "generateSecretKey" -> { | ||||
|         if (!call.hasArgument("bitsCount")) result.error("SIZE_NULL", null, null); | ||||
| 
 | ||||
|         val bitsCount : Int = call.argument<Int>("bitsCount")!! | ||||
| 
 | ||||
|         result.success(Key.fromSecureRandom(bitsCount)) | ||||
|       } | ||||
|       "generateKeyPair" -> { | ||||
|         result.notImplemented() | ||||
|       } | ||||
|       "pbkdf2" -> { | ||||
|         if (!call.hasArgument("password")) result.error("PASSWORD_NULL", null, null); | ||||
|         if (!call.hasArgument("salt")) result.error("SALT_NULL", null, null); | ||||
|         if (!call.hasArgument("keyBytesCount")) result.error("SIZE_NULL", null, null); | ||||
|         if (!call.hasArgument("iterations")) result.error("ITERATIONS_NULL", null, null); | ||||
|         if (!call.hasArgument("algorithm")) result.error("ALGORITHM_NULL", null, null); | ||||
| 
 | ||||
|         val password : String = call.argument<String>("password")!! | ||||
|         val salt : String = call.argument<String>("salt")!! | ||||
|         val keyBytesCount : Int = call.argument<Int>("keyBytesCount")!! | ||||
|         val iterations : Int = call.argument<Int>("iterations")!! | ||||
|         val algorithm : String = call.argument<String>("algorithm")!! | ||||
| 
 | ||||
|         result.success(Key.fromPBKDF2(password, salt, keyBytesCount, iterations, algorithm)) | ||||
|       } | ||||
|       "encrypt" -> { | ||||
|         if (!call.hasArgument("data")) result.error("DATA_NULL", null, null); | ||||
|         if (!call.hasArgument("key")) result.error("KEY_NULL", null, null); | ||||
|         if (!call.hasArgument("algorithm")) result.error("ALGORITHM_NULL", null, null); | ||||
| 
 | ||||
|         val data : ByteArray = call.argument<ByteArray>("data")!! | ||||
|         val key : ByteArray = call.argument<ByteArray>("key")!! | ||||
|         val algorithm : String = call.argument<String>("algorithm")!! | ||||
| 
 | ||||
|         if (algorithm == "aes") { | ||||
|           result.success(AESCipher().encrypt(data, key)) | ||||
|         } | ||||
|       } | ||||
|       "decrypt" ->  { | ||||
|         if (!call.hasArgument("data")) result.error("DATA_NULL", null, null); | ||||
|         if (!call.hasArgument("key")) result.error("KEY_NULL", null, null); | ||||
|         if (!call.hasArgument("algorithm")) result.error("ALGORITHM_NULL", null, null); | ||||
| 
 | ||||
|         val data : ByteArray = call.argument<ByteArray>("data")!! | ||||
|         val key : ByteArray = call.argument<ByteArray>("key")!! | ||||
|         val algorithm : String = call.argument<String>("algorithm")!! | ||||
| 
 | ||||
|         if (algorithm == "aes") { | ||||
|           result.success(AESCipher().decrypt(data, key)) | ||||
|         } | ||||
|       } | ||||
|       "generateSharedSecretKey" -> { | ||||
|         if (!call.hasArgument("salt")) result.error("SALT_NULL", null, null); | ||||
|         if (!call.hasArgument("keyBytesCount")) result.error("SIZE_NULL", null, null); | ||||
|         if (!call.hasArgument("ephemeralPrivateKey")) result.error("PRIVATE_KEY_NULL", null, null); | ||||
|         if (!call.hasArgument("otherPublicKey")) result.error("PUBLIC_KEY_NULL", null, null); | ||||
|         if (!call.hasArgument("hkdfAlgorithm")) result.error("ALGORITHM_NULL", null, null); | ||||
| 
 | ||||
|         val salt : ByteArray = call.argument<ByteArray>("salt")!! | ||||
|         val keyBytesCount : Int = call.argument<Int>("keyBytesCount")!! | ||||
|         val ephemeralPrivateKey : ByteArray = call.argument<ByteArray>("ephemeralPrivateKey")!! | ||||
|         val otherPublicKey : ByteArray = call.argument<ByteArray>("otherPublicKey")!! | ||||
|         val hkdfAlgorithm : String = call.argument<String>("hkdfAlgorithm")!! | ||||
| 
 | ||||
|         result.notImplemented() | ||||
|       } | ||||
|       else -> result.notImplemented() | ||||
|     override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { | ||||
|         channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) | ||||
|         channel.setMethodCallHandler(this) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||||
|     channel.setMethodCallHandler(null) | ||||
|   } | ||||
|     override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||||
|         channel.setMethodCallHandler(null) | ||||
|     } | ||||
| 
 | ||||
|     override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { | ||||
|         lateinit var methodCallTask: Task<*> | ||||
| 
 | ||||
|         when (call.method) { | ||||
|             "digest" -> methodCallTask = handleDigest(call.arguments()) | ||||
|             "generateSecretKey" -> methodCallTask = handleGenerateSecretKey(call.arguments()) | ||||
|             "generateKeyPair" -> result.notImplemented() | ||||
|             "pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments()) | ||||
|             "encrypt" -> methodCallTask = handleEncrypt(call.arguments()) | ||||
|             "decrypt" -> methodCallTask = handleDecrypt(call.arguments()) | ||||
|             "generateSharedSecretKey" -> result.notImplemented() | ||||
|             else -> result.notImplemented() | ||||
|         } | ||||
| 
 | ||||
|         methodCallTask.call() | ||||
| 
 | ||||
|         methodCallTask.finalize { task -> | ||||
|             if (task.isSuccessful()) { | ||||
|                 result.success(task.getResult()) | ||||
|             } else { | ||||
|                 val exception: Exception = task.getException() | ||||
|                 val message = exception.message | ||||
|                 result.error("native_crypto", message, null) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleDigest(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val data: ByteArray = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray | ||||
|             val algorithm: String = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String | ||||
|             HashAlgorithm.digest(data, algorithm) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleGenerateSecretKey(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val bitsCount: Int = Objects.requireNonNull(arguments?.get(Constants.BITS_COUNT)) as Int | ||||
|             SecretKey.fromSecureRandom(bitsCount).bytes | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handlePbkdf2(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val password: String = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.PASSWORD)) as String | ||||
|             val salt: String = Objects.requireNonNull(arguments?.get(Constants.SALT)) as String | ||||
|             val keyBytesCount: Int = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.KEY_BYTES_COUNT)) as Int | ||||
|             val iterations: Int = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.ITERATIONS)) as Int | ||||
|             val algorithm: String = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String | ||||
| 
 | ||||
|             val pbkdf2: Pbkdf2 = Pbkdf2(keyBytesCount, iterations, HashAlgorithm.valueOf(algorithm)) | ||||
|             pbkdf2.init(password, salt) | ||||
| 
 | ||||
|             pbkdf2.derive().bytes | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleEncrypt(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val data: ByteArray = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray | ||||
|             val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray | ||||
|             val algorithm: String = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String | ||||
| 
 | ||||
|             val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) | ||||
|             val cipher = cipherAlgorithm.getCipher() | ||||
| 
 | ||||
|             cipher.encrypt(data, key) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleDecrypt(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val data: ByteArray = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray | ||||
|             val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray | ||||
|             val algorithm: String = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String | ||||
| 
 | ||||
|             val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) | ||||
|             val cipher = cipherAlgorithm.getCipher() | ||||
| 
 | ||||
|             cipher.decrypt(data, key) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,59 @@ | ||||
| package fr.pointcheval.native_crypto_android.ciphers | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.interfaces.Cipher | ||||
| import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm | ||||
| import javax.crypto.SecretKey | ||||
| import javax.crypto.spec.GCMParameterSpec | ||||
| import javax.crypto.spec.IvParameterSpec | ||||
| import javax.crypto.spec.SecretKeySpec | ||||
| 
 | ||||
| class AES : Cipher { | ||||
|     override val algorithm: CipherAlgorithm | ||||
|         get() = CipherAlgorithm.aes | ||||
| 
 | ||||
| /*    override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") | ||||
|         cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-16) || TAG(16)] | ||||
|         val bytes = cipher.doFinal(data) | ||||
|         val iv = cipher.iv.copyOf()                                                 // 12 bytes nonce | ||||
|         // native.crypto representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)] | ||||
|         return iv.plus(bytes) | ||||
|     } | ||||
| 
 | ||||
|     override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         // native.crypto representation = [NONCE(12) || CIPHERTEXT(n-16) || TAG(16)] | ||||
|         val iv: ByteArray = data.take(12).toByteArray() | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-28) || TAG(16)] | ||||
|         val payload: ByteArray = data.drop(12).toByteArray() | ||||
|         val spec = GCMParameterSpec(16 * 8, iv) | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") | ||||
|         cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, spec) | ||||
|         return cipher.doFinal(payload) | ||||
|     }*/ | ||||
| 
 | ||||
|     override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") | ||||
|         cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-16)] | ||||
|         val bytes = cipher.doFinal(data) | ||||
|         val iv = cipher.iv | ||||
|         // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] | ||||
|         return iv.plus(bytes) | ||||
|     } | ||||
| 
 | ||||
|     override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         // native.crypto representation = [IV(16) || CIPHERTEXT(n-16)] | ||||
|         val iv: ByteArray = data.take(16).toByteArray() | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-16)] | ||||
|         val payload: ByteArray = data.drop(16).toByteArray() | ||||
|         val ivSpec = IvParameterSpec(iv) | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding") | ||||
|         cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec) | ||||
|         return cipher.doFinal(payload) | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android.ciphers | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.Cipher | ||||
| import javax.crypto.SecretKey | ||||
| import javax.crypto.spec.GCMParameterSpec | ||||
| import javax.crypto.spec.SecretKeySpec | ||||
| 
 | ||||
| class AESCipher : Cipher() { | ||||
|     override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") | ||||
|         cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-28) || TAG(16)] | ||||
|         val bytes = cipher.doFinal(data) | ||||
|         val iv = cipher.iv.copyOf()                                                 // 12 bytes nonce | ||||
|         // native.crypto representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)] | ||||
|         return iv.plus(bytes) | ||||
|     } | ||||
| 
 | ||||
|     override fun decrypt(data: ByteArray, key: ByteArray): ByteArray? { | ||||
|         val sk: SecretKey = SecretKeySpec(key, "AES") | ||||
|         // native.crypto representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)] | ||||
|         val iv = data.sliceArray(IntRange(0,11))                              // 12 bytes nonce | ||||
|         // javax.crypto representation = [CIPHERTEXT(n-28) || TAG(16)] | ||||
|         val payload = data.sliceArray(IntRange(12, data.size - 1)) | ||||
|         val spec = GCMParameterSpec(16 * 8, iv) | ||||
|         val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") | ||||
|         cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, spec) | ||||
|         return cipher.doFinal(payload) | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package fr.pointcheval.native_crypto_android.interfaces | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm | ||||
| 
 | ||||
| interface Cipher { | ||||
|     val algorithm: CipherAlgorithm | ||||
| 
 | ||||
|     fun encrypt(data: ByteArray, key: ByteArray): ByteArray | ||||
|     fun decrypt(data: ByteArray, key: ByteArray): ByteArray | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| package fr.pointcheval.native_crypto_android.interfaces | ||||
| 
 | ||||
| interface Key { | ||||
|     val bytes: ByteArray | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package fr.pointcheval.native_crypto_android.interfaces | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.keys.SecretKey | ||||
| import fr.pointcheval.native_crypto_android.utils.KdfAlgorithm | ||||
| 
 | ||||
| interface KeyDerivation { | ||||
|     val algorithm: KdfAlgorithm | ||||
| 
 | ||||
|     fun derive(): SecretKey | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| package fr.pointcheval.native_crypto_android.kdf | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.interfaces.KeyDerivation | ||||
| import fr.pointcheval.native_crypto_android.keys.SecretKey | ||||
| import fr.pointcheval.native_crypto_android.utils.HashAlgorithm | ||||
| import fr.pointcheval.native_crypto_android.utils.KdfAlgorithm | ||||
| import javax.crypto.SecretKeyFactory | ||||
| import javax.crypto.spec.PBEKeySpec | ||||
| 
 | ||||
| class Pbkdf2( | ||||
|     private val keyBytesCount: Int, private val iterations: Int, | ||||
|     private val hash: HashAlgorithm = HashAlgorithm.sha256 | ||||
| ) : KeyDerivation { | ||||
| 
 | ||||
|     private var password: String? = null | ||||
|     private var salt: String? = null | ||||
| 
 | ||||
|     fun init(password: String, salt: String) { | ||||
|         this.password = password | ||||
|         this.salt = salt | ||||
|     } | ||||
| 
 | ||||
|     override val algorithm: KdfAlgorithm | ||||
|         get() = KdfAlgorithm.pbkdf2 | ||||
| 
 | ||||
|     override fun derive(): SecretKey { | ||||
|         if (password == null || salt == null) { | ||||
|             throw Exception("Password and Salt must be initialized.") | ||||
|         } | ||||
|         val spec = PBEKeySpec( | ||||
|             password!!.toCharArray(), | ||||
|             salt!!.toByteArray(), | ||||
|             iterations, | ||||
|             keyBytesCount * 8 | ||||
|         ) | ||||
|         val skf: SecretKeyFactory = SecretKeyFactory.getInstance(hash.pbkdf2String()) | ||||
|         return SecretKey(skf.generateSecret(spec).encoded) | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package fr.pointcheval.native_crypto_android.keys | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.interfaces.Key | ||||
| import java.security.SecureRandom | ||||
| 
 | ||||
| class SecretKey(override val bytes: ByteArray) : Key { | ||||
|     companion object { | ||||
|         fun fromSecureRandom(bitsCount: Int): SecretKey { | ||||
|             val bytes = ByteArray(bitsCount / 8) | ||||
|             SecureRandom.getInstanceStrong().nextBytes(bytes) | ||||
|             return SecretKey(bytes) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.ciphers.AES | ||||
| import fr.pointcheval.native_crypto_android.interfaces.Cipher | ||||
| 
 | ||||
| enum class CipherAlgorithm { | ||||
|     aes; | ||||
| 
 | ||||
|     fun getCipher(): Cipher { | ||||
|         return when (this) { | ||||
|             aes -> AES() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| object Constants { | ||||
|     const val ALGORITHM = "algorithm" | ||||
|     const val BITS_COUNT = "bitsCount" | ||||
|     const val DATA = "data" | ||||
|     const val PASSWORD = "password" | ||||
|     const val SALT = "salt" | ||||
|     const val KEY = "key" | ||||
|     const val KEY_BYTES_COUNT = "keyBytesCount" | ||||
|     const val ITERATIONS = "iterations" | ||||
|     const val EPHEMERAL_PRIVATE_KEY = "ephemeralPrivateKey" | ||||
|     const val OTHER_PUBLIC_KEY = "otherPublicKey" | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| import java.security.MessageDigest | ||||
| 
 | ||||
| @Suppress("EnumEntryName") | ||||
| enum class HashAlgorithm(val bitsCount: Int) { | ||||
|     sha256(256), | ||||
|     sha384(384), | ||||
|     sha512(512); | ||||
| 
 | ||||
|     fun messageDigestString(): String { | ||||
|         return when (this) { | ||||
|             sha256 -> "SHA-256" | ||||
|             sha384 -> "SHA-384" | ||||
|             sha512 -> "SHA-512" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun hmacString(): String { | ||||
|         return when (this) { | ||||
|             sha256 -> "HmacSHA256" | ||||
|             sha384 -> "HmacSHA384" | ||||
|             sha512 -> "HmacSHA512" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun pbkdf2String(): String { | ||||
|         return when (this) { | ||||
|             sha256 -> "PBKDF2WithHmacSHA256" | ||||
|             sha384 -> "PBKDF2WithHmacSHA384" | ||||
|             sha512 -> "PBKDF2WithHmacSHA512" | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun digest(data: ByteArray): ByteArray { | ||||
|         val md = MessageDigest.getInstance(messageDigestString()) | ||||
|         return md.digest(data) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         fun digest(data: ByteArray, algorithm: String): ByteArray { | ||||
|             for (h in values()) { | ||||
|                 if (h.name == algorithm) { | ||||
|                     return h.digest(data) | ||||
|                 } | ||||
|             } | ||||
|             throw Exception("Unknown HashAlgorithm: $algorithm") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| enum class KdfAlgorithm { | ||||
|     pbkdf2 | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| class Task<T>(private var task: () -> T) { | ||||
| 
 | ||||
|     private var successful = false | ||||
|     private var result: T? = null | ||||
|     private var exception: Exception? = null | ||||
| 
 | ||||
|     fun isSuccessful(): Boolean { | ||||
|         return successful | ||||
|     } | ||||
| 
 | ||||
|     fun getResult(): T { | ||||
|         if (successful && result != null) { | ||||
|             return result!! | ||||
|         } else { | ||||
|             throw Exception("No result found!") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getException(): Exception { | ||||
|         if (exception != null) { | ||||
|             return exception!! | ||||
|         } else { | ||||
|             throw Exception("No exception found!") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun call() { | ||||
|         try { | ||||
|             result = task() | ||||
|             exception = null | ||||
|             successful = true | ||||
|         } catch (e: Exception) { | ||||
|             exception = e | ||||
|             result = null | ||||
|             successful = false | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun finalize(callback: (task: Task<T>) -> Unit) { | ||||
|         callback(this) | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user