5

I have an app live in store with api AES encryption, I am trying to port the app to Flutter. I am stuck at the encryption/decryption part. I cannot change the encryption in api because its live.

I have tried the Encrypt, aes_crypt, pointycastle packages. Still stuck. not sure what I am missing. I couldn't find IvParameterSpec, SecretKeySpec and ("AES/CBC/PKCS5Padding") options in these packages.

This is the CryptoHelper java class I am using in the android app.

package com.example.secureApp;

import android.util.Base64;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoHelper {
    String keyValue;
    private final IvParameterSpec ivSpec;
    private final SecretKeySpec keySpec;
    private Cipher cipher;
    private final static String ivKey = "912QWA56CFB3SA3F"; // DUMMY 16 char secret key

    public CryptoHelper() {
        keyValue = "d2AQuZZDfTIlZeXW"; // DUMMY 16 char secret key
        ivSpec = new IvParameterSpec(ivKey.getBytes());
        keySpec = new SecretKeySpec(keyValue.getBytes(), "AES");
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }

    public static String encrypt(String valueToEncrypt) throws Exception {
        CryptoHelper enc = new CryptoHelper();
        return Base64.encodeToString(enc.encryptInternal(valueToEncrypt), Base64.DEFAULT);
    }

    public static String decrypt(String valueToDecrypt) throws Exception {
        CryptoHelper enc = new CryptoHelper();
        return new String(enc.decryptInternal(valueToDecrypt));
    }

    private byte[] encryptInternal(String text) throws Exception {
        if (text == null || text.length() == 0) {
            throw new Exception("Empty string");
        }
        byte[] encrypted;
        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            encrypted = cipher.doFinal(text.getBytes());
        } catch (Exception e) {
            throw new Exception("[encrypt] " + e.getMessage());
        }
        return encrypted;
    }

    private byte[] decryptInternal(String code) throws Exception {
        if (code == null || code.length() == 0) {
            throw new Exception("Empty string");
        }
        byte[] decrypted;
        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            decrypted = cipher.doFinal(Base64.decode(code, Base64.DEFAULT));
        } catch (Exception e) {
            throw new Exception("[decrypt] " + e.getMessage());
        }
        return decrypted;
    }
}
3
  • 1
    if nothing works for you than, you can create method channel for that specific java functionality, and call it from flutter. Commented Jun 18, 2021 at 4:02
  • 1
    I suppose you are not getting the correct encrypted and decrypted text after using above mentioned flutter packages. Show your flutter code as well. Commented Jun 18, 2021 at 4:07
  • Just a note as your system is live already: in your encryptInternal-function your are converting the plaintext to bytes using this code: "encrypted = cipher.doFinal(text.getBytes());". Here is a high chance for errors because you do not define a Charset. Commented Jun 18, 2021 at 6:23

1 Answer 1

6

As @Yash Kadiya suggested, I went for Platform Specific code.

Posting it here, Happy coding!:

Flutter

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

void main() async {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Secure App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  String encryptedData = '';
  String decryptedData = '';

  static const encryptionChannel = const MethodChannel('enc/dec');

  Future<void> encryptData(String encrypted, String key) async {
    try {
      var result = await encryptionChannel.invokeMethod(
        'encrypt',
        {
          'data': jsonString,
          'key': key,
        },
      );
      print('RETURNED FROM PLATFORM');
      print(result);
      setState(() {
        encryptedData = result;
      });
    } on PlatformException catch (e) {
      print('${e.message}');
    }
  }

  Future<void> decryptData(String encrypted, String key) async {
    try {
      var result = await encryptionChannel.invokeMethod('decrypt', {
        'data': encrypted,
        'key': key,
      });
      print('RETURNED FROM PLATFORM');
      print(result);
      setState(() {
        decryptedData = result;
      });
    } on PlatformException catch (e) {
      print('${e.message}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            MaterialButton(
              child: Text('Encrypt Data'),
              onPressed: () {
                encryptData('data to be encrypted', '16 character long key');
              },
            ),
            MaterialButton(
              child: Text('Decrypt Data'),
              onPressed: () {
                decryptData('data to be decrypted', '16 character long key');
                // same key used to encrypt
              },
            ),
            Text(encryptedData),
            Text(decryptedData),
          ],
        ),
      ),
    );
  }
}

Android

MainActivity.kt

package com.example.secureapp;

import android.util.Log
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL = "enc/dec";

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler{call, result ->
            if(call.method.equals("encrypt")){
                val data = call.argument<String>("data")
                val key = call.argument<String>("key")
                val cipher = CryptoHelper.encrypt(data, key)
                result.success(cipher)
            }else if(call.method.equals("decrypt")){
                val data = call.argument<String>("data")
                val key = call.argument<String>("key")
                val jsonString = CryptoHelper.decrypt(data, key)
                result.success(jsonString)
            }else{
                result.notImplemented()
            }
        }
    }
}

CryptoHelper.java

package com.example.secureapp;

