Fix/Update #1
| @ -2,14 +2,14 @@ group 'fr.pointcheval.native_crypto_android' | |||||||
| version '1.0-SNAPSHOT' | version '1.0-SNAPSHOT' | ||||||
| 
 | 
 | ||||||
| buildscript { | buildscript { | ||||||
|     ext.kotlin_version = '1.3.50' |     ext.kotlin_version = '1.6.21' | ||||||
|     repositories { |     repositories { | ||||||
|         google() |         google() | ||||||
|         mavenCentral() |         mavenCentral() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     dependencies { |     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" |         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,13 +1,18 @@ | |||||||
| package fr.pointcheval.native_crypto_android | package fr.pointcheval.native_crypto_android | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull | 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.embedding.engine.plugins.FlutterPlugin | ||||||
| import io.flutter.plugin.common.MethodCall | import io.flutter.plugin.common.MethodCall | ||||||
| import io.flutter.plugin.common.MethodChannel | import io.flutter.plugin.common.MethodChannel | ||||||
| import io.flutter.plugin.common.MethodChannel.MethodCallHandler | import io.flutter.plugin.common.MethodChannel.MethodCallHandler | ||||||
| import io.flutter.plugin.common.MethodChannel.Result | import io.flutter.plugin.common.MethodChannel.Result | ||||||
|  | import java.util.* | ||||||
| 
 | 
 | ||||||
| /** NativeCryptoAndroidPlugin */ | /** NativeCryptoAndroidPlugin */ | ||||||
| class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { | class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { | ||||||
| @ -16,94 +21,107 @@ class NativeCryptoAndroidPlugin: FlutterPlugin, MethodCallHandler { | |||||||
|     /// This local reference serves to register the plugin with the Flutter Engine and unregister it |     /// This local reference serves to register the plugin with the Flutter Engine and unregister it | ||||||
|     /// when the Flutter Engine is detached from the Activity |     /// when the Flutter Engine is detached from the Activity | ||||||
|     private lateinit var channel: MethodChannel |     private lateinit var channel: MethodChannel | ||||||
|  |     private val name = "plugins.hugop.cl/native_crypto" | ||||||
| 
 | 
 | ||||||
|     override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |     override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { | ||||||
|     channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "plugins.hugop.cl/native_crypto") |         channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) | ||||||
|         channel.setMethodCallHandler(this) |         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 onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { |     override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { | ||||||
|         channel.setMethodCallHandler(null) |         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