Data encryption is a crucial aspect of mobile app security, especially when dealing with sensitive user information. In Flutter, securing locally stored data is essential for protecting user privacy and preventing unauthorized access. This blog post delves into how to implement data encryption for local storage in Flutter, covering various techniques, libraries, and best practices.
Why Encrypt Local Storage in Flutter?
- Protect Sensitive Data: Safeguard personal user information, financial details, and other sensitive data.
- Prevent Unauthorized Access: Ensure that even if the device is compromised, the data remains unreadable without the correct decryption key.
- Comply with Regulations: Meet data protection standards and regulatory requirements such as GDPR and CCPA.
- Enhance User Trust: Demonstrate a commitment to user privacy and data security, improving user confidence in your application.
Encryption Techniques for Local Storage
Several techniques can be employed to encrypt local storage in Flutter:
1. Symmetric Encryption
Symmetric encryption uses the same key to encrypt and decrypt data. This method is faster and more efficient for large amounts of data.
Using encrypt
Package
The encrypt
package in Flutter provides implementations of common symmetric encryption algorithms.
Step 1: Add Dependency
Include the encrypt
package in your pubspec.yaml
file:
dependencies:
encrypt: ^5.0.1
Step 2: Implement Encryption and Decryption
import 'package:encrypt/encrypt.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class EncryptionService {
static final _key = Key.fromLength(32); // 256-bit key
static final _iv = IV.fromLength(16); // 128-bit initialization vector
static final _encrypter = Encrypter(AES(_key));
static Future encryptData(String plainText) async {
final encrypted = _encrypter.encrypt(plainText, iv: _iv);
return encrypted.base64;
}
static Future decryptData(String encryptedText) async {
final encrypted = Encrypted.fromBase64(encryptedText);
return _encrypter.decrypt(encrypted, iv: _iv);
}
}
Future main() async {
final plainText = "Sensitive data to be encrypted";
final encryptedText = await EncryptionService.encryptData(plainText);
final decryptedText = await EncryptionService.decryptData(encryptedText);
print("Plain Text: $plainText");
print("Encrypted Text: $encryptedText");
print("Decrypted Text: $decryptedText");
}
In this example:
- A 256-bit key and a 128-bit initialization vector (IV) are created.
- The
AES
algorithm is used for encryption. - The
encryptData
function encrypts the plaintext using the AES algorithm. - The
decryptData
function decrypts the ciphertext back into plaintext.
2. Asymmetric Encryption
Asymmetric encryption uses a pair of keys—a public key for encryption and a private key for decryption. This method is more secure but computationally expensive and less suitable for large data.
Using RSA Algorithm
You can use the RSA algorithm for asymmetric encryption in Flutter. However, keep in mind the performance implications.
Step 1: Generate RSA Key Pair
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/key_generators/rsa_key_generator.dart';
import 'package:pointycastle/random/fortuna.dart';
class RSAHelper {
static AsymmetricKeyPair<PublicKey, PrivateKey> generateKeyPair() {
final secureRandom = FortunaRandom();
final random = SecureRandom("AES/CTR/AUTO-SEED-PRNG");
final keyGen = RSAKeyGenerator()
..init(ParametersWithRandom(
RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 12),
random));
return keyGen.generateKeyPair();
}
}
void main() {
final keyPair = RSAHelper.generateKeyPair();
final publicKey = keyPair.publicKey as RSAPublicKey;
final privateKey = keyPair.privateKey as RSAPrivateKey;
print("Public Key: $publicKey");
print("Private Key: $privateKey");
}
Step 2: Implement RSA Encryption and Decryption
import 'dart:convert';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/encrypt/rsa.dart';
import 'package:pointycastle/params/rsa/pkcs1_encoding_parameters.dart';
import 'package:pointycastle/padding/pkcs7.dart';
class RSAEncryptionService {
static String encrypt(String plainText, RSAPublicKey publicKey) {
final cipher = PKCS1Encoding(RSAEngine())
..init(true, PublicKeyParameter<RSAPublicKey>(publicKey));
final plainTextBytes = utf8.encode(plainText);
final encryptedBytes = cipher.process(plainTextBytes);
return base64.encode(encryptedBytes);
}
static String decrypt(String encryptedText, RSAPrivateKey privateKey) {
final cipher = PKCS1Encoding(RSAEngine())
..init(false, PrivateKeyParameter<RSAPrivateKey>(privateKey));
final encryptedBytes = base64.decode(encryptedText);
final decryptedBytes = cipher.process(encryptedBytes);
return utf8.decode(decryptedBytes);
}
}
void main() {
final keyPair = RSAHelper.generateKeyPair();
final publicKey = keyPair.publicKey as RSAPublicKey;
final privateKey = keyPair.privateKey as RSAPrivateKey;
final plainText = "Sensitive data to be encrypted with RSA";
final encryptedText = RSAEncryptionService.encrypt(plainText, publicKey);
final decryptedText = RSAEncryptionService.decrypt(encryptedText, privateKey);
print("Plain Text: $plainText");
print("Encrypted Text: $encryptedText");
print("Decrypted Text: $decryptedText");
}
Using RSA in Flutter requires handling keys securely and understanding the performance trade-offs.
3. Hybrid Encryption
Hybrid encryption combines the benefits of both symmetric and asymmetric encryption. It involves encrypting data with a symmetric key, which is then encrypted with the recipient’s public key. The recipient decrypts the symmetric key with their private key and uses it to decrypt the data.
Securely Storing Encryption Keys
Storing encryption keys securely is as critical as the encryption itself. Compromised keys render encryption useless.
Using flutter_secure_storage
The flutter_secure_storage
plugin provides secure storage for sensitive data like encryption keys. It uses platform-specific secure storage mechanisms (Keychain on iOS, Keystore on Android).
Step 1: Add Dependency
Include flutter_secure_storage
in your pubspec.yaml
file:
dependencies:
flutter_secure_storage: ^9.0.0
Step 2: Store and Retrieve Keys
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureKeyStorage {
static final _storage = FlutterSecureStorage();
static Future saveKey(String keyName, String keyValue) async {
await _storage.write(key: keyName, value: keyValue);
}
static Future readKey(String keyName) async {
return await _storage.read(key: keyName);
}
static Future deleteKey(String keyName) async {
await _storage.delete(key: keyName);
}
}
Future main() async {
const keyName = 'myEncryptionKey';
const keyValue = 'yourSecretEncryptionKey';
// Store the encryption key securely
await SecureKeyStorage.saveKey(keyName, keyValue);
// Retrieve the encryption key
final retrievedKey = await SecureKeyStorage.readKey(keyName);
print("Retrieved Key: $retrievedKey");
// Delete the encryption key when no longer needed
// await SecureKeyStorage.deleteKey(keyName);
}
Using flutter_secure_storage
ensures that keys are stored securely in platform-specific secure storage.
Practical Implementation: Encrypting SharedPreferences
SharedPreferences
is commonly used for storing simple data in Flutter. Encrypting the data stored in SharedPreferences
ensures sensitive information remains protected.
Step 1: Encrypt Data Before Saving
import 'package:shared_preferences/shared_preferences.dart';
Future saveEncryptedData(String key, String value) async {
final encryptedValue = await EncryptionService.encryptData(value);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, encryptedValue);
}
Step 2: Decrypt Data After Retrieving
Future getDecryptedData(String key) async {
final prefs = await SharedPreferences.getInstance();
final encryptedValue = prefs.getString(key);
if (encryptedValue == null) {
return null;
}
return await EncryptionService.decryptData(encryptedValue);
}
Complete Example
import 'package:encrypt/encrypt.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
class EncryptionService {
static final _key = Key.fromLength(32);
static final _iv = IV.fromLength(16);
static final _encrypter = Encrypter(AES(_key));
static Future encryptData(String plainText) async {
final encrypted = _encrypter.encrypt(plainText, iv: _iv);
return encrypted.base64;
}
static Future decryptData(String encryptedText) async {
final encrypted = Encrypted.fromBase64(encryptedText);
return _encrypter.decrypt(encrypted, iv: _iv);
}
}
class SecureKeyStorage {
static final _storage = FlutterSecureStorage();
static Future saveKey(String keyName, String keyValue) async {
await _storage.write(key: keyName, value: keyValue);
}
static Future readKey(String keyName) async {
return await _storage.read(key: keyName);
}
static Future deleteKey(String keyName) async {
await _storage.delete(key: keyName);
}
}
Future saveEncryptedData(String key, String value) async {
final encryptedValue = await EncryptionService.encryptData(value);
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, encryptedValue);
}
Future getDecryptedData(String key) async {
final prefs = await SharedPreferences.getInstance();
final encryptedValue = prefs.getString(key);
if (encryptedValue == null) {
return null;
}
return await EncryptionService.decryptData(encryptedValue);
}
Future main() async {
const dataKey = 'sensitiveData';
const plainText = 'My secret data to be stored securely.';
// Save encrypted data
await saveEncryptedData(dataKey, plainText);
print('Encrypted data saved successfully.');
// Retrieve and decrypt data
final decryptedData = await getDecryptedData(dataKey);
print('Decrypted data: $decryptedData');
}
Best Practices for Data Encryption
- Use Strong Encryption Algorithms: Choose well-established encryption algorithms like AES or RSA with appropriate key lengths.
- Securely Manage Keys: Store encryption keys using platform-specific secure storage mechanisms (
flutter_secure_storage
). - Regularly Update Encryption Libraries: Keep encryption libraries updated to patch vulnerabilities.
- Implement Defense in Depth: Combine encryption with other security measures, such as data masking and access controls.
- Handle Exceptions and Errors Gracefully: Ensure proper error handling to prevent data leakage in case of encryption or decryption failures.
- Conduct Security Audits: Regularly audit your code and security measures to identify and address potential vulnerabilities.
Conclusion
Implementing data encryption for local storage in Flutter is essential for protecting user privacy and ensuring data security. By using robust encryption techniques, securely managing keys, and adhering to best practices, you can build Flutter applications that protect sensitive user information. Whether using symmetric, asymmetric, or hybrid encryption, the key is to prioritize security and maintain vigilance against potential threats.