Repository navigation
perf(profile-sync-controller): Replace JS AES implementation with cryptography package - #10621
Conversation
f34700a to
f7600fc
Compare
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
mathieuartu
left a comment
There was a problem hiding this comment.
Tested manually on extension, features relying on it work without issues. Left one question, would feel more comfortable if somebody more crypto-litterate than me would also have a look, but overall looks good
| const nonce = randomBytes(ALGORITHM_NONCE_SIZE); | ||
|
|
||
| async #encrypt( | ||
| plaintext: Uint8Array<ArrayBuffer>, | ||
| key: Uint8Array<ArrayBuffer>, | ||
| ): Promise<Uint8Array<ArrayBuffer>> { | ||
| // Encrypt and prepend nonce. | ||
| const ciphertext = gcm(key, nonce).encrypt(plaintext); |
There was a problem hiding this comment.
I'm really not an expert on those matters, so please bear with me here. Genuine question: wasn't ALGORITHM_NONCE_SIZE of importance here?
There was a problem hiding this comment.
@metamask/cryptography uses a 12-byte nonce/IV as well! It is equivalent, just generated within the encryption function: https://github.com/MetaMask/core/blob/main/packages/cryptography/src/aes-gcm.ts#L5
There was a problem hiding this comment.
I wonder if we could have exported this constant? E.g we could have re-used it while decoding the bytes on the other part of the code?
So we avoid having drifiting values for ALGORITHM_NONCE_SIZE and AES_GCM_IV_LENGTH
There was a problem hiding this comment.
Ah, makes sense, thanks! But then, if it changes one day in @metamask/cryptography, wouldn't we start having problems here:
async #decrypt(
ciphertextAndNonce: Uint8Array<ArrayBuffer>,
key: Uint8Array<ArrayBuffer>,
): Promise<Uint8Array<ArrayBuffer>> {
// Create buffers of nonce and ciphertext.
const nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
const ciphertext = ciphertextAndNonce.slice(
...The fact that rely both on a "silent" external constant AND a local one is making me a bit uncomfortable. WDYT?
There was a problem hiding this comment.
That's fair, we can export it and re-use!
There was a problem hiding this comment.
Though tbf this shouldn't change, the defacto standard is 12 bytes.
There was a problem hiding this comment.
yep that's right, I feel like it still helps with the code here (especially for the encoding/decoding part)
There was a problem hiding this comment.
I agree and am inclined to approve as-is. But for the sake of future readers, this asymmetry should probably be fixed in a follow up PR.
…yptography package
9e30663 to
0a3cbc4
Compare
ccharly
left a comment
There was a problem hiding this comment.
LGTM (left 1 nit about wording, but that's really minor and not necessarily "incorrect")
| ciphertextAndNonce: Uint8Array<ArrayBuffer>, | ||
| key: Uint8Array<ArrayBuffer>, | ||
| ): Promise<Uint8Array<ArrayBuffer>> { | ||
| // Create buffers of nonce and ciphertext. | ||
| const nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE); | ||
| const nonce = ciphertextAndNonce.slice(0, IV_LENGTH); | ||
| const ciphertext = ciphertextAndNonce.slice( | ||
| ALGORITHM_NONCE_SIZE, | ||
| IV_LENGTH, | ||
| ciphertextAndNonce.length, | ||
| ); | ||
|
|
||
| // Decrypt and return result. | ||
| return gcm(key, nonce).decrypt(ciphertext); | ||
| return decrypt(key, nonce, ciphertext); |
There was a problem hiding this comment.
Nit: But I think we can use the right wording now, I'd go with Iv/iv instead of Nonce/nonce
There was a problem hiding this comment.
They are interchangeable tbh, do you feel strongly about this?
There was a problem hiding this comment.
Not that much 😄 I usually see IV rather than nonce in the context of AES, but that's not incorrect neither
Explanation
Replace
@noble/ciphersAES implementation with@metamask/cryptographyin encryption utilities used byprofile-sync-controller. As a result of this I also had to change some of the internal types to the narrowerUint8Array<ArrayBuffer>type.References
https://consensyssoftware.atlassian.net/browse/WPC-1331
Checklist
Note
Medium Risk
Changes the AES-GCM implementation backing client-side profile sync encryption while keeping the same payload format; wrong crypto behavior would break decrypt compatibility across clients.
Overview
Profile sync encryption now uses
@metamask/cryptography/aes-gcminstead of@noble/ciphers, with@metamask/cryptographyadded as a dependency and@noble/ciphersremoved. The on-disk layout is unchanged: scrypt-derived keys and IV + ciphertext still use a 12-byte IV (viaIV_LENGTH), but#encrypt/#decryptare async and byte handling goes through@metamask/utils(stringToBytes,concatBytes,getErrorMessage).Internal types are tightened to
Uint8Array<ArrayBuffer>in the encryption cache, KDF paths, andNativeScrypt.createSHA256Hashnow returns hex without a0xprefix (remove0x(bytesToHex(...))). Tests expect generic WebCrypto-style decrypt failures instead of noble’s invalid ghash tag message.Monorepo wiring adds
@metamask/cryptographyto TS project references,tsconfig.packages.jsonpaths, JestmoduleNameMapper, README dependency graph, and changelog entry.Reviewed by Cursor Bugbot for commit 8e2bd03. Bugbot is set up for automated code reviews on this repo. Configure here.