feat(android): use kotlin pigeon generator

This commit is contained in:
Hugo Pointcheval 2023-04-04 22:36:50 +02:00
parent f570ed076a
commit 560f5b4942
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
18 changed files with 741 additions and 421 deletions

View File

@ -47,4 +47,5 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.documentfile:documentfile:1.0.1'
}

View File

@ -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);
errorList.add(exception.toString());
errorList.add(exception.getClass().getSimpleName());
errorList.add(
"Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
HashRequest requestArg = (HashRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
HmacRequest requestArg = (HmacRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
GenerateSecureRandomRequest requestArg = (GenerateSecureRandomRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
Pbkdf2Request requestArg = (Pbkdf2Request) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
EncryptRequest requestArg = (EncryptRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
DecryptRequest requestArg = (DecryptRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
EncryptFileRequest requestArg = (EncryptFileRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
DecryptFileRequest requestArg = (DecryptFileRequest) args.get(0);
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.");
}
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>();
ArrayList<Object> args = (ArrayList<Object>) message;
EncryptWithIVRequest requestArg = (EncryptWithIVRequest) args.get(0);
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.");
}
EncryptResponse output = api.encryptWithIV(requestArg);
wrapped.add(0, output);
} catch (Error | RuntimeException exception) {
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}

View File

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

View File

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

View File

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

View File

@ -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())
}
// 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> {
// native.crypto cipherText representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)]
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()
cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk)
// 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)
}
// 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()
}
}

View File

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

View File

@ -1,5 +0,0 @@
package fr.pointcheval.native_crypto_android.interfaces
interface Key {
val bytes: ByteArray
}

View File

@ -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?
}

View File

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

View File

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

View File

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

View File

@ -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"
}

View File

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

View File

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

View File

@ -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"
}
}
}

View File

@ -1,5 +0,0 @@
package fr.pointcheval.native_crypto_android.utils
enum class KdfAlgorithm {
pbkdf2
}

View File

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