Encrypting Local Storage in Flutter with Hive

Flutter applications often need to store data locally, and Hive is a popular, lightweight NoSQL database solution for this purpose. However, storing sensitive information in plain text can pose significant security risks. Encrypting local storage with Hive is a crucial step to protect user data from unauthorized access.

What is Hive?

Hive is a fast and lightweight NoSQL database written in pure Dart. It’s well-suited for Flutter applications needing a local data store. Hive supports encryption, making it a good choice for applications handling sensitive data.

Why Encrypt Local Storage?

  • Data Protection: Prevents unauthorized access to sensitive information.
  • Compliance: Helps meet data protection regulations.
  • Security: Mitigates the risk of data breaches, even if the device is compromised.

How to Encrypt Local Storage with Hive in Flutter

To implement encryption with Hive, you’ll need the Hive and Hive Flutter packages along with the encrypt package to handle the encryption process. This approach provides a secure way to store and retrieve sensitive data in your Flutter app.

Step 1: Add Dependencies

Include the necessary dependencies in your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  hive: ^2.2.3
  hive_flutter: ^1.1.2
  encrypt: ^5.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  hive_generator: ^2.0.1
  build_runner: ^2.4.8

Run flutter pub get to install the dependencies.

Step 2: Initialize Hive

In your main.dart file, initialize Hive:

import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Hive.initFlutter();
  // Register adapters if needed (e.g., Hive.registerAdapter(MyTypeAdapter()))
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Encrypted Hive Demo',
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Encrypted Hive Demo')),
      body: Center(
        child: ElevatedButton(
          child: Text('Store Secret'),
          onPressed: () async {
            await storeSecret(context);
          },
        ),
      ),
    );
  }

  Future storeSecret(BuildContext context) async {
    final box = await Hive.openBox('myBox');
    box.put('secret', 'My Super Secret Data');
    print('Secret stored');
    await Future.delayed(Duration(seconds: 2));
    final secret = box.get('secret');
    print('Secret fetched: $secret');
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Stored Secret: $secret')));

  }
}

Step 3: Generate Encryption Key

Hive requires an encryption key to secure the data. It’s best to generate a secure key and store it securely. This example generates a key, which should be managed securely in a real-world application.

import 'package:encrypt/encrypt.dart' as enc;
import 'package:hive/hive.dart';

Future generateKey() async {
  final key = enc.Key.fromSecureRandom(32);
  final encryptedKey = key.base64; // Store this securely (e.g., KeyChain)
  print('Generated Encryption Key: $encryptedKey'); // Ensure key generation is logged
  return HiveAesCipher(key);
}

Step 4: Open Encrypted Box

Open your Hive box with encryption:

import 'dart:io';

import 'package:encrypt/encrypt.dart' as enc;
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Directory appDocDir = await getApplicationDocumentsDirectory();
  Hive.init(appDocDir.path);

  HiveCipher? hiveCipher = await generateKey(); // Generate or retrieve stored encryption key

  // Delete box if exists
  try {
    await Hive.deleteBoxFromDisk('encryptedBox');
  } catch (e) {
    print("Error deleting the box before: $e");
  }
  final encryptedBox = await Hive.openBox('encryptedBox', encryptionCipher: hiveCipher);

  encryptedBox.put('message', 'My Super Secret'); // Correct usage with encryptedBox
  print('Stored to encypted Box');
  runApp(MyApp(encryptedBox));
}

class MyApp extends StatelessWidget {
  final Box encryptedBox;
  MyApp(this.encryptedBox);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Encrypt Hive Data Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                  onPressed: () async {
                    // Usage

                    try {
                      final storedMessage = encryptedBox.get('message'); // Correct usage with encryptedBox
                      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('The Stored Secret is : ${storedMessage}')));

                      print("retrieved The Encripted secret");
                    } catch (e) {
                      print('box failed because =  $e');
                    }
                  },
                  child: Text('Print encrypted string!'))
            ],
          ),
        ),
      ),
    );
  }
}

Future generateKey() async {
  final key = enc.Key.fromSecureRandom(32);
  final encryptedKey = key.base64; // Store this securely (e.g., KeyChain)
  print('Generated Encryption Key: $encryptedKey'); // Ensure key generation is logged
  return HiveAesCipher(key);
}

Step 5: Securely Storing the Key

Storing the encryption key securely is vital. Using the flutter_secure_storage package or platform-specific keystore solutions like Android’s KeyStore or iOS’s Keychain is highly recommended.

