security – Attempt: securing data-in-use in Flutter application

Theres many ways and options for protecting data-in-transit as well as data-at-rest. There are also many ways of protecting authentication as well as authorization. However in my research over the past months I haven’t come across any easy high-level ways of securing data-in-use. So I put something together today that may be somewhat of a programmatic high-level solution for protecting data-in-use in Flutter apps or really any app or software.

I would like input on this way of doing things. I want to know plus points as well as negatives and vulnerabilities in this method. I’m providing everything one would need to recreate the project below.

My programmatic approach to protecting data-in-use:

Firstly not sure if this would work in a real Flutter app because of rebuilding widgets, however read my readme and give some input if you have any ideas.

This approach loads data from secured memory, un-encrypts data, feeds it to user, re-encrypts data with new keys, scrambling old keys out of memory, closes data at rest & finally erases newly re-encrypted data in memory.

CODE

import 'dart:io';
import 'package:crypton/crypton.dart';
import 'package:hive/hive.dart';
import 'package:steel_crypt/steel_crypt.dart';

Future<void> main(List<String> arguments) async {
  // Initialize Hive directory
  Hive.init('PROJECT_STORAGE');

  ///
  ///
  /// Asymmetric example
  Box<String> asymmetricBox = await Hive.openBox<String>('asymmetric_box');

  // 1 INITIALIZE DEMO - Create keys & data
  print('Initializing asymmetric demo data');
  RSAKeypair rsaKeypair = RSAKeypair.fromRandom();
  String sensitiveDataAsymmetric = 'Super_secret_stuff_asymmetric';

  // 2 INITIALIZE DEMO - Encrypt data
  String asymmetricallyEncryptedData = rsaKeypair.publicKey.encrypt(sensitiveDataAsymmetric);

  // 3 INITIALIZE DEMO - Save keys & data
  await asymmetricBox.add(asymmetricallyEncryptedData);
  await asymmetricBox.add(rsaKeypair.privateKey.toFormattedPEM());
  await asymmetricBox.add(rsaKeypair.publicKey.toFormattedPEM());

  // 4 INITIALIZE DEMO - Scramble sensitive data with new misdirector keys, rescramble keys & reassign data in memory
  rsaKeypair = RSAKeypair.fromRandom();
  sensitiveDataAsymmetric = rsaKeypair.publicKey.encrypt(sensitiveDataAsymmetric);
  rsaKeypair = RSAKeypair.fromRandom();
  sensitiveDataAsymmetric = '';

  // 5 INITIALIZE DEMO - Close Hive box
  await asymmetricBox.close();

  sleep(Duration(seconds: 2));
  print('''
Begin asymmetric test ----------
''');

  // 1 TEST DEMO - Load private key from persistent storage
  Box<String> reopenedAsymmetricBox = await Hive.openBox<String>('asymmetric_box');
  bool boxOpen = reopenedAsymmetricBox.isOpen;
  (boxOpen) ? print('Box open') : print('Box closed');
  RSAPrivateKey privateKey = RSAPrivateKey.fromPEM(reopenedAsymmetricBox.getAt(1)!);

  // 2 TEST DEMO - Load, decrypt & display sensitive data
  String decryptedData = privateKey.decrypt(reopenedAsymmetricBox.getAt(0)!);
  print('Decrypted data: ' + decryptedData);

  // 3 TEST DEMO - Secure data, keys & storage
  await reopenedAsymmetricBox.close();
  (boxOpen) ? print('Box open') : print('Box closed');
  print('Exposed memory private key: ' + privateKey.toFormattedPEM());
  privateKey = RSAKeypair.fromRandom().privateKey;
  print('Misdirected private key: ' + privateKey.toFormattedPEM());
  RSAKeypair misdirectorKeys = RSAKeypair.fromRandom();
  decryptedData = misdirectorKeys.publicKey.encrypt(decryptedData);
  misdirectorKeys = RSAKeypair.fromRandom();
  print('Decrypted data re-encrypted: ' + decryptedData);
  decryptedData = '';
  print('Data cleared: ' + decryptedData);

  ///
  ///
  /// Symmetric example
  Box<String> symmetricBox = await Hive.openBox<String>('symmetric_box');

  // 1 INITIALIZE DEMO - Create keys & data
  print('''
Initializing symmetric demo data
''');
  String symmetricKey = CryptKey().genFortuna();
  String symmetricNonce = CryptKey().genDart(len: 12);
  AesCrypt symmetricCypher = AesCrypt(key: symmetricKey, padding: PaddingAES.pkcs7);
  String sensitiveDataSymmetric = 'Super_secret_stuff_symmetric';

  // 2 INITIALIZE DEMO - Encrypt data
  String symmetricallyEncryptedData =
      symmetricCypher.gcm.encrypt(inp: sensitiveDataSymmetric, iv: symmetricNonce);

  // 3 INITIALIZE DEMO - Save keys & data
  await symmetricBox.add(symmetricallyEncryptedData);
  await symmetricBox.add(symmetricKey);
  await symmetricBox.add(symmetricNonce);

  // 4 INITIALIZE DEMO - Scramble sensitive data with new misdirector keys, rescramble keys & reassign data in memory
  symmetricKey = CryptKey().genFortuna();
  symmetricNonce = CryptKey().genDart(len: 12);
  symmetricCypher = AesCrypt(key: symmetricKey, padding: PaddingAES.pkcs7);
  sensitiveDataSymmetric = symmetricCypher.gcm.encrypt(inp: sensitiveDataSymmetric, iv: symmetricNonce);
  sensitiveDataSymmetric = '';

  // 5 INITIALIZE DEMO - Close Hive box
  await symmetricBox.close();

  sleep(Duration(seconds: 2));
  print('''
Begin symmetric test ----------
''');

  // 1 TEST DEMO - Load private key from persistent storage
  Box<String> reopenedSymmetricBox = await Hive.openBox<String>('symmetric_box');
  boxOpen = reopenedSymmetricBox.isOpen;
  (boxOpen) ? print('Box open') : print('Box closed');
  AesCrypt symmetricCypherII = AesCrypt(key: reopenedSymmetricBox.getAt(1)!, padding: PaddingAES.pkcs7);

  // 2 TEST DEMO - Load, decrypt & display sensitive data
  String decryptedDataII =
      symmetricCypherII.gcm.decrypt(enc: reopenedSymmetricBox.getAt(0)!, iv: reopenedSymmetricBox.getAt(2)!);
  print('Decrypted data: ' + decryptedDataII);

  // 3 TEST DEMO - Secure data, keys & storage
  await reopenedSymmetricBox.close();
  (boxOpen) ? print('Box open') : print('Box closed');
  String symmetricKeyScramble = CryptKey().genFortuna();
  String symmetricNonceScramble = CryptKey().genDart(len: 12);
  symmetricCypherII = AesCrypt(padding: PaddingAES.pkcs7, key: symmetricKeyScramble);
  decryptedDataII = symmetricCypherII.gcm.encrypt(inp: decryptedDataII, iv: symmetricNonceScramble);
  misdirectorKeys = RSAKeypair.fromRandom();
  print('Decrypted data re-encrypted: ' + decryptedDataII);
  decryptedDataII = '';
  print('Data cleared: ' + decryptedDataII);
}

