AES Encryption - Decryption in Dart - Flutter

5,247

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"
        }
    }
}
Share:
5,247
Vettiyanakan
Author by

Vettiyanakan

Previously Web, IOS and Android developer, Now Fluttering with Dart.

Updated on December 30, 2022

Comments

  • Vettiyanakan
    Vettiyanakan over 1 year

    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;
        }
    }
    
    • Yash Kadiya
      Yash Kadiya almost 3 years
      if nothing works for you than, you can create method channel for that specific java functionality, and call it from flutter.
    • Taha Asif
      Taha Asif almost 3 years
      I suppose you are not getting the correct encrypted and decrypted text after using above mentioned flutter packages. Show your flutter code as well.
    • Michael Fehr
      Michael Fehr almost 3 years
      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.
  • Arun Sriramula
    Arun Sriramula over 2 years
    While using flutter code having an issue with encryption channel, "Unhandled Exception: MissingPluginException(No implementation found for method encrypt on channel enc/dec)"
  • Vettiyanakan
    Vettiyanakan over 2 years
    @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.
  • Arun Sriramula
    Arun Sriramula over 2 years
    @Vettityanakan Platform is Flutter, although I got a simple solution from flutter's own library "encrypt".
  • Vettiyanakan
    Vettiyanakan over 2 years
    @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.
  • Arun Sriramula
    Arun Sriramula over 2 years
    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.
  • Vettiyanakan
    Vettiyanakan over 2 years
    @ArunSriramula Open android or ios project separately and try to run it.
  • Ramkesh Yadav
    Ramkesh Yadav almost 2 years
    Yes, i was looking for this from a day. Thanks Dear!!
  • Ramkesh Yadav
    Ramkesh Yadav almost 2 years
    Android decryption is working smoothly, But for iOS its giving error ========== while decryption "2022-06-20 19:04:36.599505+0530 Runner[94495:7319981] Runner/CryptoHelper.swift:29: Fatal error: Unexpectedly found nil while unwrapping an Optional value"============== Nil Error coming in these 2 lines : let data = Data(base64Encoded: dataFromFlutter) let decrypted = try AES(key: keyFromFlutter, iv: iv, padding: .pkcs5).decrypt(data!.bytes)
  • Vettiyanakan
    Vettiyanakan almost 2 years
    @RamkeshYadav Please make sure dataFromFlutter :String, keyFromFlutter :String this values are not null. try to print it before using it.
  • Ramkesh Yadav
    Ramkesh Yadav almost 2 years
    @Vettiyanakan yes i have did a cross check, the values and key are not null and key is same which we have used for encryption, but "let data = Data(base64Encoded: dataFromFlutter)" unable to decode the data. Kindly suggest.