Fix OutOfMemoryError on large files

This commit is contained in:
Hugo Pointcheval 2021-02-16 21:41:30 +01:00
parent 4d0dd7e5e3
commit 5da95a0c39
2 changed files with 125 additions and 18 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020
// Copyright (c) 2021
// Author: Hugo Pointcheval
import 'dart:typed_data';
@ -52,14 +52,23 @@ abstract class Cipher {
///
/// It's the result of an encryption.
abstract class CipherText {
/// Returns the standard algorithm name used for this ciphertext
/// Returns the standard algorithm name used for this ciphertext.
CipherAlgorithm get algorithm;
/// Returns the data of this ciphertext
Uint8List get bytes;
/// Returns the data of this ciphertext (in chunks).
List<Uint8List> get bytes;
/// Returns the IV of this cipertext
Uint8List get iv;
/// Returns the IV of this cipertext (in chunks).
List<Uint8List> get iv;
/// Returns the chunk number of this cipherText.
int get size;
/// Returns this ciphertext in simple Byte Array format.
Uint8List encode();
/// Transforms a simple Byte Array to a NativeCrypto cipherText.
void decode(Uint8List src);
}
/// Represents a pair of [BlockCipherMode] and [Padding]

View File

@ -1,8 +1,10 @@
// Copyright (c) 2020
// Copyright (c) 2021
// Author: Hugo Pointcheval
import 'dart:typed_data';
import 'package:native_crypto/native_crypto.dart';
import '../cipher.dart';
import '../exceptions.dart';
import '../key.dart';
@ -81,9 +83,28 @@ class AESCipher implements Cipher {
} else if (_sk == null || _sk.isEmpty) {
throw CipherInitException('Invalid key size.');
}
List<Uint8List> c =
await Platform().encrypt(data, _sk.encoded, algorithm, _params);
return AESCipherText(c[0], c[1]);
Uint8List dataToEncrypt;
int maxSize = 33554432;
AESCipherText cipherText = AESCipherText.empty();
// If data is bigger than 32mB -> split in chunks
if (data.length > maxSize) {
int chunkNb = (data.length / maxSize).ceil();
for (var i = 0; i < chunkNb; i++) {
if (i < (chunkNb - 1)) {
dataToEncrypt = data.sublist(i * maxSize, (i + 1) * maxSize);
} else {
dataToEncrypt = data.sublist(i * maxSize);
}
List<Uint8List> c = await Platform()
.encrypt(dataToEncrypt, _sk.encoded, algorithm, _params);
cipherText.append(c[0], c[1]);
}
} else {
List<Uint8List> c =
await Platform().encrypt(data, _sk.encoded, algorithm, _params);
cipherText.append(c[0], c[1]);
}
return cipherText;
}
@override
@ -96,29 +117,106 @@ class AESCipher implements Cipher {
throw CipherInitException('Cipher not properly initialized.');
} else if (_sk == null || _sk.isEmpty) {
throw CipherInitException('Invalid key size.');
} else if (cipherText.bytes.length != cipherText.iv.length) {
throw DecryptionException(
"This cipher text's bytes chunks length is not the same as iv chunks length");
}
List<Uint8List> payload = [cipherText.bytes, cipherText.iv];
Uint8List d =
await Platform().decrypt(payload, _sk.encoded, algorithm, _params);
return d;
BytesBuilder decryptedData = BytesBuilder();
if (cipherText.size > 1) {
for (var i = 0; i < cipherText.size; i++) {
List<Uint8List> payload = [cipherText.bytes[i], cipherText.iv[i]];
Uint8List d =
await Platform().decrypt(payload, _sk.encoded, algorithm, _params);
decryptedData.add(d);
}
} else {
List<Uint8List> payload = [cipherText.bytes[0], cipherText.iv[0]];
Uint8List d =
await Platform().decrypt(payload, _sk.encoded, algorithm, _params);
decryptedData.add(d);
}
return decryptedData.toBytes();
}
}
class AESCipherText implements CipherText {
Uint8List _bytes;
Uint8List _iv;
List<Uint8List> _bytes;
List<Uint8List> _iv;
@override
CipherAlgorithm get algorithm => CipherAlgorithm.AES;
@override
Uint8List get bytes => _bytes;
List<Uint8List> get bytes => _bytes;
@override
Uint8List get iv => _iv;
List<Uint8List> get iv => _iv;
@override
int get size => _bytes.length;
AESCipherText(Uint8List bytes, Uint8List iv) {
_bytes = List.from([bytes]);
_iv = List.from([iv]);
}
AESCipherText.from(List<Uint8List> bytes, List<Uint8List> iv) {
_bytes = bytes;
_iv = iv;
}
AESCipherText.empty() {
_bytes = <Uint8List>[];
_iv = <Uint8List>[];
}
void append(Uint8List bytes, Uint8List iv) {
_bytes.add(bytes);
_iv.add(iv);
}
/// Returns this ciphertext in [Uint8List] format.
///
/// Encoding
/// --------
/// Uint8List encoding is : IV_1 + M_1 + IV_2 + M_2 + ... + IV_n + M_n
///
/// Where **IV_k** is the IV of the cipher text **M_k**
///
/// IV is **always** 16 bytes long, And the **M** are all max
/// size (of 33 554 480 bytes) except the last one which is shorter than the others.
Uint8List encode() {
BytesBuilder builder = BytesBuilder();
for (var i = 0; i < size; i++) {
builder.add(_iv[i]);
builder.add(_bytes[i]);
}
return builder.toBytes();
}
/// Transforms a [Uint8List] to a *NativeCrypto* cipherText.
///
/// Decoding
/// --------
/// See the list as a chain of chunks (IV and Messages)
/// `[IV][MESSAGE][IV][MESSAGE] ... [IV][MESSA...]`
///
/// Chunk length is IV length + Message length = 16 + 33 554 480 bytes
void decode(Uint8List src) {
ByteBuffer buffer = src.buffer;
int chunkSize = 16 + 33554480;
int chunkNb = (buffer.lengthInBytes / chunkSize).ceil();
for (var i = 0; i < chunkNb; i++) {
_iv.add(buffer.asUint8List(i * chunkSize, 16));
if (i < (chunkNb - 1)) {
_bytes.add(buffer.asUint8List(16 + i * chunkSize, 33554480));
} else {
_bytes.add(buffer.asUint8List(16 + i * chunkSize));
}
}
}
}