v2 #12
| @ -47,4 +47,5 @@ android { | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||
|     implementation 'androidx.documentfile:documentfile:1.0.1' | ||||
| } | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| // -- | ||||
| // Autogenerated from Pigeon (v9.0.0), do not edit directly. | ||||
| // Autogenerated from Pigeon (v9.2.0), do not edit directly. | ||||
| // See also: https://pub.dev/packages/pigeon | ||||
| 
 | ||||
| package fr.pointcheval.native_crypto_android; | ||||
| @ -26,15 +26,40 @@ import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| /** Generated class from Pigeon. */ | ||||
| @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) | ||||
| @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) | ||||
| public class GeneratedAndroidNativeCrypto { | ||||
| 
 | ||||
|   /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ | ||||
|   public static class FlutterError extends RuntimeException { | ||||
| 
 | ||||
|     /** The error code. */ | ||||
|     public final String code; | ||||
| 
 | ||||
|     /** The error details. Must be a datatype supported by the api codec. */ | ||||
|     public final Object details; | ||||
| 
 | ||||
|     public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details)  | ||||
|     { | ||||
|       super(message); | ||||
|       this.code = code; | ||||
|       this.details = details; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @NonNull | ||||
|   private static ArrayList<Object> wrapError(@NonNull Throwable exception) { | ||||
|     ArrayList<Object> errorList = new ArrayList<Object>(3); | ||||
|     if (exception instanceof FlutterError) { | ||||
|       FlutterError error = (FlutterError) exception; | ||||
|       errorList.add(error.code); | ||||
|       errorList.add(error.getMessage()); | ||||
|       errorList.add(error.details); | ||||
|     } else { | ||||
|       errorList.add(exception.toString()); | ||||
|       errorList.add(exception.getClass().getSimpleName()); | ||||
|       errorList.add( | ||||
|         "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); | ||||
|     } | ||||
|     return errorList; | ||||
|   } | ||||
| 
 | ||||
| @ -1339,16 +1364,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 HashRequest requestArg = (HashRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   HashResponse output = api.hash(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1366,16 +1388,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 HmacRequest requestArg = (HmacRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   HmacResponse output = api.hmac(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1393,16 +1412,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 GenerateSecureRandomRequest requestArg = (GenerateSecureRandomRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   GenerateSecureRandomResponse output = api.generateSecureRandom(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1420,16 +1436,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 Pbkdf2Request requestArg = (Pbkdf2Request) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   Pbkdf2Response output = api.pbkdf2(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1447,16 +1460,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 EncryptRequest requestArg = (EncryptRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   EncryptResponse output = api.encrypt(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1474,16 +1484,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 DecryptRequest requestArg = (DecryptRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   DecryptResponse output = api.decrypt(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1501,16 +1508,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 EncryptFileRequest requestArg = (EncryptFileRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   EncryptFileResponse output = api.encryptFile(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1528,16 +1532,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 DecryptFileRequest requestArg = (DecryptFileRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   DecryptFileResponse output = api.decryptFile(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
| @ -1555,16 +1556,13 @@ public class GeneratedAndroidNativeCrypto { | ||||
|           channel.setMessageHandler( | ||||
|               (message, reply) -> { | ||||
|                 ArrayList<Object> wrapped = new ArrayList<Object>(); | ||||
|                 try { | ||||
|                 ArrayList<Object> args = (ArrayList<Object>) message; | ||||
|                   assert args != null; | ||||
|                 EncryptWithIVRequest requestArg = (EncryptWithIVRequest) args.get(0); | ||||
|                   if (requestArg == null) { | ||||
|                     throw new NullPointerException("requestArg unexpectedly null."); | ||||
|                   } | ||||
|                 try { | ||||
|                   EncryptResponse output = api.encryptWithIV(requestArg); | ||||
|                   wrapped.add(0, output); | ||||
|                 } catch (Error | RuntimeException exception) { | ||||
|                 } | ||||
|  catch (Throwable exception) { | ||||
|                   ArrayList<Object> wrappedError = wrapError(exception); | ||||
|                   wrapped = wrappedError; | ||||
|                 } | ||||
|  | ||||
| @ -0,0 +1,118 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import android.content.Context | ||||
| import fr.pointcheval.native_crypto_android.ciphers.AES | ||||
| import fr.pointcheval.native_crypto_android.kdf.Pbkdf2 | ||||
| import fr.pointcheval.native_crypto_android.utils.FileParameters | ||||
| import fr.pointcheval.native_crypto_android.utils.HashAlgorithmParser | ||||
| import java.security.SecureRandom | ||||
| 
 | ||||
| class NativeCrypto(private val context: Context) : NativeCryptoAPI { | ||||
|     override fun hash(data: ByteArray, algorithm: HashAlgorithm): ByteArray? { | ||||
|         val md = HashAlgorithmParser.getMessageDigest(algorithm) | ||||
| 
 | ||||
|         return md.digest(data) | ||||
|     } | ||||
| 
 | ||||
|     override fun hmac(data: ByteArray, key: ByteArray, algorithm: HashAlgorithm): ByteArray? { | ||||
|         val mac = HashAlgorithmParser.getMac(algorithm) | ||||
|         val secretKey = javax.crypto.spec.SecretKeySpec(key, mac.algorithm) | ||||
|         mac.init(secretKey) | ||||
| 
 | ||||
|         return mac.doFinal(data) | ||||
|     } | ||||
| 
 | ||||
|     override fun generateSecureRandom(length: Long): ByteArray { | ||||
|         val bytes = ByteArray(length.toInt()) | ||||
|         SecureRandom.getInstanceStrong().nextBytes(bytes) | ||||
| 
 | ||||
|         return bytes | ||||
|     } | ||||
| 
 | ||||
|     override fun pbkdf2( | ||||
|         password: ByteArray, | ||||
|         salt: ByteArray, | ||||
|         length: Long, | ||||
|         iterations: Long, | ||||
|         algorithm: HashAlgorithm | ||||
|     ): ByteArray? { | ||||
|         val pbkdf2 = Pbkdf2(length.toInt(), iterations.toInt(), algorithm) | ||||
|         pbkdf2.init(password, salt) | ||||
| 
 | ||||
|         return pbkdf2.derive() | ||||
|     } | ||||
| 
 | ||||
|     override fun encrypt( | ||||
|         plainText: ByteArray, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): ByteArray { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
| 
 | ||||
|         return aes.encrypt(plainText, key, null) | ||||
|     } | ||||
| 
 | ||||
|     override fun encryptWithIV( | ||||
|         plainText: ByteArray, | ||||
|         iv: ByteArray, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): ByteArray { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
| 
 | ||||
|         return aes.encrypt(plainText, key, iv) | ||||
|     } | ||||
| 
 | ||||
|     override fun decrypt( | ||||
|         cipherText: ByteArray, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): ByteArray { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
| 
 | ||||
|         return aes.decrypt(cipherText, key) | ||||
|     } | ||||
| 
 | ||||
|     override fun encryptFile( | ||||
|         plainTextPath: String, | ||||
|         cipherTextPath: String, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): Boolean { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
|         val params = FileParameters(context, plainTextPath, cipherTextPath) | ||||
| 
 | ||||
|         return aes.encryptFile(params, key, null) | ||||
|     } | ||||
| 
 | ||||
|     override fun encryptFileWithIV( | ||||
|         plainTextPath: String, | ||||
|         cipherTextPath: String, | ||||
|         iv: ByteArray, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): Boolean { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
|         val params = FileParameters(context, plainTextPath, cipherTextPath) | ||||
| 
 | ||||
|         return aes.encryptFile(params, key, iv) | ||||
|     } | ||||
| 
 | ||||
|     override fun decryptFile( | ||||
|         cipherTextPath: String, | ||||
|         plainTextPath: String, | ||||
|         key: ByteArray, | ||||
|         algorithm: CipherAlgorithm | ||||
|     ): Boolean { | ||||
|         // For now, only AES is supported | ||||
|         val aes = AES() | ||||
|         val params = FileParameters(context, plainTextPath, cipherTextPath) | ||||
| 
 | ||||
|         return aes.decryptFile(params, key) | ||||
|     } | ||||
| } | ||||
| @ -1,160 +1,19 @@ | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import androidx.annotation.NonNull | ||||
| import fr.pointcheval.native_crypto_android.interfaces.Cipher | ||||
| 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 | ||||
|     private val name = "plugins.hugop.cl/native_crypto" | ||||
| class NativeCryptoAndroidPlugin : FlutterPlugin { | ||||
|     private var nativeCrypto: NativeCrypto? = null | ||||
| 
 | ||||
|     private var cipherInstance: Cipher? = null | ||||
| 
 | ||||
|     override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { | ||||
|         channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) | ||||
|         channel.setMethodCallHandler(this) | ||||
|     override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { | ||||
|         val context = flutterPluginBinding.applicationContext | ||||
|         nativeCrypto = NativeCrypto(context) | ||||
|         NativeCryptoAPI.setUp(flutterPluginBinding.binaryMessenger, nativeCrypto) | ||||
|     } | ||||
| 
 | ||||
|     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()) | ||||
|             "pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments()) | ||||
|             "encryptAsList" -> methodCallTask = handleEncryptAsList(call.arguments()) | ||||
|             "decryptAsList" -> methodCallTask = handleDecryptAsList(call.arguments()) | ||||
|             "encrypt" -> methodCallTask = handleCrypt(call.arguments(), true) | ||||
|             "decrypt" -> methodCallTask = handleCrypt(call.arguments(), false) | ||||
|             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 lazyLoadCipher(cipherAlgorithm: CipherAlgorithm) { | ||||
|         if (cipherInstance == null) { | ||||
|             cipherInstance = cipherAlgorithm.getCipher() | ||||
|         } else { | ||||
|             if (cipherInstance!!.algorithm != cipherAlgorithm) { | ||||
|                 cipherInstance = cipherAlgorithm.getCipher() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleEncryptAsList(arguments: Map<String, Any>?): Task<List<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) | ||||
|             lazyLoadCipher(cipherAlgorithm) | ||||
| 
 | ||||
|             cipherInstance!!.encryptAsList(data, key) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun handleDecryptAsList(arguments: Map<String, Any>?): Task<ByteArray> { | ||||
|         return Task { | ||||
|             val data: List<ByteArray> = | ||||
|                 Objects.requireNonNull(arguments?.get(Constants.DATA)) as List<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) | ||||
|             lazyLoadCipher(cipherAlgorithm) | ||||
| 
 | ||||
|             cipherInstance!!.decryptAsList(data, key) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // **EN**Crypt and **DE**Crypt | ||||
|     private fun handleCrypt(arguments: Map<String, Any>?, forEncryption: Boolean): 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) | ||||
|             lazyLoadCipher(cipherAlgorithm) | ||||
| 
 | ||||
|             if (forEncryption) { | ||||
|                 cipherInstance!!.encrypt(data, key) | ||||
|             } else { | ||||
|                 cipherInstance!!.decrypt(data, key) | ||||
|             } | ||||
|         } | ||||
|     override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { | ||||
|         NativeCryptoAPI.setUp(binding.binaryMessenger, null) | ||||
|         nativeCrypto = null | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,300 @@ | ||||
| // Copyright 2019-2023 Hugo Pointcheval | ||||
| //  | ||||
| // Use of this source code is governed by an MIT-style | ||||
| // license that can be found in the LICENSE file or at | ||||
| // https://opensource.org/licenses/MIT. | ||||
| // -- | ||||
| // Autogenerated from Pigeon (v9.2.0), do not edit directly. | ||||
| // See also: https://pub.dev/packages/pigeon | ||||
| 
 | ||||
| package fr.pointcheval.native_crypto_android | ||||
| 
 | ||||
| import android.util.Log | ||||
| import io.flutter.plugin.common.BasicMessageChannel | ||||
| import io.flutter.plugin.common.BinaryMessenger | ||||
| import io.flutter.plugin.common.MessageCodec | ||||
| import io.flutter.plugin.common.StandardMessageCodec | ||||
| import java.io.ByteArrayOutputStream | ||||
| import java.nio.ByteBuffer | ||||
| 
 | ||||
| private fun wrapResult(result: Any?): List<Any?> { | ||||
|   return listOf(result) | ||||
| } | ||||
| 
 | ||||
| private fun wrapError(exception: Throwable): List<Any?> { | ||||
|   if (exception is FlutterError) { | ||||
|     return listOf( | ||||
|       exception.code, | ||||
|       exception.message, | ||||
|       exception.details | ||||
|     ) | ||||
|   } else { | ||||
|     return listOf( | ||||
|       exception.javaClass.simpleName, | ||||
|       exception.toString(), | ||||
|       "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) | ||||
|     ) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Error class for passing custom error details to Flutter via a thrown PlatformException. | ||||
|  * @property code The error code. | ||||
|  * @property message The error message. | ||||
|  * @property details The error details. Must be a datatype supported by the api codec. | ||||
|  */ | ||||
| class FlutterError ( | ||||
|   val code: String, | ||||
|   override val message: String? = null, | ||||
|   val details: Any? = null | ||||
| ) : Throwable() | ||||
| 
 | ||||
| enum class HashAlgorithm(val raw: Int) { | ||||
|   SHA256(0), | ||||
|   SHA384(1), | ||||
|   SHA512(2); | ||||
| 
 | ||||
|   companion object { | ||||
|     fun ofRaw(raw: Int): HashAlgorithm? { | ||||
|       return values().firstOrNull { it.raw == raw } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| enum class CipherAlgorithm(val raw: Int) { | ||||
|   AES(0); | ||||
| 
 | ||||
|   companion object { | ||||
|     fun ofRaw(raw: Int): CipherAlgorithm? { | ||||
|       return values().firstOrNull { it.raw == raw } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ | ||||
| interface NativeCryptoAPI { | ||||
|   fun hash(data: ByteArray, algorithm: HashAlgorithm): ByteArray? | ||||
|   fun hmac(data: ByteArray, key: ByteArray, algorithm: HashAlgorithm): ByteArray? | ||||
|   fun generateSecureRandom(length: Long): ByteArray? | ||||
|   fun pbkdf2(password: ByteArray, salt: ByteArray, length: Long, iterations: Long, algorithm: HashAlgorithm): ByteArray? | ||||
|   fun encrypt(plainText: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? | ||||
|   fun encryptWithIV(plainText: ByteArray, iv: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? | ||||
|   fun decrypt(cipherText: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? | ||||
|   fun encryptFile(plainTextPath: String, cipherTextPath: String, key: ByteArray, algorithm: CipherAlgorithm): Boolean? | ||||
|   fun encryptFileWithIV(plainTextPath: String, cipherTextPath: String, iv: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): Boolean? | ||||
|   fun decryptFile(cipherTextPath: String, plainTextPath: String, key: ByteArray, algorithm: CipherAlgorithm): Boolean? | ||||
| 
 | ||||
|   companion object { | ||||
|     /** The codec used by NativeCryptoAPI. */ | ||||
|     val codec: MessageCodec<Any?> by lazy { | ||||
|       StandardMessageCodec() | ||||
|     } | ||||
|     /** Sets up an instance of `NativeCryptoAPI` to handle messages through the `binaryMessenger`. */ | ||||
|     @Suppress("UNCHECKED_CAST") | ||||
|     fun setUp(binaryMessenger: BinaryMessenger, api: NativeCryptoAPI?) { | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hash", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val dataArg = args[0] as ByteArray | ||||
|             val algorithmArg = HashAlgorithm.ofRaw(args[1] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.hash(dataArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hmac", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val dataArg = args[0] as ByteArray | ||||
|             val keyArg = args[1] as ByteArray | ||||
|             val algorithmArg = HashAlgorithm.ofRaw(args[2] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.hmac(dataArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val lengthArg = args[0].let { if (it is Int) it.toLong() else it as Long } | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.generateSecureRandom(lengthArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.pbkdf2", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val passwordArg = args[0] as ByteArray | ||||
|             val saltArg = args[1] as ByteArray | ||||
|             val lengthArg = args[2].let { if (it is Int) it.toLong() else it as Long } | ||||
|             val iterationsArg = args[3].let { if (it is Int) it.toLong() else it as Long } | ||||
|             val algorithmArg = HashAlgorithm.ofRaw(args[4] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.pbkdf2(passwordArg, saltArg, lengthArg, iterationsArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encrypt", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val plainTextArg = args[0] as ByteArray | ||||
|             val keyArg = args[1] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[2] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.encrypt(plainTextArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val plainTextArg = args[0] as ByteArray | ||||
|             val ivArg = args[1] as ByteArray | ||||
|             val keyArg = args[2] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.encryptWithIV(plainTextArg, ivArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decrypt", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val cipherTextArg = args[0] as ByteArray | ||||
|             val keyArg = args[1] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[2] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.decrypt(cipherTextArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptFile", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val plainTextPathArg = args[0] as String | ||||
|             val cipherTextPathArg = args[1] as String | ||||
|             val keyArg = args[2] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.encryptFile(plainTextPathArg, cipherTextPathArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val plainTextPathArg = args[0] as String | ||||
|             val cipherTextPathArg = args[1] as String | ||||
|             val ivArg = args[2] as ByteArray | ||||
|             val keyArg = args[3] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[4] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.encryptFileWithIV(plainTextPathArg, cipherTextPathArg, ivArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|       run { | ||||
|         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decryptFile", codec) | ||||
|         if (api != null) { | ||||
|           channel.setMessageHandler { message, reply -> | ||||
|             val args = message as List<Any?> | ||||
|             val cipherTextPathArg = args[0] as String | ||||
|             val plainTextPathArg = args[1] as String | ||||
|             val keyArg = args[2] as ByteArray | ||||
|             val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! | ||||
|             var wrapped: List<Any?> | ||||
|             try { | ||||
|               wrapped = listOf<Any?>(api.decryptFile(cipherTextPathArg, plainTextPathArg, keyArg, algorithmArg)) | ||||
|             } catch (exception: Throwable) { | ||||
|               wrapped = wrapError(exception) | ||||
|             } | ||||
|             reply.reply(wrapped) | ||||
|           } | ||||
|         } else { | ||||
|           channel.setMessageHandler(null) | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,55 +1,146 @@ | ||||
| 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 fr.pointcheval.native_crypto_android.utils.FileParameters | ||||
| import javax.crypto.CipherOutputStream | ||||
| 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 | ||||
|    private var cipherInstance: javax.crypto.Cipher? = null | ||||
| 
 | ||||
|     var cipherInstance: javax.crypto.Cipher? = null; | ||||
| 
 | ||||
|     fun lazyLoadCipher() { | ||||
|     private fun lazyLoadCipher() { | ||||
|         if (cipherInstance == null) { | ||||
|             cipherInstance = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // native.crypto cipherText representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)] | ||||
|     // javax.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)] | ||||
|     override fun encrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         val list : List<ByteArray> = encryptAsList(data, key) | ||||
|         return list.first().plus(list.last()) | ||||
|     override fun encrypt(data: ByteArray, key: ByteArray, predefinedIV: ByteArray?): ByteArray { | ||||
|         // Initialize secret key spec | ||||
|         val sk = SecretKeySpec(key, "AES") | ||||
| 
 | ||||
|         // Initialize cipher (if not already done) | ||||
|         lazyLoadCipher() | ||||
| 
 | ||||
|         // If predefinedIV is not null, use it | ||||
|         if (predefinedIV != null && predefinedIV.isNotEmpty()) { | ||||
|             // Here we use the predefinedIV as the nonce (12 bytes) | ||||
|             // And we set the tag length to 16 bytes (128 bits) | ||||
|             val gcmParameterSpec = GCMParameterSpec(16*8, predefinedIV) | ||||
|             cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk, gcmParameterSpec) | ||||
|         } else { | ||||
|             // If predefinedIV is null, we generate a new one | ||||
|             cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         } | ||||
| 
 | ||||
|     // native.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)] | ||||
|     // javax.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)] | ||||
|     override fun encryptAsList(data: ByteArray, key: ByteArray): List<ByteArray> { | ||||
|         val sk = SecretKeySpec(key, "AES") | ||||
|         lazyLoadCipher() | ||||
|         cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         // Encrypt data | ||||
|         val bytes: ByteArray = cipherInstance!!.doFinal(data) | ||||
|         val iv: ByteArray = cipherInstance!!.iv | ||||
|         return listOf(iv, bytes) | ||||
| 
 | ||||
|         return iv.plus(bytes) | ||||
|     } | ||||
| 
 | ||||
|     override fun encryptFile(fileParameters: FileParameters, key: ByteArray, predefinedIV: ByteArray?): Boolean { | ||||
|         // Initialize secret key spec | ||||
|         val sk = SecretKeySpec(key, "AES") | ||||
| 
 | ||||
|         // Initialize cipher (if not already done) | ||||
|         lazyLoadCipher() | ||||
| 
 | ||||
|         // If predefinedIV is not null, use it | ||||
|         if (predefinedIV != null && predefinedIV.isNotEmpty()) { | ||||
|             // Here we use the predefinedIV as the nonce (12 bytes) | ||||
|             // And we set the tag length to 16 bytes (128 bits) | ||||
|             val gcmParameterSpec = GCMParameterSpec(16*8, predefinedIV) | ||||
|             cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk, gcmParameterSpec) | ||||
|         } else { | ||||
|             // If predefinedIV is null, we generate a new one | ||||
|             cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) | ||||
|         } | ||||
| 
 | ||||
|         var len: Int? | ||||
|         val buffer = ByteArray(8192) | ||||
|         val inputFile = fileParameters.getFileInputStream() | ||||
|         val outputFile = fileParameters.getFileOutputStream() | ||||
|         val iv: ByteArray? = cipherInstance!!.iv | ||||
| 
 | ||||
|         outputFile?.write(iv) | ||||
|         outputFile?.flush() | ||||
| 
 | ||||
|         val encryptedStream = CipherOutputStream(outputFile!!, cipherInstance) | ||||
|         while(true) { | ||||
|             len = inputFile?.read(buffer) | ||||
|             if (len != null && len > 0) { | ||||
|                 encryptedStream.write(buffer,0,len) | ||||
|             } else { | ||||
|                 break | ||||
|             } | ||||
|         } | ||||
|         encryptedStream.flush() | ||||
|         encryptedStream.close() | ||||
|         inputFile?.close() | ||||
|         outputFile.close() | ||||
| 
 | ||||
|         return fileParameters.outputExists() | ||||
|     } | ||||
| 
 | ||||
|     override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { | ||||
|         // Extract the IV from the cipherText | ||||
|         val iv: ByteArray = data.take(12).toByteArray() | ||||
|         val payload: ByteArray = data.drop(12).toByteArray() | ||||
|         return decryptAsList(listOf(iv, payload), key) | ||||
|     } | ||||
| 
 | ||||
|     override fun decryptAsList(data: List<ByteArray>, key: ByteArray): ByteArray { | ||||
|         // Initialize secret key spec | ||||
|         val sk = SecretKeySpec(key, "AES") | ||||
|         val payload: ByteArray = data.last() | ||||
|         val iv: ByteArray = data.first() | ||||
|         val gcmSpec = GCMParameterSpec(16 * 8, iv) | ||||
| 
 | ||||
|         // Initialize GCMParameterSpec | ||||
|         val gcmParameterSpec = GCMParameterSpec(16 * 8, iv) | ||||
| 
 | ||||
|         // Initialize cipher (if not already done) | ||||
|         lazyLoadCipher() | ||||
|         cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmSpec) | ||||
|         cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmParameterSpec) | ||||
| 
 | ||||
|         // Decrypt data | ||||
|         return cipherInstance!!.doFinal(payload) | ||||
|     } | ||||
| 
 | ||||
|     override fun decryptFile(fileParameters: FileParameters, key: ByteArray): Boolean { | ||||
|         val iv = ByteArray(12) | ||||
|         val inputFile = fileParameters.getFileInputStream() ?: throw Exception("Error while reading IV") | ||||
| 
 | ||||
|         // Read the first 12 bytes from the file | ||||
|         for (i in 0 until 12) { | ||||
|             iv[i] = inputFile.read().toByte() | ||||
|         } | ||||
| 
 | ||||
|         // Initialize secret key spec | ||||
|         val sk = SecretKeySpec(key, "AES") | ||||
| 
 | ||||
|         // Initialize GCMParameterSpec | ||||
|         val gcmParameterSpec = GCMParameterSpec(16 * 8, iv) | ||||
| 
 | ||||
|         // Initialize cipher (if not already done) | ||||
|         lazyLoadCipher() | ||||
| 
 | ||||
|         cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmParameterSpec) | ||||
| 
 | ||||
|         var len: Int? | ||||
|         val buffer = ByteArray(8192) | ||||
|         val outputFile = fileParameters.getFileOutputStream() | ||||
|         val decryptedStream = CipherOutputStream(outputFile!!, cipherInstance) | ||||
|         while (true) { | ||||
|             len = inputFile.read(buffer) | ||||
|             if(len > 0){ | ||||
|                 decryptedStream.write(buffer,0, len) | ||||
|             } else { | ||||
|                 break | ||||
|             } | ||||
|         } | ||||
|         decryptedStream.flush() | ||||
|         decryptedStream.close() | ||||
|         inputFile.close() | ||||
| 
 | ||||
|         return fileParameters.outputExists() | ||||
|     } | ||||
| } | ||||
| @ -1,12 +1,10 @@ | ||||
| package fr.pointcheval.native_crypto_android.interfaces | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm | ||||
| import fr.pointcheval.native_crypto_android.utils.FileParameters | ||||
| 
 | ||||
| interface Cipher { | ||||
|     val algorithm: CipherAlgorithm | ||||
| 
 | ||||
|     fun encrypt(data: ByteArray, key: ByteArray): ByteArray | ||||
|     fun encrypt(data: ByteArray, key: ByteArray, predefinedIV: ByteArray?): ByteArray | ||||
|     fun decrypt(data: ByteArray, key: ByteArray): ByteArray | ||||
|     fun encryptAsList(data: ByteArray, key: ByteArray): List<ByteArray> | ||||
|     fun decryptAsList(data: List<ByteArray>, key: ByteArray): ByteArray | ||||
|     fun encryptFile(fileParameters: FileParameters, key: ByteArray, predefinedIV: ByteArray?): Boolean | ||||
|     fun decryptFile(fileParameters: FileParameters, key: ByteArray): Boolean | ||||
| } | ||||
| @ -1,5 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android.interfaces | ||||
| 
 | ||||
| interface Key { | ||||
|     val bytes: ByteArray | ||||
| } | ||||
| @ -1,10 +1,5 @@ | ||||
| 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 | ||||
|     fun derive(): ByteArray? | ||||
| } | ||||
| @ -1,39 +1,43 @@ | ||||
| package fr.pointcheval.native_crypto_android.kdf | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.HashAlgorithm | ||||
| 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 fr.pointcheval.native_crypto_android.utils.HashAlgorithmParser | ||||
| 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 | ||||
|     private val length: Int, private val iterations: Int, | ||||
|     private val hashAlgorithm: HashAlgorithm | ||||
| ) : KeyDerivation { | ||||
| 
 | ||||
|     private var password: String? = null | ||||
|     private var salt: String? = null | ||||
|     private var password: CharArray? = null | ||||
|     private var salt: ByteArray? = null | ||||
| 
 | ||||
|     fun init(password: String, salt: String) { | ||||
|         this.password = password | ||||
|     fun init(password: ByteArray, salt: ByteArray) { | ||||
|         // Transform the password to a char array | ||||
|         val passwordCharArray = CharArray(password.size) | ||||
|         for (i in password.indices) { | ||||
|             passwordCharArray[i] = password[i].toInt().toChar() | ||||
|         } | ||||
| 
 | ||||
|         this.password = passwordCharArray | ||||
|         this.salt = salt | ||||
|     } | ||||
| 
 | ||||
|     override val algorithm: KdfAlgorithm | ||||
|         get() = KdfAlgorithm.pbkdf2 | ||||
| 
 | ||||
|     override fun derive(): SecretKey { | ||||
|     override fun derive(): ByteArray? { | ||||
|         if (password == null || salt == null) { | ||||
|             throw Exception("Password and Salt must be initialized.") | ||||
|         } | ||||
|         val spec = PBEKeySpec( | ||||
|             password!!.toCharArray(), | ||||
|             salt!!.toByteArray(), | ||||
|             password!!, | ||||
|             salt!!, | ||||
|             iterations, | ||||
|             keyBytesCount * 8 | ||||
|             length * 8 | ||||
|         ) | ||||
|         val skf: SecretKeyFactory = SecretKeyFactory.getInstance(hash.pbkdf2String()) | ||||
|         return SecretKey(skf.generateSecret(spec).encoded) | ||||
|         val skf: SecretKeyFactory = | ||||
|             SecretKeyFactory.getInstance(HashAlgorithmParser.getPbkdf2String(hashAlgorithm)) | ||||
| 
 | ||||
|         return skf.generateSecret(spec).encoded | ||||
|     } | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| 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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,14 +0,0 @@ | ||||
| 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,71 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.content.res.Resources | ||||
| import android.net.Uri | ||||
| import androidx.documentfile.provider.DocumentFile | ||||
| import java.io.* | ||||
| 
 | ||||
| class FileParameters(ctx: Context, input: String, output: String) { | ||||
|     private var context: Context | ||||
| 
 | ||||
|     private var inputPath: String | ||||
|     private var outputPath: String | ||||
| 
 | ||||
|     init { | ||||
|         this.context = ctx | ||||
|         this.inputPath = input | ||||
|         this.outputPath = output | ||||
|     } | ||||
| 
 | ||||
|     private fun getUri(): Uri? { | ||||
|         val persistedUriPermissions = context.contentResolver.persistedUriPermissions | ||||
|         if (persistedUriPermissions.size > 0) { | ||||
|             val uriPermission = persistedUriPermissions[0] | ||||
|             return uriPermission.uri | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| 
 | ||||
|     private fun getDocumentFileByPath(path: String): DocumentFile { | ||||
|         var doc = DocumentFile.fromTreeUri(context, getUri()!!) | ||||
|         val parts = path.split("/") | ||||
|         for (i in parts.indices) { | ||||
|             val nextFile = doc?.findFile(parts[i]) | ||||
|             if(nextFile != null){ | ||||
|                 doc = nextFile | ||||
|             } | ||||
|         } | ||||
|         if (doc != null){ | ||||
|             return doc | ||||
|         } else { | ||||
|             throw Resources.NotFoundException("File not found") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getFileOutputStream(): OutputStream? { | ||||
|         val path = outputPath | ||||
|         return try{ | ||||
|             FileOutputStream(path) | ||||
|         } catch(e: IOException){ | ||||
|             val documentFile: DocumentFile = this.getDocumentFileByPath(path) | ||||
|             val documentUri = documentFile.uri | ||||
|             context.contentResolver.openOutputStream(documentUri) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getFileInputStream(): InputStream? { | ||||
|         val path = inputPath | ||||
|         return try{ | ||||
|             FileInputStream(path) | ||||
|         } catch(e: IOException){ | ||||
|             val documentFile: DocumentFile = this.getDocumentFileByPath(path) | ||||
|             val documentUri = documentFile.uri | ||||
|             context.contentResolver.openInputStream(documentUri) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun outputExists(): Boolean { | ||||
|         return File(outputPath).exists() | ||||
|     } | ||||
| } | ||||
| @ -1,50 +0,0 @@ | ||||
| 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,31 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| import fr.pointcheval.native_crypto_android.HashAlgorithm | ||||
| import java.security.MessageDigest | ||||
| import javax.crypto.Mac | ||||
| 
 | ||||
| object HashAlgorithmParser { | ||||
|     fun getMessageDigest(algorithm: HashAlgorithm): MessageDigest { | ||||
|         return when (algorithm) { | ||||
|             HashAlgorithm.SHA256 -> MessageDigest.getInstance("SHA-256") | ||||
|             HashAlgorithm.SHA384 -> MessageDigest.getInstance("SHA-384") | ||||
|             HashAlgorithm.SHA512 -> MessageDigest.getInstance("SHA-512") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getMac(algorithm: HashAlgorithm): Mac { | ||||
|         return when (algorithm) { | ||||
|             HashAlgorithm.SHA256 -> Mac.getInstance("HmacSHA256") | ||||
|             HashAlgorithm.SHA384 -> Mac.getInstance("HmacSHA384") | ||||
|             HashAlgorithm.SHA512 -> Mac.getInstance("HmacSHA512") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getPbkdf2String(algorithm: HashAlgorithm): String { | ||||
|         return when (algorithm) { | ||||
|             HashAlgorithm.SHA256 -> "PBKDF2WithHmacSHA256" | ||||
|             HashAlgorithm.SHA384 -> "PBKDF2WithHmacSHA384" | ||||
|             HashAlgorithm.SHA512 -> "PBKDF2WithHmacSHA512" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,5 +0,0 @@ | ||||
| package fr.pointcheval.native_crypto_android.utils | ||||
| 
 | ||||
| enum class KdfAlgorithm { | ||||
|     pbkdf2 | ||||
| } | ||||
| @ -1,44 +0,0 @@ | ||||
| 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