Files
notas/lib/data/note_encryption.dart
T
Marcos efe602a5da 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.
2026-05-18 16:11:19 +02:00

87 lines
2.5 KiB
Dart

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));
}
}