Skip to content

Commit f584cf0

Browse files
committed
fix: use salt_v2 to prevent cache collisions
1 parent 052e332 commit f584cf0

File tree

4 files changed

+27
-44
lines changed

4 files changed

+27
-44
lines changed

packages/profile-sync-controller/src/shared/encryption/cache.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { SHARED_SALT } from './constants';
21
import { byteArrayToBase64 } from './utils';
32

43
type CachedEntry = {
@@ -47,31 +46,6 @@ export function getCachedKeyBySalt(
4746
};
4847
}
4948

50-
/**
51-
* Gets the cached key that was generated without a salt, if it exists.
52-
* This is unique per hashed password.
53-
*
54-
* @param hashedPassword - hashed password for cache lookup
55-
* @returns the cached key
56-
*/
57-
export function getCachedKeyGeneratedWithSharedSalt(
58-
hashedPassword: string,
59-
): CachedEntry | undefined {
60-
const cache = getPasswordCache(hashedPassword);
61-
const base64Salt = byteArrayToBase64(SHARED_SALT);
62-
const cachedKey = cache.get(base64Salt);
63-
64-
if (!cachedKey) {
65-
return undefined;
66-
}
67-
68-
return {
69-
salt: SHARED_SALT,
70-
base64Salt,
71-
key: cachedKey,
72-
};
73-
}
74-
7549
/**
7650
* Sets a key to the in memory cache.
7751
* We have set an arbitrary size of 10 cached keys per hashed password.

packages/profile-sync-controller/src/shared/encryption/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ export const SCRYPT_p = 1; // Parallelization parameter
1313
export const SHARED_SALT = new Uint8Array([
1414
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
1515
]);
16+
// Different salt for SCRYPT_N_V2 to prevent cache collisions on outdated clients
17+
export const SHARED_SALT_V2 = new Uint8Array([
18+
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
19+
]);

packages/profile-sync-controller/src/shared/encryption/encryption.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@ describe('encryption tests', () => {
4141
});
4242

4343
it('should derive and use a different key for entries that have the same password but different encryption options', async () => {
44+
// Test decryption of old format (N:2^17 with SHARED_SALT)
4445
const data1EncryptedWithOldN =
4546
'{"v":"1","t":"scrypt","d":"AAECAwQFBgcICQoLDA0OD5rMiKCTz61h5abF98yn50UPflCq6Ozov3NAk+y4h6o5bp0jJLJ0rw==","o":{"N":131072,"r":8,"p":1,"dkLen":16},"saltLen":16}';
46-
const data1EncryptedWithNewN =
47-
'{"v":"1","t":"scrypt","d":"AAECAwQFBgcICQoLDA0ODx8V+QwHALZFtAw/DhXD9ev78fT327jgGXlB7/kXeZQc6zaRqw6VgA==","o":{"N":2,"r":8,"p":1,"dkLen":16},"saltLen":16}';
47+
48+
// Generate encrypted data with new format dynamically
49+
const data1EncryptedWithNewN = await encryption.encryptString(
50+
DATA1,
51+
PASSWORD,
52+
);
4853

4954
const decrypted1 = await encryption.decryptString(
5055
data1EncryptedWithOldN,
@@ -160,7 +165,7 @@ describe('encryption tests', () => {
160165
expect(result).toBe(true);
161166
});
162167

163-
it('should return false if entry uses the shared salt and the new scrypt N parameter', () => {
168+
it('should return false if entry uses new scrypt N parameter', () => {
164169
const entry = `{"v":"1","t":"scrypt","d":"AAECAwQFBgcICQoLDA0ODzGY11N8GhRoaD6YMqLp/sZnRHaG9gbNEyASWZSok/xBZhAOw2hKOH6qieSQSCYmtZZjqZ6lxfEfsYyS2ivGhUrVQmJYSXpr78As4Bc7pnLQACPfLJqiFwDVRG4Lf/k+DpfKzBmdS1h+nOiTHaN8MmMY6jKkfjVqnJSEkvKcQBnOBw27+PW8L1OQBXITaImO1GOE4OOBjfD4XX7uezBrsv0TuFWeDumSzYqDnw==","o":{"N":${SCRYPT_N_V2},"r":8,"p":1,"dkLen":16},"saltLen":16}`;
165170

166171
const result = encryption.doesEntryNeedReEncryption(entry);

packages/profile-sync-controller/src/shared/encryption/encryption.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import { scryptAsync } from '@noble/hashes/scrypt';
44
import { sha256 } from '@noble/hashes/sha256';
55
import { utf8ToBytes, concatBytes, bytesToHex } from '@noble/hashes/utils';
66

7-
import {
8-
getCachedKeyBySalt,
9-
getCachedKeyGeneratedWithSharedSalt,
10-
setCachedKey,
11-
} from './cache';
7+
import { getCachedKeyBySalt, setCachedKey } from './cache';
128
import {
139
ALGORITHM_KEY_SIZE,
1410
ALGORITHM_NONCE_SIZE,
@@ -18,6 +14,7 @@ import {
1814
SCRYPT_r,
1915
SCRYPT_SALT_SIZE,
2016
SHARED_SALT,
17+
SHARED_SALT_V2,
2118
} from './constants';
2219
import {
2320
base64ToByteArray,
@@ -229,12 +226,12 @@ class EncryptorDecryptor {
229226

230227
doesEntryNeedReEncryption(encryptedDataStr: string): boolean {
231228
try {
232-
const doesEntryHaveRandomSalt =
233-
this.getSalt(encryptedDataStr).toString() !== SHARED_SALT.toString();
234-
const doesEntryUseOldScryptN =
235-
JSON.parse(encryptedDataStr).o?.N !== SCRYPT_N_V2;
229+
const encryptedData: EncryptedPayload = JSON.parse(encryptedDataStr);
236230

237-
return doesEntryHaveRandomSalt || doesEntryUseOldScryptN;
231+
// Only check N value - in production, only two valid scenarios exist:
232+
// 1. N:2**17 + SHARED_SALT (old code)
233+
// 2. N:2 + SHARED_SALT_V2 (new code)
234+
return encryptedData.o?.N !== SCRYPT_N_V2;
238235
} catch {
239236
return false;
240237
}
@@ -267,10 +264,13 @@ class EncryptorDecryptor {
267264
salt?: Uint8Array,
268265
nativeScryptCrypto?: NativeScrypt,
269266
) {
270-
const hashedPassword = `${password}.${o.N}.${o.r}.${o.p}.${o.dkLen}`;
271-
const cachedKey = salt
272-
? getCachedKeyBySalt(hashedPassword, salt)
273-
: getCachedKeyGeneratedWithSharedSalt(hashedPassword);
267+
const hashedPassword = createSHA256Hash(password);
268+
269+
// Determine which salt to use (for both lookup and generation)
270+
const targetSalt =
271+
salt ?? (o.N === SCRYPT_N_V2 ? SHARED_SALT_V2 : SHARED_SALT);
272+
273+
const cachedKey = getCachedKeyBySalt(hashedPassword, targetSalt);
274274

275275
if (cachedKey) {
276276
return {
@@ -279,7 +279,7 @@ class EncryptorDecryptor {
279279
};
280280
}
281281

282-
const newSalt = salt ?? SHARED_SALT;
282+
const newSalt = targetSalt;
283283

284284
let newKey: Uint8Array;
285285

0 commit comments

Comments
 (0)