Secure Storage Implementation di Flutter

kholil 03 September 2025 Flutter 6 views 0 comment 6 minutes
Gambar Artikel

Secure storage adalah komponen kritis dalam pengembangan aplikasi mobile yang sering diabaikan. Di Flutter, implementasi penyimpanan yang aman melibatkan berbagai teknik dan library untuk melindungi data sensitif seperti token autentikasi, kredensial pengguna, dan informasi pribadi lainnya.

Mengapa Secure Storage Penting?

Data yang tersimpan di local storage biasa dapat dengan mudah diakses oleh: - Aplikasi lain (jika device di-root) - Malware - Physical access ke device - Backup yang tidak terenkripsi

Library Utama untuk Secure Storage

1. flutter_secure_storage

Library paling populer untuk secure storage di Flutter.

dependencies:
  flutter_secure_storage: ^9.0.0

Setup dasar:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
      keyCipherAlgorithm: KeyCipherAlgorithm.RSA_ECB_PKCS1Padding,
      storageCipherAlgorithm: StorageCipherAlgorithm.AES_GCM_NoPadding,
    ),
    iOptions: IOSOptions(
      accessibility: KeychainAccessibility.first_unlock_this_device,
    ),
  );

  // Menyimpan data
  static Future<void> storeData(String key, String value) async {
    await _storage.write(key: key, value: value);
  }

  // Mengambil data
  static Future<String?> getData(String key) async {
    return await _storage.read(key: key);
  }

  // Menghapus data
  static Future<void> deleteData(String key) async {
    await _storage.delete(key: key);
  }

  // Menghapus semua data
  static Future<void> deleteAll() async {
    await _storage.deleteAll();
  }
}

2. Hive dengan Encryption

Alternatif yang powerful untuk data yang lebih kompleks.

dependencies:
  hive: ^2.2.3
  hive_flutter: ^1.1.0
  crypto: ^3.0.3
import 'package:hive_flutter/hive_flutter.dart';
import 'package:crypto/crypto.dart';

class HiveSecureStorage {
  static late Box _secureBox;

  static Future<void> init() async {
    await Hive.initFlutter();

    // Generate encryption key
    final key = Hive.generateSecureKey();

    _secureBox = await Hive.openBox(
      'secureBox',
      encryptionCipher: HiveAesCipher(key),
    );
  }

  static Future<void> store<T>(String key, T value) async {
    await _secureBox.put(key, value);
  }

  static T? get<T>(String key) {
    return _secureBox.get(key);
  }
}

Advanced Security Patterns

1. Token Management dengan Auto-Refresh

class TokenManager {
  static const _accessTokenKey = 'access_token';
  static const _refreshTokenKey = 'refresh_token';
  static const _expiryKey = 'token_expiry';

  static Future<void> saveTokens({
    required String accessToken,
    required String refreshToken,
    required DateTime expiry,
  }) async {
    await Future.wait([
      SecureStorageService.storeData(_accessTokenKey, accessToken),
      SecureStorageService.storeData(_refreshTokenKey, refreshToken),
      SecureStorageService.storeData(_expiryKey, expiry.toIso8601String()),
    ]);
  }

  static Future<String?> getValidAccessToken() async {
    final token = await SecureStorageService.getData(_accessTokenKey);
    final expiryStr = await SecureStorageService.getData(_expiryKey);

    if (token == null || expiryStr == null) return null;

    final expiry = DateTime.parse(expiryStr);
    if (DateTime.now().isAfter(expiry)) {
      // Token expired, try refresh
      return await _refreshToken();
    }

    return token;
  }

  static Future<String?> _refreshToken() async {
    final refreshToken = await SecureStorageService.getData(_refreshTokenKey);
    if (refreshToken == null) return null;

    // Call API to refresh token
    // Implementation depends on your API
    return null;
  }
}

2. Biometric Authentication Integration

import 'package:local_auth/local_auth.dart';

class BiometricSecureStorage {
  static const _auth = LocalAuthentication();

  static Future<bool> _authenticateUser() async {
    try {
      final isAvailable = await _auth.isDeviceSupported();
      if (!isAvailable) return false;

      final isAuthenticated = await _auth.authenticate(
        localizedReason: 'Authenticate to access secure data',
        options: const AuthenticationOptions(
          biometricOnly: true,
          stickyAuth: true,
        ),
      );

      return isAuthenticated;
    } catch (e) {
      return false;
    }
  }

  static Future<void> storeWithBiometric(String key, String value) async {
    final authenticated = await _authenticateUser();
    if (!authenticated) throw Exception('Authentication failed');

    await SecureStorageService.storeData(key, value);
  }

  static Future<String?> getWithBiometric(String key) async {
    final authenticated = await _authenticateUser();
    if (!authenticated) return null;

    return await SecureStorageService.getData(key);
  }
}

3. Data Encryption Layer

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart';

class EncryptionService {
  static final _key = Key.fromSecureRandom(32);
  static final _iv = IV.fromSecureRandom(16);
  static final _encrypter = Encrypter(AES(_key));

  static String encryptData(String data) {
    final encrypted = _encrypter.encrypt(data, iv: _iv);
    return encrypted.base64;
  }