For using the KeyChain to strore secure date in the Flutter:

flutter pub add flutter_secure_storage
import 'package:encrypt/encrypt.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureKeyStorage {
  static const _key = 'hive_encryption_key';
  final _storage = FlutterSecureStorage();

  Future getKey() async {
    try {
      final storedKey = await _storage.read(key: _key);
      if (storedKey == null) {
        return null;
      }
      return Key.fromBase64(storedKey);
    } catch (e) {
      print('Error reading key: $e');
      return null;
    }
  }

  Future saveKey(Key key) async {
    try {
      await _storage.write(key: _key, value: key.base64);
      print('Key saved successfully');
    } catch (e) {
      print('Error saving key: $e');
    }
  }

  Future deleteKey() async {
    try {
      await _storage.delete(key: _key);
      print('Key deleted successfully');
    } catch (e) {
      print('Error deleting key: $e');
    }
  }
}

Future generateKeySecure() async {
  final secureKeyStorage = SecureKeyStorage();
  Key? key = await secureKeyStorage.getKey();

  if (key == null) {
    key = Key.fromSecureRandom(32);
    await secureKeyStorage.saveKey(key);
  }

  print('Encryption Key (Securely Retrieved/Generated): ${key.base64}');
  return HiveAesCipher(key);
}

Complete Example: Integrating Encryption with Hive

Here’s a comprehensive example demonstrating how to integrate Hive encryption within your Flutter application:

import 'dart:io';
import 'package:encrypt/encrypt.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Get the application documents directory
  Directory appDocDir = await getApplicationDocumentsDirectory();
  Hive.init(appDocDir.path);

  // Securely retrieve or generate the encryption key
  HiveCipher? hiveCipher = await SecureKeyManager.getHiveCipher();

  try {
    // Ensure the box is deleted before reopening if you encounter initialization issues during development.
    // NEVER SHIP THIS TO PRODUCTION!  This is purely for debugging and development.
    await Hive.deleteBoxFromDisk('secureBox');
    print('Deleted the old Hive box.');
  } catch (e) {
    print('Error deleting Hive box: $e');
  }
  // Open the box with the encryption cipher.
  final secureBox = await Hive.openBox('secureBox', encryptionCipher: hiveCipher);
  print('Opened secureBox successfully.');
  secureBox.put('message', 'My Super Secret');

  // Start the Flutter app
  runApp(MyApp(secureBox));
}

class MyApp extends StatelessWidget {
  final Box secureBox;
  MyApp(this.secureBox);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Hive Encryption with Secure Storage'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              try {
                // Retrieve data from the secure box
                final storedMessage = secureBox.get('message');
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('The Stored Secret is: $storedMessage'),
                  ),
                );
                print("Retrieved the encrypted secret successfully");
              } catch (e) {
                print('Error retrieving encrypted value: $e');
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Failed to retrieve encrypted value. Check logs.'),
                    backgroundColor: Colors.red,
                  ),
                );
              }
            },
            child: Text('Get encrypted string!'),
          ),
        ),
      ),
    );
  }
}

class SecureKeyManager {
  static const _keyName = 'hive_box_key';
  static final _secureStorage = FlutterSecureStorage();

  /// Retrieve the HiveCipher
  static Future getHiveCipher() async {
    final key = await _getExistingKeyOrCreateNew();
    if (key == null) return null; // Handle appropriately in a real app

    return HiveAesCipher(key);
  }

  /// Attempt to get an existing key, or create a new one if it does not exist
  static Future _getExistingKeyOrCreateNew() async {
    // Check if the encryption key exists in secure storage
    String? base64Key = await _secureStorage.read(key: _keyName);

    if (base64Key == null) {
      // Key does not exist, let's create a new one
      final key = Key.fromSecureRandom(32);
      base64Key = key.base64;

      // Store the key securely
      await _secureStorage.write(key: _keyName, value: base64Key);
      print('Generated and stored a new encryption key');
    } else {
      print('Retrieved existing encryption key');
    }

    // Return the Key object
    return Key.fromBase64(base64Key);
  }
}

Conclusion

Encrypting local storage in Flutter with Hive enhances the security of your application by protecting sensitive data. Implementing Hive encryption ensures that even if a device is compromised, the data remains unreadable without the correct encryption key. Follow the steps outlined above, always manage encryption keys securely, and consider using secure storage options for the best data protection practices in your Flutter applications.