From 5da95a0c3976620ae17788ffd45fa255cb3081d5 Mon Sep 17 00:00:00 2001 From: Pointcheval Hugo Date: Tue, 16 Feb 2021 21:41:30 +0100 Subject: [PATCH] Fix OutOfMemoryError on large files --- lib/src/cipher.dart | 21 +++++--- lib/src/sym/AES.dart | 122 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 125 insertions(+), 18 deletions(-) diff --git a/lib/src/cipher.dart b/lib/src/cipher.dart index c895475..17e3f5a 100644 --- a/lib/src/cipher.dart +++ b/lib/src/cipher.dart @@ -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 get bytes; - /// Returns the IV of this cipertext - Uint8List get iv; + /// Returns the IV of this cipertext (in chunks). + List 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] diff --git a/lib/src/sym/AES.dart b/lib/src/sym/AES.dart index b47f68e..d9b7800 100644 --- a/lib/src/sym/AES.dart +++ b/lib/src/sym/AES.dart @@ -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 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 c = await Platform() + .encrypt(dataToEncrypt, _sk.encoded, algorithm, _params); + cipherText.append(c[0], c[1]); + } + } else { + List 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 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 payload = [cipherText.bytes[i], cipherText.iv[i]]; + Uint8List d = + await Platform().decrypt(payload, _sk.encoded, algorithm, _params); + decryptedData.add(d); + } + } else { + List 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 _bytes; + List _iv; @override CipherAlgorithm get algorithm => CipherAlgorithm.AES; @override - Uint8List get bytes => _bytes; + List get bytes => _bytes; @override - Uint8List get iv => _iv; + List 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 bytes, List iv) { _bytes = bytes; _iv = iv; } + + AESCipherText.empty() { + _bytes = []; + _iv = []; + } + + 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)); + } + } + } }