refactor: replace pointycastle with cryptography for AES-GCM
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
- [ ] Translations
|
||||
- [ ] Drop file
|
||||
- [ ] Keep file name
|
||||
- [ ] Use streams
|
||||
- [ ] Better UI/UX
|
||||
- [ ] Remove snackbars
|
||||
- [x] Use https://pub.dev/packages/webcrypto or https://pub.dev/packages/cryptography for efficient web crypto operations
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pointycastle/export.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
|
||||
/// Decrypts data using AES-256-GCM
|
||||
/// [encryptedBytes] - the encrypted data
|
||||
/// [encryptedBytes] - the encrypted data (ciphertext + MAC)
|
||||
/// [key] - base64 encoded encryption key
|
||||
/// [nonce] - base64 encoded nonce (salt)
|
||||
Future<Uint8List> decryptBlob({
|
||||
@@ -12,20 +12,37 @@ Future<Uint8List> decryptBlob({
|
||||
required String key,
|
||||
required String nonce,
|
||||
}) async {
|
||||
final algorithm = AesGcm.with256bits();
|
||||
|
||||
// Decode base64 key and nonce
|
||||
final keyBytes = base64Decode(key);
|
||||
final nonceBytes = base64Decode(nonce);
|
||||
|
||||
// Set up AES-GCM parameters
|
||||
final keyParam = KeyParameter(keyBytes);
|
||||
final params = ParametersWithIV(keyParam, nonceBytes);
|
||||
// Extract ciphertext and MAC (MAC is last 16 bytes)
|
||||
final macLength = algorithm.macAlgorithm.macLength;
|
||||
final ciphertext = encryptedBytes.sublist(
|
||||
0,
|
||||
encryptedBytes.length - macLength,
|
||||
);
|
||||
final macBytes = encryptedBytes.sublist(
|
||||
encryptedBytes.length - macLength,
|
||||
);
|
||||
|
||||
// Create and initialize the cipher for decryption (false = decrypt)
|
||||
final aesGcm = GCMBlockCipher(AESEngine());
|
||||
aesGcm.init(false, params);
|
||||
// Create secret key from bytes
|
||||
final secretKey = SecretKey(keyBytes);
|
||||
|
||||
// Create secret box with ciphertext, nonce, and MAC
|
||||
final secretBox = SecretBox(
|
||||
ciphertext,
|
||||
nonce: nonceBytes,
|
||||
mac: Mac(macBytes),
|
||||
);
|
||||
|
||||
// Decrypt the data
|
||||
final decryptedData = aesGcm.process(encryptedBytes);
|
||||
final decryptedData = await algorithm.decrypt(
|
||||
secretBox,
|
||||
secretKey: secretKey,
|
||||
);
|
||||
|
||||
return decryptedData;
|
||||
return Uint8List.fromList(decryptedData);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:file_transfer/models/encrypted_blob.dart';
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
/// Encrypts data using AES-256-GCM
|
||||
/// Returns the encrypted blob with key, nonce, and auth tag
|
||||
Future<EncryptedBlob> encryptBlob(Uint8List bytes) async {
|
||||
// Generate random 256-bit key (32 bytes)
|
||||
final key = _generateRandomBytes(32);
|
||||
final algorithm = AesGcm.with256bits();
|
||||
|
||||
// Generate random 12-byte nonce (96 bits - recommended for GCM)
|
||||
final nonce = _generateRandomBytes(12);
|
||||
// Generate random secret key
|
||||
final secretKey = await algorithm.newSecretKey();
|
||||
|
||||
// Set up AES-GCM parameters
|
||||
final keyParam = KeyParameter(key);
|
||||
final params = ParametersWithIV(keyParam, nonce);
|
||||
// Encrypt with the key
|
||||
final secretBox = await algorithm.encrypt(bytes, secretKey: secretKey);
|
||||
|
||||
// Create and initialize the cipher for encryption (true = encrypt)
|
||||
final aesGcm = GCMBlockCipher(AESEngine());
|
||||
aesGcm.init(true, params);
|
||||
|
||||
// Encrypt the data
|
||||
final encryptedData = aesGcm.process(bytes);
|
||||
// Concatenate ciphertext + MAC for storage
|
||||
final macBytes = secretBox.mac.bytes;
|
||||
final encryptedData = Uint8List.fromList(
|
||||
[...secretBox.cipherText, ...macBytes],
|
||||
);
|
||||
|
||||
return EncryptedBlob(
|
||||
bytes: encryptedData,
|
||||
key: base64Encode(key),
|
||||
nonce: base64Encode(nonce),
|
||||
key: base64Encode(await secretKey.extractBytes()),
|
||||
nonce: base64Encode(secretBox.nonce),
|
||||
);
|
||||
}
|
||||
|
||||
Uint8List _generateRandomBytes(int length) {
|
||||
final secureRandom = SecureRandom('Fortuna');
|
||||
// Seed with platform entropy
|
||||
final seed = Uint8List(32);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
seed[i] = (DateTime.now().microsecondsSinceEpoch >> (i % 32)) & 0xFF;
|
||||
}
|
||||
secureRandom.seed(KeyParameter(seed));
|
||||
|
||||
return secureRandom.nextBytes(length);
|
||||
}
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ packages:
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0"
|
||||
@@ -558,7 +558,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5"
|
||||
|
||||
+1
-1
@@ -8,6 +8,7 @@ environment:
|
||||
|
||||
dependencies:
|
||||
crypto: ^3.0.7
|
||||
cryptography: ^2.9.0
|
||||
file_picker: ^10.3.10
|
||||
file_saver: ^0.3.1
|
||||
flutter:
|
||||
@@ -17,7 +18,6 @@ dependencies:
|
||||
ndk: ^0.7.1-dev.19
|
||||
ndk_flutter: ^0.0.2-dev.13
|
||||
nip49: ^1.1.2
|
||||
pointycastle: ^4.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user