import android.util.Base64;
import android.util.Log;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoHelper {
    static String keyValue;
    private final IvParameterSpec ivSpec;
    private final SecretKeySpec keySpec;
    private Cipher cipher;
    private final static String ivKey = "RF22SW76BV83EDH8"; //16 char secret key

    public CryptoHelper() {
        ivSpec = new IvParameterSpec(ivKey.getBytes());
        keySpec = new SecretKeySpec(keyValue.getBytes(), "AES");
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }

    public static String encrypt(String valueToEncrypt, String key) throws Exception {
        keyValue = key;
        CryptoHelper enc = new CryptoHelper();
        return Base64.encodeToString(enc.encryptInternal(valueToEncrypt), Base64.DEFAULT);
    }

    public static String decrypt(String valueToDecrypt, String key) throws Exception {
        keyValue = key;
        CryptoHelper enc = new CryptoHelper();
        return new String(enc.decryptInternal(valueToDecrypt));
    }

    private byte[] encryptInternal(String text) throws Exception {
        if (text == null || text.length() == 0) {
            throw new Exception("Empty string");
        }
        byte[] encrypted;
        try {
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            encrypted = cipher.doFinal(text.getBytes());
        } catch (Exception e) {
            throw new Exception("[encrypt] " + e.getMessage());
        }
        return encrypted;
    }

    private byte[] decryptInternal(String code) throws Exception {
        if (code == null || code.length() == 0) {
            throw new Exception("Empty string");
        }
        byte[] decrypted;
        try {
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            decrypted = cipher.doFinal(Base64.decode(code, Base64.DEFAULT));
        } catch (Exception e) {
            throw new Exception("[decrypt] " + e.getMessage());
        }
        return decrypted;
    }
}

IOS

Using CryptoSwift library in ios side

Podfile (add this to podfile)

  pod 'CryptoSwift'

then run pod install

AppDelegate.swift

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        
        let encryptionChannel = FlutterMethodChannel(name: "enc/dec", binaryMessenger: controller.binaryMessenger)
        encryptionChannel.setMethodCallHandler({
          [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
          // Note: this method is invoked on the UI thread.
            if(call.method == "encrypt"){
                guard let args = call.arguments as? [String : Any] else {return}
                let data = args["data"] as! String
                let key = args["key"] as! String
                let encryptedString = CryptoHelper.encrypt(dataFromFlutter: data, keyFromFlutter: key)
                self?.encrypt(result: result, encrypted: encryptedString!)
                return
            }else if(call.method == "decrypt"){
                guard let args = call.arguments as? [String : Any] else {return}
                let data = args["data"] as! String
                let key = args["key"] as! String
                let decryptedString = CryptoHelper.decrypt(dataFromFlutter: data, keyFromFlutter: key)
                self?.decrypt(result: result, decrypted: decryptedString!)
                return
            }else{
                result(FlutterMethodNotImplemented)
                return
            }
        })

        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func encrypt(result: FlutterResult, encrypted: String) {
        result(encrypted)
    }
    
    private func decrypt(result: FlutterResult, decrypted: String) {
        result(decrypted)
    }
    
}

CryptoHelper.swift

import Foundation
import CryptoSwift

var keyValue :String!

class CryptoHelper{
    private static let iv = "RF22SW76BV83EDH8";
    // ENC
    public static func encrypt(dataFromFlutter :String, keyFromFlutter :String) -> String? {
        do{
            let encrypted: Array<UInt8> = try AES(key: keyFromFlutter, iv: iv, padding: .pkcs5).encrypt(Array(dataFromFlutter.utf8))
            return encrypted.toBase64()
        }catch{
            return "DATA ERROR"
        }
    }
    
    // DEC
    public static func decrypt(dataFromFlutter :String, keyFromFlutter :String) -> String? {
        do{
            let data = Data(base64Encoded: dataFromFlutter)
            let decrypted = try AES(key: keyFromFlutter, iv: iv, padding: .pkcs5).decrypt(data!.bytes)
            return String(data: Data(decrypted), encoding: .utf8)
        }catch{
            return "DATA ERROR"
        }
    }
}
Sign up to request clarification or add additional context in comments.

10 Comments

While using flutter code having an issue with encryption channel, "Unhandled Exception: MissingPluginException(No implementation found for method encrypt on channel enc/dec)"
@Arun Sriramula On wich platform? did you add the android and ios platform specific codes? on ios you have to add the library in pod file and run pod install.
@Vettityanakan Platform is Flutter, although I got a simple solution from flutter's own library "encrypt".
@ArunSriramula MissingPluginException this error is occurring because the android or ios specific code have not compiled. I was also using encrypt (pub.dev/packages/encrypt) package. There was some issue with the iv generation. I needed to generate IV with some specific letter. That was not possible with this package. Thats why i came up with this solution.
What's the reason behind it's not compiling? Cause I've even tried flutter clean and running the project again and tried restarting the Android Studio too.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.