1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

All: Resolves #7686: Upgrade E2EE encryption method to AES-256

This commit is contained in:
Laurent Cozic 2023-06-10 19:19:27 +01:00
parent 95d83a8ced
commit 2dc02fe0f2

View File

@ -38,6 +38,7 @@ export enum EncryptionMethod {
SJCL4 = 4, SJCL4 = 4,
SJCL1a = 5, SJCL1a = 5,
Custom = 6, Custom = 6,
SJCL1b = 7,
} }
export interface EncryptOptions { export interface EncryptOptions {
@ -68,7 +69,7 @@ export default class EncryptionService {
// changed easily since the chunk size is incorporated into the encrypted data. // changed easily since the chunk size is incorporated into the encrypted data.
private chunkSize_ = 5000; private chunkSize_ = 5000;
private decryptedMasterKeys_: Record<string, DecryptedMasterKey> = {}; private decryptedMasterKeys_: Record<string, DecryptedMasterKey> = {};
public defaultEncryptionMethod_ = EncryptionMethod.SJCL1a; // public because used in tests public defaultEncryptionMethod_ = EncryptionMethod.SJCL1b; // public because used in tests
private defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4; private defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4;
private headerTemplates_ = { private headerTemplates_ = {
@ -79,34 +80,6 @@ export default class EncryptionService {
}, },
}; };
public constructor() {
// 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.decryptedMasterKeys_ = {};
this.defaultEncryptionMethod_ = EncryptionMethod.SJCL1a;
this.defaultMasterKeyEncryptionMethod_ = EncryptionMethod.SJCL4;
this.headerTemplates_ = {
// Template version 1
1: {
// Fields are defined as [name, valueSize, valueType]
fields: [['encryptionMethod', 2, 'int'], ['masterKeyId', 32, 'hex']],
},
};
}
public static instance() { public static instance() {
if (this.instance_) return this.instance_; if (this.instance_) return this.instance_;
this.instance_ = new EncryptionService(); this.instance_ = new EncryptionService();
@ -296,96 +269,128 @@ export default class EncryptionService {
const sjcl = shim.sjclModule; const sjcl = shim.sjclModule;
// 2020-01-23: Deprecated and no longer secure due to the use og OCB2 mode - do not use. const handlers: Record<EncryptionMethod, ()=> string> = {
if (method === EncryptionMethod.SJCL) { // 2020-01-23: Deprecated and no longer secure due to the use og OCB2 mode - do not use.
try { [EncryptionMethod.SJCL]: () => {
// Good demo to understand each parameter: https://bitwiseshiftleft.github.io/sjcl/demo/ try {
return sjcl.json.encrypt(key, plainText, { // Good demo to understand each parameter: https://bitwiseshiftleft.github.io/sjcl/demo/
v: 1, // version return sjcl.json.encrypt(key, plainText, {
iter: 1000, // Defaults to 1000 in sjcl but since we're running this on mobile devices, use a lower value. Maybe review this after some time. https://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256 v: 1, // version
ks: 128, // Key size - "128 bits should be secure enough" iter: 1000, // Defaults to 1000 in sjcl but since we're running this on mobile devices, use a lower value. Maybe review this after some time. https://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256
ts: 64, // ??? ks: 128, // Key size - "128 bits should be secure enough"
mode: 'ocb2', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented. ts: 64, // ???
// "adata":"", // Associated Data - not needed? mode: 'ocb2', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
cipher: 'aes', // "adata":"", // Associated Data - not needed?
}); cipher: 'aes',
} catch (error) { });
throw this.wrapSjclError(error); } catch (error) {
} throw this.wrapSjclError(error);
} }
},
// 2020-03-06: Added method to fix https://github.com/laurent22/joplin/issues/2591 // 2020-03-06: Added method to fix https://github.com/laurent22/joplin/issues/2591
// Also took the opportunity to change number of key derivations, per Isaac Potoczny's suggestion // Also took the opportunity to change number of key derivations, per Isaac Potoczny's suggestion
if (method === EncryptionMethod.SJCL1a) { // 2023-06-10: Deprecated in favour of SJCL1b
try { [EncryptionMethod.SJCL1a]: () => {
// We need to escape the data because SJCL uses encodeURIComponent to process the data and it only try {
// accepts UTF-8 data, or else it throws an error. And the notes might occasionally contain // We need to escape the data because SJCL uses encodeURIComponent to process the data and it only
// invalid UTF-8 data. Fixes https://github.com/laurent22/joplin/issues/2591 // accepts UTF-8 data, or else it throws an error. And the notes might occasionally contain
return sjcl.json.encrypt(key, escape(plainText), { // invalid UTF-8 data. Fixes https://github.com/laurent22/joplin/issues/2591
v: 1, // version return sjcl.json.encrypt(key, escape(plainText), {
iter: 101, // Since the master key already uses key derivations and is secure, additional iteration here aren't necessary, which will make decryption faster. SJCL enforces an iter strictly greater than 100 v: 1, // version
ks: 128, // Key size - "128 bits should be secure enough" iter: 101, // Since the master key already uses key derivations and is secure, additional iteration here aren't necessary, which will make decryption faster. SJCL enforces an iter strictly greater than 100
ts: 64, // ??? ks: 128, // Key size - "128 bits should be secure enough"
mode: 'ccm', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented. ts: 64, // ???
// "adata":"", // Associated Data - not needed? mode: 'ccm', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
cipher: 'aes', // "adata":"", // Associated Data - not needed?
}); cipher: 'aes',
} catch (error) { });
throw this.wrapSjclError(error); } catch (error) {
} throw this.wrapSjclError(error);
} }
},
// 2020-01-23: Deprecated - see above. // 2023-06-10: Changed AES-128 to AES-256 per TheQuantumPhysicist's suggestions
// Was used to encrypt master keys // https://github.com/laurent22/joplin/issues/7686
if (method === EncryptionMethod.SJCL2) { [EncryptionMethod.SJCL1b]: () => {
try { try {
return sjcl.json.encrypt(key, plainText, { // We need to escape the data because SJCL uses encodeURIComponent to process the data and it only
v: 1, // accepts UTF-8 data, or else it throws an error. And the notes might occasionally contain
iter: 10000, // invalid UTF-8 data. Fixes https://github.com/laurent22/joplin/issues/2591
ks: 256, return sjcl.json.encrypt(key, escape(plainText), {
ts: 64, v: 1, // version
mode: 'ocb2', iter: 101, // Since the master key already uses key derivations and is secure, additional iteration here aren't necessary, which will make decryption faster. SJCL enforces an iter strictly greater than 100
cipher: 'aes', ks: 256, // Key size - "256-bit is the golden standard that we should follow."
}); ts: 64, // ???
} catch (error) { mode: 'ccm', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
throw this.wrapSjclError(error); // "adata":"", // Associated Data - not needed?
} cipher: 'aes',
} });
} catch (error) {
throw this.wrapSjclError(error);
}
},
if (method === EncryptionMethod.SJCL3) { // 2020-01-23: Deprecated - see above.
try { // Was used to encrypt master keys
// Good demo to understand each parameter: https://bitwiseshiftleft.github.io/sjcl/demo/ [EncryptionMethod.SJCL2]: () => {
return sjcl.json.encrypt(key, plainText, { try {
v: 1, // version return sjcl.json.encrypt(key, plainText, {
iter: 1000, // Defaults to 1000 in sjcl. Since we're running this on mobile devices we need to be careful it doesn't affect performances too much. Maybe review this after some time. https://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256 v: 1,
ks: 128, // Key size - "128 bits should be secure enough" iter: 10000,
ts: 64, // ??? ks: 256,
mode: 'ccm', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented. ts: 64,
// "adata":"", // Associated Data - not needed? mode: 'ocb2',
cipher: 'aes', cipher: 'aes',
}); });
} catch (error) { } catch (error) {
throw this.wrapSjclError(error); throw this.wrapSjclError(error);
} }
} },
// Same as above but more secure (but slower) to encrypt master keys // Don't know why we have this - it's not used anywhere. It must be
if (method === EncryptionMethod.SJCL4) { // kept however, in case some note somewhere is encrypted using this
try { // method.
return sjcl.json.encrypt(key, plainText, { [EncryptionMethod.SJCL3]: () => {
v: 1, try {
iter: 10000, // Good demo to understand each parameter: https://bitwiseshiftleft.github.io/sjcl/demo/
ks: 256, return sjcl.json.encrypt(key, plainText, {
ts: 64, v: 1, // version
mode: 'ccm', iter: 1000, // Defaults to 1000 in sjcl. Since we're running this on mobile devices we need to be careful it doesn't affect performances too much. Maybe review this after some time. https://security.stackexchange.com/questions/3959/recommended-of-iterations-when-using-pkbdf2-sha256
cipher: 'aes', ks: 128, // Key size - "128 bits should be secure enough"
}); ts: 64, // ???
} catch (error) { mode: 'ccm', // The cipher mode is a standard for how to use AES and other algorithms to encrypt and authenticate your message. OCB2 mode is slightly faster and has more features, but CCM mode has wider support because it is not patented.
throw this.wrapSjclError(error); // "adata":"", // Associated Data - not needed?
} cipher: 'aes',
} });
} catch (error) {
throw this.wrapSjclError(error);
}
},
throw new Error(`Unknown encryption method: ${method}`); // Same as above but more secure (but slower) to encrypt master keys
[EncryptionMethod.SJCL4]: () => {
try {
return sjcl.json.encrypt(key, plainText, {
v: 1,
iter: 10000,
ks: 256,
ts: 64,
mode: 'ccm',
cipher: 'aes',
});
} catch (error) {
throw this.wrapSjclError(error);
}
},
[EncryptionMethod.Custom]: () => {
// This is handled elsewhere but as a sanity check, throw an exception
throw new Error('Custom encryption method is not supported here');
},
};
return handlers[method]();
} }
public async decrypt(method: EncryptionMethod, key: string, cipherText: string) { public async decrypt(method: EncryptionMethod, key: string, cipherText: string) {
@ -398,7 +403,7 @@ export default class EncryptionService {
try { try {
const output = sjcl.json.decrypt(key, cipherText); const output = sjcl.json.decrypt(key, cipherText);
if (method === EncryptionMethod.SJCL1a) { if (method === EncryptionMethod.SJCL1a || method === EncryptionMethod.SJCL1b) {
return unescape(output); return unescape(output);
} else { } else {
return output; return output;
@ -649,7 +654,7 @@ export default class EncryptionService {
} }
public isValidEncryptionMethod(method: EncryptionMethod) { public isValidEncryptionMethod(method: EncryptionMethod) {
return [EncryptionMethod.SJCL, EncryptionMethod.SJCL1a, EncryptionMethod.SJCL2, EncryptionMethod.SJCL3, EncryptionMethod.SJCL4].indexOf(method) >= 0; return [EncryptionMethod.SJCL, EncryptionMethod.SJCL1a, EncryptionMethod.SJCL1b, EncryptionMethod.SJCL2, EncryptionMethod.SJCL3, EncryptionMethod.SJCL4].indexOf(method) >= 0;
} }
public async itemIsEncrypted(item: any) { public async itemIsEncrypted(item: any) {