Update kotlin specific code

This commit is contained in:
Hugo Pointcheval 2020-12-19 17:19:32 +01:00
parent 4d84a938ac
commit 11d43fe64c
7 changed files with 317 additions and 135 deletions

View File

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

View 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package fr.pointcheval.native_crypto
import java.lang.Exception
class PlatformVersionException(message : String) : Exception(message)