MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter数据存储的安全性:保护用户隐私信息

2021-08-064.9k 阅读

Flutter 数据存储概述

在 Flutter 应用开发中,数据存储是一项关键功能。应用常常需要存储各种类型的数据,比如用户配置、登录信息、本地缓存数据等。Flutter 提供了多种数据存储方式,每种方式在安全性、性能和适用场景上各有不同。

常用数据存储方式

  1. SharedPreferences:这是一种轻量级的键值对存储机制,适用于存储简单的应用配置数据,如用户的偏好设置、是否首次登录等。它基于 Android 的 SharedPreferences 和 iOS 的 NSUserDefaults 实现。在 Android 上,数据以 XML 文件形式存储在设备的内部存储中;在 iOS 上,数据存储在应用的偏好设置数据库里。虽然它使用方便,但安全性相对较低,因为存储的数据没有加密,任何人只要有设备的访问权限就可以读取这些 XML 文件或数据库内容。
  2. 文件存储:Flutter 可以直接在设备的文件系统中读写文件,这适用于存储较大的数据,比如图片、音频、视频或者自定义格式的文档。在 Android 上,可以使用外部存储(如 SD 卡,需申请相应权限)或内部存储;在 iOS 上,应用只能在自己的沙盒目录内进行文件操作。文件存储的安全性取决于文件系统的权限设置,但如果没有额外的加密处理,数据在设备上仍然是明文存在的,容易被窃取。
  3. SQLite 数据库:SQLite 是一个轻量级的嵌入式数据库,在 Flutter 中可以通过 sqflite 插件来使用。它适用于存储结构化数据,如用户的联系人列表、待办事项等。SQLite 数据库文件同样存储在设备的文件系统中,如果没有加密,数据库文件可以被直接访问和读取,存在数据泄露风险。
  4. Hive:Hive 是 Flutter 社区开发的轻量级、高性能的键值对和对象存储解决方案。它支持将自定义对象序列化并存储,而且提供了一些基本的加密功能。Hive 将数据存储在二进制文件中,相比 SharedPreferences 等方式,在安全性上有一定提升,但默认情况下数据仍然没有强加密保护。

数据存储面临的安全威胁

  1. 数据泄露:如果数据以明文形式存储在设备上,无论是通过文件存储、SharedPreferences 还是未加密的数据库,一旦设备丢失或者被恶意攻击者获取访问权限,用户的隐私信息就会直接暴露。例如,用户的登录凭证、聊天记录、个人健康数据等都可能被窃取,这会给用户带来极大的安全风险,如账号被盗用、个人隐私被公开等。
  2. 数据篡改:攻击者可能会尝试修改存储在设备上的数据,以达到某种目的。比如,修改游戏的存档数据来获取不公平的游戏优势,或者篡改应用的配置数据以绕过某些限制。如果没有适当的验证和保护机制,这种篡改行为很难被发现。
  3. 恶意访问:在多用户或多应用环境下,恶意应用可能会尝试访问其他应用存储的数据。虽然现代操作系统提供了一定的隔离机制,但如果存在漏洞或者权限设置不当,恶意应用就有可能获取到敏感数据。

保护用户隐私信息的方法

  1. 数据加密
    • 对称加密:对称加密使用相同的密钥进行加密和解密。在 Flutter 中,可以使用 encrypt 库来实现对称加密。例如,使用 AES(高级加密标准)算法对数据进行加密。
import 'package:encrypt/encrypt.dart';

void main() {
  final key = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(key));
  final plainText = 'user sensitive information';
  final encrypted = encrypter.encrypt(plainText, iv: iv);
  print('Encrypted: ${encrypted.base64}');
  final decrypted = encrypter.decrypt(encrypted, iv: iv);
  print('Decrypted: ${decrypted}');
}

在这个示例中,首先创建了一个长度为 32 字节的密钥(实际应用中密钥应妥善保管,不能像示例中这样硬编码),然后创建了一个长度为 16 字节的初始化向量(IV)。使用 AES 算法对明文进行加密,加密后的数据可以存储在设备上。在需要使用数据时,再使用相同的密钥和 IV 进行解密。 - 非对称加密:非对称加密使用公钥和私钥对数据进行加密和解密。公钥用于加密数据,只有对应的私钥才能解密。在 Flutter 中,可以借助 pointycastle 库来实现非对称加密。例如,使用 RSA 算法。

import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/paddings/pkcs1_1_5.dart';
import 'package:pointycastle/random/fortuna_random.dart';