LIBRARIES

dependencies: 
  crypton: ^2.0.2
  hive: ^2.0.4
  steel_crypt: ^3.0.0+1

README

# demo_project_security_data_at_rest
    - Testing and experimental protection of data-in-use

# Project purpose:
    - Main purpose is to experiment with methods for protecting data-in-use
    - DATA SECURITY IS OF THE HIGHEST PRIORITY OVER RESOURCE MANAGEMENT (let the processor do its job baby)
    - Overall purpose is also to cut down on possibility of memory style attacks such as memory dumps, cold boot attack etc.
    - This is being done at a high level with simple actions such as:
      - Immediately re-encrypting decrypted data after use is garnered from exposed decrypted data
      - Immediately deleting from memory the previously re-encrypted data
  
    - Essentially this means:
      - 1: Decrypt data for immediate use
      - 2: Load data for user
      - 3: Re-encrypt data in-memory
      - 4: Delete re-encrypted data from memory
      - Note: the same would go for all keys used for encryption and decryption
        - The storage avenue used for keys would always be flutter_secure_storage i.e. iOS Keychain & Android Keystore

# This project features:
    - Steel Crypt package for symmetric encryption
    - Crypton package for asymmetric encryption
    - Hive boxes for storing data-at-rest & keys (for the sake of this demo project, non-encrypted boxes)

    - Note:
      - In a Flutter application:
        - Hive encrypted boxes would be used for data-at-rest storage
        - Flutter_secure_storage would be used for key storage
        - Steel Crypt & Crypton would be used for data-in-transit cryptography

# Possible issue:
- This is a Dart application only, so it should work as intended above with simple print statements. However when using rebuilding widgets I imagine that the widgets would reflect re-encrypted and also deleted data as they occur. Essentially due to computing speed the user wouldn't even see decrypted useful data for a split-second much less be able to use their data as normally. Must find a way around this.

- This will be posted for help on Stack Overflow and/or Information Security prior to attempting a Flutter graphical test.
```