Search code examples
flutterdartencryptionaes

AES Encryption - Decryption in Dart - Flutter


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

Solution

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