Future<void> main() async {
  final keyPairGenerator = RSAKeyGenerator();
  keyPairGenerator.init(RSAKeyGeneratorParameters(BigInt.from(65537), 2048));
  final keyPair = keyPairGenerator.generateKeyPair();
  final publicKey = keyPair.publicKey as RSAPublicKey;
  final privateKey = keyPair.privateKey as RSAPrivateKey;

  final plainText = 'user important data';
  final encoder = PaddedBlockCipherEncoder(PKCS115Padding(), RSAEngine());
  encoder.init(true, publicKey);
  final encrypted = encoder.process(plainText.codeUnits);
  print('Encrypted: ${encrypted.map((e) => e.toRadixString(16)).join()}');

  final decoder = PaddedBlockCipherDecoder(PKCS115Padding(), RSAEngine());
  decoder.init(false, privateKey);
  final decrypted = decoder.process(encrypted);
  print('Decrypted: ${String.fromCharCodeList(decrypted)}');
}

此示例中,首先生成了 RSA 密钥对,包括公钥和私钥。使用公钥对明文进行加密,加密后的数据可以安全传输或存储。只有持有私钥的一方才能对数据进行解密。非对称加密通常用于密钥交换、数字签名等场景,在数据存储方面,结合对称加密可以进一步提高安全性,比如使用非对称加密来加密对称加密的密钥。 2. 安全的密钥管理 - 密钥生成:密钥应该是随机生成的,并且具有足够的长度和复杂度。在 Flutter 中,可以使用 crypto 库的 Random 类来生成随机数作为密钥的一部分。例如:

import 'dart:math';
import 'package:crypto/crypto.dart';

Uint8List generateKey(int length) {
  final random = Random.secure();
  final key = Uint8List(length);
  random.nextBytes(key);
  return key;
}
- **密钥存储**:密钥不应该硬编码在代码中,而应该存储在安全的地方。在 Android 上,可以使用 Keystore 来存储密钥。Keystore 提供了硬件级别的密钥保护,密钥在设备的可信执行环境(TEE)中生成和使用,无法直接从设备存储中读取。在 iOS 上,可以使用 Keychain 来实现类似功能。
// Android 示例,使用 flutter_keychain 插件
import 'package:flutter_keychain/flutter_keychain.dart';

Future<void> saveKeyToKeystore(String keyAlias, Uint8List key) async {
  await FlutterKeychain.set(keyAlias, key, service: 'com.example.app');
}

Future<Uint8List?> getKeyFromKeystore(String keyAlias) async {
  final value = await FlutterKeychain.get(keyAlias, service: 'com.example.app');
  if (value != null) {
    return Uint8List.fromList(value.codeUnits);
  }
  return null;
}
  1. 权限管理
    • 最小权限原则:应用应该只申请必要的权限,并且在运行时动态申请权限,而不是在安装时一次性申请所有权限。例如,如果应用只需要读取设备的照片,就不应该申请写入外部存储的权限。在 Flutter 中,可以使用 permission_handler 插件来处理权限申请。
import 'package:permission_handler/permission_handler.dart';

Future<void> requestPhotoPermission() async {
  final status = await Permission.photos.request();
  if (status.isGranted) {
    // 权限已授予,可以进行照片读取操作
  } else if (status.isDenied) {
    // 用户拒绝了权限请求
  } else if (status.isPermanentlyDenied) {
    // 用户永久拒绝了权限请求,引导用户到设置页面开启权限
    openAppSettings();
  }
}
- **权限检查**:在进行数据存储操作时,应该检查应用是否具有相应的权限。例如,在写入外部存储文件之前,检查是否有写入权限。
import 'package:permission_handler/permission_handler.dart';

Future<void> writeToExternalStorage(String data) async {
  final status = await Permission.storage.request();
  if (status.isGranted) {
    // 权限已授予,进行文件写入操作
    // 这里省略实际的文件写入代码
  } else {
    // 权限不足,提示用户
  }
}
  1. 数据验证和完整性保护
    • 数字签名:可以使用数字签名来验证数据的完整性和来源。例如,在存储数据之前,使用私钥对数据进行签名,在读取数据时,使用公钥验证签名。在 Flutter 中,可以结合 pointycastle 库实现数字签名。
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/paddings/pkcs1_1_5.dart';