  static String decryptData(String encryptedData) {
    final encrypted = Encrypted.fromBase64(encryptedData);
    return _encrypter.decrypt(encrypted, iv: _iv);
  }

  // Hash sensitive data
  static String hashData(String data) {
    final bytes = utf8.encode(data);
    final digest = sha256.convert(bytes);
    return digest.toString();
  }
}

class SecureDataManager {
  static Future<void> storeEncrypted(String key, String data) async {
    final encrypted = EncryptionService.encryptData(data);
    await SecureStorageService.storeData(key, encrypted);
  }

  static Future<String?> getDecrypted(String key) async {
    final encrypted = await SecureStorageService.getData(key);
    if (encrypted == null) return null;

    try {
      return EncryptionService.decryptData(encrypted);
    } catch (e) {
      // Handle decryption error
      return null;
    }
  }
}

Best Practices

1. Key Management

class KeyManager {
  // Jangan hardcode keys dalam kode
  static const _keyAlias = 'app_master_key';

  static Future<String> getMasterKey() async {
    String? key = await SecureStorageService.getData(_keyAlias);

    if (key == null) {
      // Generate new key
      key = _generateSecureKey();
      await SecureStorageService.storeData(_keyAlias, key);
    }

    return key;
  }

  static String _generateSecureKey() {
    // Implementation untuk generate secure key
    return base64.encode(List<int>.generate(32, (i) => Random().nextInt(256)));
  }
}

2. Error Handling dan Logging

class SecureStorageManager {
  static Future<Result<String>> safeGet(String key) async {
    try {
      final value = await SecureStorageService.getData(key);
      return Result.success(value);
    } catch (e) {
      // Log error tanpa expose sensitive data
      logger.error('Failed to retrieve secure data for key: ${key.hashCode}');
      return Result.failure('Failed to retrieve data');
    }
  }

  static Future<Result<void>> safeStore(String key, String value) async {
    try {
      await SecureStorageService.storeData(key, value);
      return Result.success(null);
    } catch (e) {
      logger.error('Failed to store secure data for key: ${key.hashCode}');
      return Result.failure('Failed to store data');
    }
  }
}

class Result<T> {
  final T? data;
  final String? error;
  final bool isSuccess;

  Result.success(this.data) : error = null, isSuccess = true;
  Result.failure(this.error) : data = null, isSuccess = false;
}

3. Data Migration dan Versioning

class SecureStorageVersion {
  static const _versionKey = 'storage_version';
  static const currentVersion = 2;

  static Future<void> migrateIfNeeded() async {
    final version = await _getCurrentVersion();

    if (version < currentVersion) {
      await _performMigration(version);
      await _setVersion(currentVersion);
    }
  }

  static Future<int> _getCurrentVersion() async {
    final versionStr = await SecureStorageService.getData(_versionKey);
    return int.tryParse(versionStr ?? '1') ?? 1;
  }

  static Future<void> _performMigration(int fromVersion) async {
    switch (fromVersion) {
      case 1:
        await _migrateFromV1ToV2();
        break;
    }
  }

  static Future<void> _migrateFromV1ToV2() async {
    // Implementasi migrasi data
  }

  static Future<void> _setVersion(int version) async {
    await SecureStorageService.storeData(_versionKey, version.toString());
  }
}

Testing Secure Storage

// test/secure_storage_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

class MockSecureStorage extends Mock implements FlutterSecureStorage {}

void main() {
  group('SecureStorageService Tests', () {
    late MockSecureStorage mockStorage;

    setUp(() {
      mockStorage = MockSecureStorage();
    });

    testWidgets('should store and retrieve data correctly', (tester) async {
      const key = 'test_key';
      const value = 'test_value';

      when(mockStorage.write(key: key, value: value))
          .thenAnswer((_) async => {});
      when(mockStorage.read(key: key))
          .thenAnswer((_) async => value);

      // Test implementation
      verify(mockStorage.write(key: key, value: value)).called(1);
      verify(mockStorage.read(key: key)).called(1);
    });
  });
}

Platform-Specific Considerations

Android

  • Data tersimpan di Android Keystore
  • Mendukung hardware-backed security
  • Perlu permission khusus untuk biometric

iOS

  • Data tersimpan di iOS Keychain
  • Automatic backup exclusion
  • Touch ID/Face ID integration

Monitoring dan Analytics

class SecureStorageAnalytics {
  static void trackStorageEvent(String event, Map<String, dynamic> params) {
    // Jangan log sensitive data
    final sanitizedParams = Map<String, dynamic>.from(params);
    sanitizedParams.removeWhere((key, value) => 
        key.toLowerCase().contains('token') ||
        key.toLowerCase().contains('password'));

    // Send to analytics service
  }
}

Kesimpulan

Implementasi secure storage yang baik memerlukan:

  1. Layer Security Berlapis: Encryption, secure storage, dan biometric
  2. Proper Key Management: Jangan hardcode, gunakan secure key generation
  3. Error Handling: Graceful handling tanpa expose sensitive data
  4. Testing: Unit test dan integration test
  5. Migration Strategy: Untuk update aplikasi yang seamless
  6. Monitoring: Track usage tanpa compromise security

Secure storage bukan hanya tentang menggunakan library, tetapi membangun arsitektur yang comprehensive untuk melindungi data pengguna.

Komentar (0)

Tinggalkan Komentar