Native Secured Storage

native_secured_storage is a Flutter plugin designed for securely storing and retrieving primitive data types (int, double, bool, and String) using Android Keystore and iOS Keychain. This ensures data is encrypted at rest and protected against unauthorized access, even if the device is compromised.


Why Use native_secured_storage?

Security Features

  1. Android Keystore with EncryptedSharedPreferences:

    • Uses AES256 encryption with a unique key stored securely in the Android Keystore.
    • Ensures encryption and decryption happen only within the device.
  2. iOS Keychain:

    • Integrates directly with Apple's Keychain, providing system-level security for sensitive data.
    • Protects data using the device’s secure enclave, if available.
  3. No Plaintext Storage:

    • All data is encrypted before being saved, ensuring that sensitive information is never stored in plaintext.
  4. Platform Isolation:

    • Separate implementations for Android and iOS ensure data is stored securely in each platform's native environment.

Use Cases

  • Authentication: Securely store tokens, passwords, or sensitive user credentials.
  • Preferences: Save encrypted app preferences such as user settings or feature flags.
  • Secure Identifiers: Store unique identifiers or keys for app logic.

Features

  • Cross-platform: Supports both Android and iOS.
  • Type Support: Securely handles int, double, bool, and String types.
  • Easy-to-use API: Simplified Dart API for saving, retrieving, and deleting data.
  • Error Handling: Comprehensive error handling to catch issues during encryption or storage operations.

Installation

Add native_secured_storage to your pubspec.yaml:

dependencies:
  native_secured_storage: ^1.0.0

Run the following command to install the package:

flutter pub get

Usage

Import the Package

import 'package:native_secured_storage/native_secured_storage.dart';

Save a Value

await NativeSecuredStorage.save('key_name', 123); // Save an integer
await NativeSecuredStorage.save('key_name', 45.67); // Save a double
await NativeSecuredStorage.save('key_name', true); // Save a boolean
await NativeSecuredStorage.save('key_name', 'Hello World'); // Save a string

Retrieve a Value

final value = await NativeSecuredStorage.retrieve('key_name');
if (value != null) {
  print('Retrieved value: $value');
} else {
  print('No value found for the given key.');
}

Delete a Value

await NativeSecuredStorage.delete('key_name');
print('Value deleted successfully.');

Example

Below is a complete example using native_secured_storage:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Native Secured Storage Example',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: SecureStorageDemo(),
    );
  }
}

class SecureStorageDemo extends StatefulWidget {
  @override
  _SecureStorageDemoState createState() => _SecureStorageDemoState();
}

class _SecureStorageDemoState extends State<SecureStorageDemo> {
  final TextEditingController _keyController = TextEditingController();
  final TextEditingController _valueController = TextEditingController();
  String? _retrievedValue;

  Future<void> _saveValue() async {
    final key = _keyController.text;
    final value = _valueController.text;

    if (key.isEmpty || value.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Key and value cannot be empty')),
      );
      return;
    }

    dynamic parsedValue;
    if (value.toLowerCase() == 'true' || value.toLowerCase() == 'false') {
      parsedValue = value.toLowerCase() == 'true';
    } else if (double.tryParse(value) != null) {
      parsedValue = value.contains('.') ? double.parse(value) : int.parse(value);
    } else {
      parsedValue = value;
    }

    try {
      await NativeSecuredStorage.save(key, parsedValue);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Value saved successfully')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error saving value: $e')),
      );
    }
  }

  Future<void> _retrieveValue() async {
    final key = _keyController.text;

    if (key.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Key cannot be empty')),
      );
      return;
    }

    try {
      final value = await NativeSecuredStorage.retrieve(key);
      setState(() {
        _retrievedValue = value?.toString();
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Value retrieved successfully')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error retrieving value: $e')),
      );
    }
  }

  Future<void> _deleteValue() async {
    final key = _keyController.text;

    if (key.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Key cannot be empty')),
      );
      return;
    }

    try {
      await NativeSecuredStorage.delete(key);
      setState(() {
        _retrievedValue = null;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Value deleted successfully')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error deleting value: $e')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Secure Storage Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            TextField(
              controller: _keyController,
              decoration: InputDecoration(labelText: 'Key'),
            ),
            TextField(
              controller: _valueController,
              decoration: InputDecoration(labelText: 'Value'),
            ),
            SizedBox(height: 16),
            Row(
              children: [
                ElevatedButton(
                  onPressed: _saveValue,
                  child: Text('Save'),
                ),
                SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _retrieveValue,
                  child: Text('Retrieve'),
                ),
                SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _deleteValue,
                  child: Text('Delete'),
                ),
              ],
            ),
            SizedBox(height: 16),
            if (_retrievedValue != null)
              Text('Retrieved Value: $_retrievedValue'),
          ],
        ),
      ),
    );
  }
}

Testing

Run the example app and perform the following actions:

  1. Save values of different types (int, double, bool, String).
  2. Retrieve and verify the values.
  3. Delete a value and confirm it’s removed.

License

This plugin is released under the MIT License.