feat: Implement note encryption and synchronization features
- Added NoteEncryption class for encrypting and decrypting note content using AES-GCM. - Updated NoteRepository to handle synchronization of notes and categories with the server, including encryption of note data before sending. - Introduced SyncRequest and SyncResponse models for managing synchronization data. - Enhanced LocalVaultService to store and retrieve the encryption key. - Modified HomeScreen and SettingsScreen to trigger synchronization after note operations and manage API endpoint settings. - Added SyncStatusIndicator to provide visual feedback on synchronization status in the app title bar. - Created Category model to manage note categories with encryption support. - Updated note model to include UUID, server version, deletion status, and category ID. - Added necessary UI elements for displaying and managing the encryption key in SettingsScreen. - Updated dependencies in pubspec.yaml for cryptography and HTTP handling.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
|
||||
/// Encriptación de contenido de notas usando AES-GCM
|
||||
/// Usa una clave derivada del master key del usuario
|
||||
class NoteEncryption {
|
||||
static final AesGcm _aes = AesGcm.with256bits();
|
||||
static const int _passwordHashVersion = 1;
|
||||
static const int _kdfIterations = 100000;
|
||||
static final Pbkdf2 _kdf = Pbkdf2(
|
||||
macAlgorithm: Hmac.sha256(),
|
||||
iterations: _kdfIterations,
|
||||
bits: 256,
|
||||
);
|
||||
|
||||
/// Encripta el contenido de una nota usando el master key
|
||||
/// Retorna el contenido encriptado en formato JSON base64
|
||||
static Future<String> encryptNote(
|
||||
String plaintext,
|
||||
String masterKey,
|
||||
) async {
|
||||
final List<int> salt = _randomBytes(16);
|
||||
final SecretKey secretKey = await _kdf.deriveKey(
|
||||
secretKey: SecretKey(utf8.encode(masterKey)),
|
||||
nonce: salt,
|
||||
);
|
||||
|
||||
final List<int> nonce = _randomBytes(12);
|
||||
final SecretBox box = await _aes.encrypt(
|
||||
utf8.encode(plaintext),
|
||||
secretKey: secretKey,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
return jsonEncode(<String, dynamic>{
|
||||
'v': _passwordHashVersion,
|
||||
'salt': base64Encode(salt),
|
||||
'nonce': base64Encode(box.nonce),
|
||||
'cipherText': base64Encode(box.cipherText),
|
||||
'mac': base64Encode(box.mac.bytes),
|
||||
});
|
||||
}
|
||||
|
||||
/// Desencripta el contenido de una nota usando el master key
|
||||
static Future<String> decryptNote(
|
||||
String encodedBox,
|
||||
String masterKey,
|
||||
) async {
|
||||
try {
|
||||
final Map<String, dynamic> payload =
|
||||
jsonDecode(encodedBox) as Map<String, dynamic>;
|
||||
final List<int> salt = base64Decode(payload['salt'] as String);
|
||||
final List<int> nonce = base64Decode(payload['nonce'] as String);
|
||||
final List<int> cipherText =
|
||||
base64Decode(payload['cipherText'] as String);
|
||||
final List<int> macBytes = base64Decode(payload['mac'] as String);
|
||||
|
||||
final SecretKey secretKey = await _kdf.deriveKey(
|
||||
secretKey: SecretKey(utf8.encode(masterKey)),
|
||||
nonce: salt,
|
||||
);
|
||||
|
||||
final SecretBox box = SecretBox(
|
||||
cipherText,
|
||||
nonce: nonce,
|
||||
mac: Mac(macBytes),
|
||||
);
|
||||
|
||||
final List<int> clearText = await _aes.decrypt(
|
||||
box,
|
||||
secretKey: secretKey,
|
||||
);
|
||||
|
||||
return utf8.decode(clearText);
|
||||
} catch (e) {
|
||||
throw Exception('Failed to decrypt note: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static List<int> _randomBytes(int length) {
|
||||
final Random random = Random.secure();
|
||||
return List<int>.generate(length, (_) => random.nextInt(256));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user