Future<void> main() async {
  final keyPairGenerator = RSAKeyGenerator();
  keyPairGenerator.init(RSAKeyGeneratorParameters(BigInt.from(65537), 2048));
  final keyPair = keyPairGenerator.generateKeyPair();
  final privateKey = keyPair.privateKey as RSAPrivateKey;
  final publicKey = keyPair.publicKey as RSAPublicKey;

  final plainText = 'user data to be signed';
  final digest = Sha256Digest();
  final digestResult = Digest(digest.process(plainText.codeUnits));
  final signer = Pkcs115Signer(RSAEngine());
  signer.init(true, privateKey);
  final signature = signer.generateSignature(digestResult);

  final verifier = Pkcs115Signer(RSAEngine());
  verifier.init(false, publicKey);
  final isVerified = verifier.verifySignature(digestResult, signature);
  print('Is verified: $isVerified');
}
- **哈希校验**:计算数据的哈希值并存储,在读取数据时重新计算哈希值并与存储的哈希值进行比较。如果哈希值相同,则说明数据没有被篡改。在 Flutter 中,可以使用 `crypto` 库来计算哈希值。
import 'package:crypto/crypto.dart';

String calculateHash(String data) {
  final bytes = utf8.encode(data);
  final digest = sha256.convert(bytes);
  return digest.toString();
}
  1. 安全的数据存储位置选择
    • 使用受保护的存储区域:尽量使用操作系统提供的受保护存储区域。如前面提到的,在 Android 上,Keystore 用于存储密钥,应用的内部存储相对外部存储更安全;在 iOS 上,Keychain 用于安全存储敏感信息,应用的沙盒目录限制了其他应用的访问。
    • 避免公共存储区域:除非必要,不要将敏感数据存储在公共可访问的存储区域,如 Android 的外部存储根目录或者共享文件夹。如果必须使用外部存储,要对存储的数据进行加密,并设置合适的文件权限。

针对不同存储方式的安全强化

  1. SharedPreferences
    • 加密存储:由于 SharedPreferences 本身不支持加密,在存储敏感数据之前,先对数据进行加密。可以使用前面提到的对称加密或非对称加密方法。例如,使用对称加密:
import 'package:encrypt/encrypt.dart';
import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveEncryptedToSharedPreferences(String key, String value) async {
  final prefs = await SharedPreferences.getInstance();
  final keyForEncryption = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(keyForEncryption));
  final encrypted = encrypter.encrypt(value, iv: iv);
  await prefs.setString(key, encrypted.base64);
}

Future<String?> getDecryptedFromSharedPreferences(String key) async {
  final prefs = await SharedPreferences.getInstance();
  final storedValue = prefs.getString(key);
  if (storedValue != null) {
    final keyForEncryption = Key.fromUtf8('my 32 length key................');
    final iv = IV.fromLength(16);
    final encrypter = Encrypter(AES(keyForEncryption));
    final encrypted = Encrypted.fromBase64(storedValue);
    return encrypter.decrypt(encrypted, iv: iv);
  }
  return null;
}
- **限制访问**:虽然 SharedPreferences 数据默认只能被应用本身访问,但仍然要谨慎处理,避免在应用内随意暴露数据读取接口。只有在必要的地方,并且经过严格的权限检查后,才读取 SharedPreferences 中的数据。

2. 文件存储 - 加密文件内容:在将数据写入文件之前,对数据进行加密。可以使用加密库对文件内容进行整体加密,例如使用 encrypt 库对文件流进行加密。

import 'package:encrypt/encrypt.dart';
import 'dart:io';

Future<void> writeEncryptedFile(String filePath, String content) async {
  final key = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(key));
  final encrypted = encrypter.encrypt(content, iv: iv);
  final file = File(filePath);
  await file.writeAsString(encrypted.base64);
}

Future<String?> readDecryptedFile(String filePath) async {
  final file = File(filePath);
  if (await file.exists()) {
    final content = await file.readAsString();
    final key = Key.fromUtf8('my 32 length key................');
    final iv = IV.fromLength(16);
    final encrypter = Encrypter(AES(key));
    final encrypted = Encrypted.fromBase64(content);
    return encrypter.decrypt(encrypted, iv: iv);
  }
  return null;
}
- **文件权限设置**:在 Android 上,通过 `File` 类的 `setReadable`、`setWritable` 等方法设置文件的访问权限,确保只有应用本身可以读取和写入敏感文件。在 iOS 上,应用沙盒机制已经限制了其他应用对文件的访问,但仍然要注意文件的访问控制,避免在应用内不当暴露文件路径。

