1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-03 08:35:29 +02:00

All: Optimised encryption and decryption of items so that it doesn't freeze the UI, especially on mobile

This commit is contained in:
Laurent Cozic 2018-06-25 18:14:57 +01:00
parent 84adf64271
commit fea83e28c4
5 changed files with 66 additions and 15 deletions

View File

@ -73,7 +73,12 @@ class Resource extends BaseItem {
}
try {
await this.encryptionService().decryptFile(encryptedPath, plainTextPath);
// const stat = await this.fsDriver().stat(encryptedPath);
await this.encryptionService().decryptFile(encryptedPath, plainTextPath, {
// onProgress: (progress) => {
// console.info('Decryption: ', progress.doneSize / stat.size);
// },
});
} catch (error) {
if (error.code === 'invalidIdentifier') {
// As the identifier is invalid it most likely means that this is not encrypted data
@ -108,7 +113,12 @@ class Resource extends BaseItem {
if (resource.encryption_blob_encrypted) return { path: encryptedPath, resource: resource };
try {
await this.encryptionService().encryptFile(plainTextPath, encryptedPath);
// const stat = await this.fsDriver().stat(plainTextPath);
await this.encryptionService().encryptFile(plainTextPath, encryptedPath, {
// onProgress: (progress) => {
// console.info(progress.doneSize / stat.size);
// },
});
} catch (error) {
if (error.code === 'ENOENT') throw new JoplinError('File not found:' + error.toString(), 'fileNotFound');
throw error;

View File

@ -14,9 +14,20 @@ function hexPad(s, length) {
class EncryptionService {
constructor() {
// Note: 1 MB is very slow with Node and probably even worse on mobile. 50 KB seems to work well
// and doesn't produce too much overhead in terms of headers.
this.chunkSize_ = 50000;
// Note: 1 MB is very slow with Node and probably even worse on mobile.
//
// On mobile the time it takes to decrypt increases exponentially for some reason, so it's important
// to have a relatively small size so as not to freeze the app. For example, on Android 7.1 simulator
// with 4.1 GB RAM, it takes this much to decrypt a block;
//
// 50KB => 1000 ms
// 25KB => 250ms
// 10KB => 200ms
// 5KB => 10ms
//
// So making the block 10 times smaller make it 100 times faster! So for now using 5KB. This can be
// changed easily since the chunk size is incorporated into the encrypted data.
this.chunkSize_ = 5000;
this.loadedMasterKeys_ = {};
this.activeMasterKeyId_ = null;
this.defaultEncryptionMethod_ = EncryptionService.METHOD_SJCL;
@ -299,7 +310,9 @@ class EncryptionService {
throw new Error('Unknown decryption method: ' + method);
}
async encryptAbstract_(source, destination) {
async encryptAbstract_(source, destination, options = null) {
if (!options) options = {};
const method = this.defaultEncryptionMethod();
const masterKeyId = this.activeMasterKeyId();
const masterKeyPlainText = this.loadedMasterKey(masterKeyId);
@ -311,17 +324,29 @@ class EncryptionService {
await destination.append(this.encodeHeader_(header));
let doneSize = 0;
while (true) {
const block = await source.read(this.chunkSize_);
if (!block) break;
doneSize += this.chunkSize_;
if (options.onProgress) options.onProgress({ doneSize: doneSize });
// Wait for a frame so that the app remains responsive in mobile.
// https://corbt.com/posts/2015/12/22/breaking-up-heavy-processing-in-react-native.html
await shim.waitForFrame();
const encrypted = await this.encrypt(method, masterKeyPlainText, block);
await destination.append(padLeft(encrypted.length.toString(16), 6, '0'));
await destination.append(encrypted);
}
}
async decryptAbstract_(source, destination) {
async decryptAbstract_(source, destination, options = null) {
if (!options) options = {};
const identifier = await source.read(5);
if (!this.isValidHeaderIdentifier(identifier)) throw new JoplinError('Invalid encryption identifier. Data is not actually encrypted? ID was: ' + identifier, 'invalidIdentifier');
const mdSizeHex = await source.read(6);
@ -331,6 +356,8 @@ class EncryptionService {
const header = this.decodeHeader_(identifier + mdSizeHex + md);
const masterKeyPlainText = this.loadedMasterKey(header.masterKeyId);
let doneSize = 0;
while (true) {
const lengthHex = await source.read(6);
if (!lengthHex) break;
@ -338,6 +365,11 @@ class EncryptionService {
const length = parseInt(lengthHex, 16);
if (!length) continue; // Weird but could be not completely invalid (block of size 0) so continue decrypting
doneSize += length;
if (options.onProgress) options.onProgress({ doneSize: doneSize });
await shim.waitForFrame();
const block = await source.read(length);
const plainText = await this.decrypt(header.encryptionMethod, masterKeyPlainText, block);
@ -395,21 +427,21 @@ class EncryptionService {
};
}
async encryptString(plainText) {
async encryptString(plainText, options = null) {
const source = this.stringReader_(plainText);
const destination = this.stringWriter_();
await this.encryptAbstract_(source, destination);
await this.encryptAbstract_(source, destination, options);
return destination.result();
}
async decryptString(cipherText) {
async decryptString(cipherText, options = null) {
const source = this.stringReader_(cipherText);
const destination = this.stringWriter_();
await this.decryptAbstract_(source, destination);
await this.decryptAbstract_(source, destination, options);
return destination.data.join('');
}
async encryptFile(srcPath, destPath) {
async encryptFile(srcPath, destPath, options = null) {
let source = await this.fileReader_(srcPath, 'base64');
let destination = await this.fileWriter_(destPath, 'ascii');
@ -422,7 +454,7 @@ class EncryptionService {
try {
await this.fsDriver().unlink(destPath);
await this.encryptAbstract_(source, destination);
await this.encryptAbstract_(source, destination, options);
} catch (error) {
cleanUp();
await this.fsDriver().unlink(destPath);
@ -432,7 +464,7 @@ class EncryptionService {
await cleanUp();
}
async decryptFile(srcPath, destPath) {
async decryptFile(srcPath, destPath, options = null) {
let source = await this.fileReader_(srcPath, 'ascii');
let destination = await this.fileWriter_(destPath, 'base64');
@ -445,7 +477,7 @@ class EncryptionService {
try {
await this.fsDriver().unlink(destPath);
await this.decryptAbstract_(source, destination);
await this.decryptAbstract_(source, destination, options);
} catch (error) {
cleanUp();
await this.fsDriver().unlink(destPath);

View File

@ -300,6 +300,8 @@ function shimInit() {
bridge().openExternal(url)
}
shim.waitForFrame = () => {}
}
module.exports = { shimInit };

View File

@ -123,6 +123,12 @@ function shimInit() {
shim.openUrl = (url) => {
Linking.openURL(url);
}
shim.waitForFrame = () => {
return new Promise(function(resolve, reject) {
requestAnimationFrame(function() { resolve(); });
});
}
}
module.exports = { shimInit };

View File

@ -139,5 +139,6 @@ shim.attachFileToNote = async (note, filePath) => {}
shim.imageFromDataUrl = async function(imageDataUrl, filePath, options = null) { throw new Error('Not implemented') }
shim.Buffer = null;
shim.openUrl = () => { throw new Error('Not implemented'); }
shim.waitForFrame = () => { throw new Error('Not implemented'); }
module.exports = { shim };