Update kotlin specific code
This commit is contained in:
parent
4d84a938ac
commit
11d43fe64c
@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
101
android/src/main/kotlin/fr/pointcheval/native_crypto/Cipher.kt
Normal file
101
android/src/main/kotlin/fr/pointcheval/native_crypto/Cipher.kt
Normal file
@ -0,0 +1,101 @@
|
||||
package fr.pointcheval.native_crypto
|
||||
|
||||
import java.lang.Exception
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
enum class CipherAlgorithm(val spec: String) {
|
||||
AES("AES"),
|
||||
BlowFish("BLOWFISH")
|
||||
}
|
||||
|
||||
enum class BlockCipherMode(val instance: String) {
|
||||
ECB("ECB"),
|
||||
CBC("CBC"),
|
||||
CFB("CFB"),
|
||||
GCM("GCM"),
|
||||
CGM("CGM"),
|
||||
}
|
||||
|
||||
enum class Padding(val instance: String) {
|
||||
PKCS5("PKCS5Padding"),
|
||||
None("NoPadding")
|
||||
}
|
||||
|
||||
class CipherParameters(private val mode: BlockCipherMode, private val padding: Padding) {
|
||||
override fun toString(): String {
|
||||
return mode.instance + "/" + padding.instance
|
||||
}
|
||||
}
|
||||
|
||||
class Cipher {
|
||||
|
||||
fun getCipherAlgorithm(dartAlgorithm: String) : CipherAlgorithm {
|
||||
return when (dartAlgorithm) {
|
||||
"aes" -> CipherAlgorithm.AES
|
||||
"blowfish" -> CipherAlgorithm.BlowFish
|
||||
else -> CipherAlgorithm.AES
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(mode : String, padding : String) : CipherParameters {
|
||||
val m = when (mode) {
|
||||
"ecb" -> BlockCipherMode.ECB
|
||||
"cbc" -> BlockCipherMode.CBC
|
||||
"cfb" -> BlockCipherMode.CFB
|
||||
"gcm" -> BlockCipherMode.GCM
|
||||
"cgm" -> BlockCipherMode.CGM
|
||||
else -> throw Exception()
|
||||
}
|
||||
val p = when (padding) {
|
||||
"pkcs5" -> Padding.PKCS5
|
||||
else -> Padding.None
|
||||
}
|
||||
return CipherParameters(m,p)
|
||||
}
|
||||
|
||||
fun encrypt(data: ByteArray, key: ByteArray, algorithm: String, mode: String, padding: String) : List<ByteArray> {
|
||||
val algo = getCipherAlgorithm(algorithm)
|
||||
val params = getInstance(mode, padding)
|
||||
|
||||
val keySpecification = algo.spec + "/" + params.toString()
|
||||
|
||||
val mac = Hash().digest(key + data)
|
||||
val sk: SecretKey = SecretKeySpec(key, algo.spec)
|
||||
|
||||
val cipher = Cipher.getInstance(keySpecification)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sk)
|
||||
|
||||
val encryptedBytes = cipher.doFinal(mac + data)
|
||||
val iv = cipher.iv
|
||||
|
||||
return listOf(encryptedBytes, iv);
|
||||
}
|
||||
|
||||
fun decrypt(payload: Collection<ByteArray>, key: ByteArray, algorithm: String, mode: String, padding: String) : ByteArray? {
|
||||
val algo = getCipherAlgorithm(algorithm)
|
||||
val params = getInstance(mode, padding)
|
||||
|
||||
val keySpecification = algo.spec + "/" + params.toString()
|
||||
|
||||
val sk: SecretKey = SecretKeySpec(key, algo.spec)
|
||||
val cipher = Cipher.getInstance(keySpecification);
|
||||
val iv = payload.last();
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
cipher.init(Cipher.DECRYPT_MODE, sk, ivSpec);
|
||||
|
||||
val decryptedBytes = cipher.doFinal(payload.first());
|
||||
|
||||
val mac = decryptedBytes.copyOfRange(0, 32)
|
||||
val decryptedContent : ByteArray = decryptedBytes.copyOfRange(32, decryptedBytes.size)
|
||||
val verificationMac = Hash().digest(key + decryptedContent)
|
||||
|
||||
return if (mac.contentEquals(verificationMac)) {
|
||||
decryptedContent
|
||||
} else {
|
||||
null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package fr.pointcheval.native_crypto
|
||||
|
||||
import java.security.MessageDigest
|
||||
|
||||
enum class HashAlgorithm(val length : Int) {
|
||||
SHA1(160),
|
||||
SHA128(128),
|
||||
SHA256(256),
|
||||
SHA512(512);
|
||||
}
|
||||
|
||||
|
||||
class Hash() {
|
||||
fun digest(data: ByteArray?, algorithm: HashAlgorithm): ByteArray {
|
||||
val func : String = when (algorithm) {
|
||||
HashAlgorithm.SHA1 -> "SHA-1"
|
||||
HashAlgorithm.SHA128 -> "SHA-128"
|
||||
HashAlgorithm.SHA256 -> "SHA-256"
|
||||
HashAlgorithm.SHA512 -> "SHA-512"
|
||||
}
|
||||
val md = MessageDigest.getInstance(func)
|
||||
return md.digest(data)
|
||||
}
|
||||
|
||||
fun digest(data: ByteArray?, algorithm: String): ByteArray {
|
||||
val func : HashAlgorithm = when (algorithm) {
|
||||
"sha1" -> HashAlgorithm.SHA1
|
||||
"sha128" -> HashAlgorithm.SHA128
|
||||
"sha256" -> HashAlgorithm.SHA256
|
||||
"sha512" -> HashAlgorithm.SHA512
|
||||
else -> HashAlgorithm.SHA256
|
||||
}
|
||||
return digest(data, func)
|
||||
}
|
||||
|
||||
fun digest(data: ByteArray?): ByteArray {
|
||||
return digest(data, HashAlgorithm.SHA256)
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package fr.pointcheval.native_crypto
|
||||
|
||||
import android.os.Build
|
||||
import java.lang.IllegalArgumentException
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
|
||||
class KeyDerivation {
|
||||
fun pbkdf2(password: String, salt: String, keyLength: Int, iteration: Int, algorithm: String): ByteArray {
|
||||
val chars: CharArray = password.toCharArray()
|
||||
val availableHashAlgorithm: Map<String, String> = mapOf(
|
||||
"sha1" to "PBKDF2withHmacSHA1",
|
||||
"sha224" to "PBKDF2withHmacSHA224",
|
||||
"sha256" to "PBKDF2WithHmacSHA256",
|
||||
"sha284" to "PBKDF2withHmacSHA384",
|
||||
"sha512" to "PBKDF2withHmacSHA512"
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
// SHA-1 and SHA-2 implemented
|
||||
val spec = PBEKeySpec(chars, salt.toByteArray(), iteration, keyLength * 8)
|
||||
val skf: SecretKeyFactory = SecretKeyFactory.getInstance(availableHashAlgorithm[algorithm]);
|
||||
return skf.generateSecret(spec).encoded
|
||||
} else if (Build.VERSION.SDK_INT >= 10) {
|
||||
// Only SHA-1 is implemented
|
||||
if (!algorithm.equals("sha1")) {
|
||||
throw PlatformVersionException("Only SHA1 is implemented on this SDK version!")
|
||||
}
|
||||
val spec = PBEKeySpec(chars, salt.toByteArray(), iteration, keyLength * 8)
|
||||
val skf: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
|
||||
return skf.generateSecret(spec).encoded
|
||||
}
|
||||
throw PlatformVersionException("Invalid SDK version!")
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package fr.pointcheval.native_crypto
|
||||
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.KeyGenerator
|
||||
|
||||
class KeyGeneration {
|
||||
fun keygen(size : Int) : ByteArray {
|
||||
val secureRandom = SecureRandom()
|
||||
val keyGenerator = if (size in listOf<Int>(128,192,256)) {
|
||||
KeyGenerator.getInstance("AES")
|
||||
} else {
|
||||
KeyGenerator.getInstance("BLOWFISH")
|
||||
}
|
||||
|
||||
keyGenerator.init(size, secureRandom)
|
||||
val sk = keyGenerator.generateKey()
|
||||
|
||||
return sk!!.encoded
|
||||
}
|
||||
|
||||
fun rsaKeypairGen(size : Int) : List<ByteArray> {
|
||||
val secureRandom = SecureRandom()
|
||||
val keyGenerator = KeyPairGenerator.getInstance("RSA")
|
||||
|
||||
keyGenerator.initialize(size, secureRandom)
|
||||
val keypair = keyGenerator.genKeyPair()
|
||||
val res : List<ByteArray> = listOf(keypair.public.encoded, keypair.private.encoded)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
@ -11,162 +11,132 @@ import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
import io.flutter.plugin.common.PluginRegistry.Registrar
|
||||
import java.security.MessageDigest
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
||||
/** NativeCryptoPlugin */
|
||||
public class NativeCryptoPlugin : FlutterPlugin, MethodCallHandler {
|
||||
// CRYPTO CONSTS
|
||||
private val HASH_FUNC = "SHA-256"
|
||||
private val SYM_CRYPTO_METHOD = "AES"
|
||||
private val SYM_CRYPTO_PADDING = "AES/CBC/PKCS5PADDING"
|
||||
|
||||
class NativeCryptoPlugin : FlutterPlugin, MethodCallHandler {
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "native.crypto.helper")
|
||||
val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "native.crypto")
|
||||
channel.setMethodCallHandler(NativeCryptoPlugin());
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun registerWith(registrar: Registrar) {
|
||||
val channel = MethodChannel(registrar.messenger(), "native.crypto.helper")
|
||||
val channel = MethodChannel(registrar.messenger(), "native.crypto")
|
||||
channel.setMethodCallHandler(NativeCryptoPlugin())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
if (call.method == "pbkdf2") {
|
||||
|
||||
val password = call.argument<String>("password")
|
||||
val salt = call.argument<String>("salt")
|
||||
val keyLength = call.argument<Int>("keyLength")
|
||||
val iteration = call.argument<Int>("iteration")
|
||||
val algorithm = call.argument<String>("algorithm")
|
||||
when (call.method) {
|
||||
"digest" -> {
|
||||
val message = call.argument<ByteArray>("message")
|
||||
val algorithm = call.argument<String>("algorithm")
|
||||
|
||||
val key = pbkdf2(password!!, salt!!, keyLength!!, iteration!!, algorithm!!)
|
||||
|
||||
if (key.isNotEmpty()) {
|
||||
result.success(key)
|
||||
} else {
|
||||
result.error("PBKDF2ERROR", "PBKDF2 KEY IS NULL.", null)
|
||||
}
|
||||
} else if (call.method == "symKeygen") {
|
||||
val keySize = call.argument<Int>("size") // 128, 192, 256
|
||||
|
||||
val aesKey = symKeygen(keySize!!) // Collection<ByteArray>
|
||||
|
||||
if (aesKey.isNotEmpty()) {
|
||||
result.success(aesKey)
|
||||
} else {
|
||||
result.error("SYMKEYGENERROR", "GENERATED KEY IS NULL.", null)
|
||||
}
|
||||
} else if (call.method == "symEncrypt") {
|
||||
val payload = call.argument<ByteArray>("payload") // ByteArray
|
||||
val aesKey = call.argument<ByteArray>("aesKey") // ByteArray
|
||||
|
||||
val encryptedPayload = symEncrypt(payload!!, aesKey!!) // Collection<ByteArray>
|
||||
|
||||
if (encryptedPayload.isNotEmpty()) {
|
||||
result.success(encryptedPayload)
|
||||
} else {
|
||||
result.error("ENCRYPTIONERROR", "ENCRYPTED PAYLOAD IS NULL.", null)
|
||||
}
|
||||
} else if (call.method == "symDecrypt") {
|
||||
val payload = call.argument<Collection<ByteArray>>("payload") // Collection<ByteArray>
|
||||
val aesKey = call.argument<ByteArray>("aesKey") // ByteArray
|
||||
var decryptedPayload : ByteArray? = null
|
||||
|
||||
try {
|
||||
decryptedPayload = symDecrypt(payload!!, aesKey!!)
|
||||
if (decryptedPayload != null && decryptedPayload.isNotEmpty()) {
|
||||
result.success(decryptedPayload)
|
||||
} else {
|
||||
result.error("DECRYPTIONERROR", "DECRYPTED PAYLOAD IS NULL. MAYBE VERIFICATION MAC IS UNVALID.", null)
|
||||
try {
|
||||
val d = Hash().digest(message, algorithm!!)
|
||||
if (d.isNotEmpty()) {
|
||||
result.success(d)
|
||||
} else {
|
||||
result.error("DIGESTERROR", "DIGEST IS NULL.", null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("DIGESTEXCEPTION", e.message, null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("DECRYPTIONERROR", "AN ERROR OCCURED WHILE DECRYPTING.", null)
|
||||
}
|
||||
|
||||
} else {
|
||||
result.notImplemented()
|
||||
"pbkdf2" -> {
|
||||
val password = call.argument<String>("password")
|
||||
val salt = call.argument<String>("salt")
|
||||
val keyLength = call.argument<Int>("keyLength")
|
||||
val iteration = call.argument<Int>("iteration")
|
||||
val algorithm = call.argument<String>("algorithm")
|
||||
|
||||
try {
|
||||
val key = KeyDerivation().pbkdf2(password!!, salt!!, keyLength!!, iteration!!, algorithm!!)
|
||||
if (key.isNotEmpty()) {
|
||||
result.success(key)
|
||||
} else {
|
||||
result.error("PBKDF2ERROR", "PBKDF2 KEY IS NULL.", null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("PBKDF2EXCEPTION", e.message, null)
|
||||
}
|
||||
}
|
||||
"keygen" -> {
|
||||
val size = call.argument<Int>("size") // 128, 192, 256
|
||||
try {
|
||||
val key = KeyGeneration().keygen(size!!)
|
||||
|
||||
if (key.isNotEmpty()) {
|
||||
result.success(key)
|
||||
} else {
|
||||
result.error("KEYGENERROR", "GENERATED KEY IS NULL.", null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("KEYGENEXCEPTION", e.message, null)
|
||||
}
|
||||
}
|
||||
"rsaKeypairGen" -> {
|
||||
val size = call.argument<Int>("size")
|
||||
try {
|
||||
val keypair = KeyGeneration().rsaKeypairGen(size!!)
|
||||
|
||||
if (keypair.isNotEmpty()) {
|
||||
result.success(keypair)
|
||||
} else {
|
||||
result.error("KEYPAIRGENERROR", "GENERATED KEYPAIR IS EMPTY.", null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("KEYPAIRGENEXCEPTION", e.message, null)
|
||||
}
|
||||
}
|
||||
"encrypt" -> {
|
||||
val data = call.argument<ByteArray>("data")
|
||||
val key = call.argument<ByteArray>("key")
|
||||
val algorithm = call.argument<String>("algorithm")
|
||||
val mode = call.argument<String>("mode")
|
||||
val padding = call.argument<String>("padding")
|
||||
|
||||
try {
|
||||
val payload = Cipher().encrypt(data!!, key!!, algorithm!!, mode!!, padding!!)
|
||||
|
||||
if (payload.isNotEmpty()) {
|
||||
result.success(payload)
|
||||
} else {
|
||||
result.error("ENCRYPTIONERROR", "ENCRYPTED PAYLOAD IS EMPTY.", null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.error("ENCRYPTIONEXCEPTION", e.message, null)
|
||||
}
|
||||
|
||||
}
|
||||
"decrypt" -> {
|
||||
val payload = call.argument<Collection<ByteArray>>("payload") // Collection<ByteArray>
|
||||
|
||||
val key = call.argument<ByteArray>("key")
|
||||
val algorithm = call.argument<String>("algorithm")
|
||||
val mode = call.argument<String>("mode")
|
||||
val padding = call.argument<String>("padding")
|
||||
|
||||
var decryptedPayload : ByteArray? = null
|
||||
|
||||
try {
|
||||
decryptedPayload = Cipher().decrypt(payload!!, key!!, algorithm!!, mode!!, padding!!)
|
||||
if (decryptedPayload != null && decryptedPayload.isNotEmpty()) {
|
||||
result.success(decryptedPayload)
|
||||
} else {
|
||||
result.error("DECRYPTIONERROR", "DECRYPTED PAYLOAD IS NULL. MAYBE VERIFICATION MAC IS UNVALID.", null)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
result.error("DECRYPTIONEXCEPTION", e.message, null)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
}
|
||||
|
||||
// CRYPTO NATIVE FUNCTIONS
|
||||
|
||||
private fun digest(obj: ByteArray?): ByteArray {
|
||||
val md = MessageDigest.getInstance(HASH_FUNC)
|
||||
return md.digest(obj)
|
||||
}
|
||||
|
||||
private fun pbkdf2(password : String, salt : String, keyLength : Int, iteration : Int, algorithm : String) : ByteArray {
|
||||
val chars: CharArray = password.toCharArray()
|
||||
|
||||
val spec = PBEKeySpec(chars, salt.toByteArray(), iteration, keyLength * 8)
|
||||
val skf: SecretKeyFactory = if (algorithm == "sha1") {
|
||||
SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
|
||||
} else if (algorithm == "sha256") {
|
||||
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||
} else {
|
||||
SecretKeyFactory.getInstance("PBKDF2withHmacSHA512")
|
||||
}
|
||||
return skf.generateSecret(spec).encoded
|
||||
}
|
||||
|
||||
private fun symKeygen(keySize : Int): ByteArray {
|
||||
|
||||
val SYM_CRYPTO_BITS = keySize
|
||||
|
||||
val secureRandom = SecureRandom()
|
||||
val keyGenerator = KeyGenerator.getInstance(SYM_CRYPTO_METHOD)
|
||||
keyGenerator?.init(SYM_CRYPTO_BITS, secureRandom)
|
||||
val skey = keyGenerator?.generateKey()
|
||||
|
||||
return skey!!.encoded
|
||||
}
|
||||
|
||||
private fun symEncrypt(payload: ByteArray, aesKey: ByteArray): Collection<ByteArray> {
|
||||
|
||||
val mac = digest(aesKey + payload)
|
||||
val key: SecretKey = SecretKeySpec(aesKey, SYM_CRYPTO_METHOD)
|
||||
|
||||
val cipher = Cipher.getInstance(SYM_CRYPTO_PADDING)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
||||
|
||||
val encryptedBytes = cipher.doFinal(mac + payload)
|
||||
val iv = cipher.iv
|
||||
|
||||
return listOf(encryptedBytes, iv);
|
||||
}
|
||||
|
||||
private fun symDecrypt(payload: Collection<ByteArray>, aesKey: ByteArray): ByteArray? {
|
||||
|
||||
val key: SecretKey = SecretKeySpec(aesKey, SYM_CRYPTO_METHOD)
|
||||
val cipher = Cipher.getInstance(SYM_CRYPTO_PADDING);
|
||||
val iv = payload.last();
|
||||
val ivSpec = IvParameterSpec(iv)
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
||||
|
||||
val decryptedBytes = cipher.doFinal(payload.first());
|
||||
|
||||
val mac = decryptedBytes.copyOfRange(0, 32)
|
||||
val decryptedContent = decryptedBytes.copyOfRange(32, decryptedBytes.size)
|
||||
val verificationMac = digest(aesKey + decryptedContent)
|
||||
|
||||
if (mac.contentEquals(verificationMac)) return decryptedContent
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package fr.pointcheval.native_crypto
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
class PlatformVersionException(message : String) : Exception(message)
|
Loading…
x
Reference in New Issue
Block a user