3. SQLite 数据库 - 数据库加密:可以使用 sqflite 插件结合加密库来实现数据库加密。例如,对数据库中的每一条记录进行加密存储。

import 'package:encrypt/encrypt.dart';
import 'package:sqflite/sqflite.dart';

Future<void> createEncryptedDatabase() async {
  final database = await openDatabase(
    'encrypted_database.db',
    version: 1,
    onCreate: (db, version) async {
      await db.execute('''
        CREATE TABLE users (
          id INTEGER PRIMARY KEY,
          name TEXT,
          encrypted_email TEXT
        )
      ''');
    },
  );

  final key = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(key));

  final email = 'user@example.com';
  final encryptedEmail = encrypter.encrypt(email, iv: iv);

  await database.insert('users', {
    'name': 'John Doe',
    'encrypted_email': encryptedEmail.base64,
  });
}

Future<String?> getDecryptedEmailFromDatabase() async {
  final database = await openDatabase(
    'encrypted_database.db',
    version: 1,
  );
  final key = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(key));

  final results = await database.query('users');
  if (results.isNotEmpty) {
    final encryptedEmail = results.first['encrypted_email'] as String;
    final decrypted = encrypter.decrypt(Encrypted.fromBase64(encryptedEmail), iv: iv);
    return decrypted;
  }
  return null;
}
- **数据库权限**:确保 SQLite 数据库文件存储在应用的私有目录下,并且设置合适的文件权限,防止其他应用访问数据库文件。在 Android 上,可以通过设置文件的访问权限来限制访问;在 iOS 上,应用沙盒机制保证了数据库文件的安全性,但也要注意在应用内对数据库的访问控制。

4. Hive - 启用加密:Hive 提供了基本的加密功能。在打开 Hive 盒子时,可以设置加密密钥。

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:encrypt/encrypt.dart';

Future<void> main() async {
  await Hive.initFlutter();
  final key = Key.fromUtf8('my 32 length key................');
  final iv = IV.fromLength(16);
  final encrypter = Encrypter(AES(key));

  Hive.registerAdapter(UserAdapter());
  await Hive.openBox<User>('users_box', encryptionCipher: encrypter);

  final user = User('John Doe', 'user@example.com');
  final box = Hive.box<User>('users_box');
  await box.add(user);
}

class User {
  final String name;
  final String email;

  User(this.name, this.email);
}

class UserAdapter extends TypeAdapter<User> {
  @override
  User read(BinaryReader reader) {
    final numOfFields = reader.readByte();
    final fields = <int, dynamic>{
      for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
    };
    return User(
      fields[0] as String,
      fields[1] as String,
    );
  }

  @override
  void write(BinaryWriter writer, User obj) {
    writer
     ..writeByte(2)
     ..writeByte(0)
     ..write(obj.name)
     ..writeByte(1)
     ..write(obj.email);
  }

  @override
  int get typeId => 0;
}
- **数据验证**:结合 Hive 的数据存储功能,在读取和写入数据时进行数据验证,确保数据的完整性和准确性。可以通过在对象的 `read` 和 `write` 方法中添加验证逻辑来实现。

安全审计和漏洞检测

  1. 代码审查:定期对应用的代码进行审查,特别是涉及数据存储的部分。检查是否存在硬编码的密钥、未加密的数据存储、权限滥用等安全问题。在团队开发中,可以采用同行评审的方式,让不同的开发人员互相审查代码,提高发现安全问题的几率。
  2. 静态分析工具:使用静态分析工具,如 flutter analyze 等,来检测代码中的潜在安全漏洞。这些工具可以分析代码结构,发现可能导致安全问题的代码模式,如未处理的异常、不安全的 API 使用等。此外,还可以使用专门的安全扫描工具,如 SonarQube 等,对 Flutter 项目进行全面的安全扫描。
  3. 动态测试:在应用开发过程中,进行动态测试,模拟各种攻击场景,如尝试访问未授权的数据、篡改存储的数据等,检查应用的安全性。可以使用自动化测试框架,如 flutter_test,编写测试用例来验证数据存储的安全性。例如,编写测试用例来验证加密和解密功能是否正常,以及数据完整性保护机制是否有效。

通过以上全面的安全措施,可以有效提高 Flutter 应用数据存储的安全性,保护用户的隐私信息,提升用户对应用的信任度。在实际开发中,需要根据应用的具体需求和安全要求,灵活选择和组合这些安全方法,构建一个安全可靠的数据存储系统。