From bbeaee8344a81204a2b45b8939fb1af0b906787c Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 10:16:32 -0400 Subject: [PATCH 1/6] chore(ts): update to latest v6 --- yarn.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yarn.lock b/yarn.lock index f6ebc6a..3761281 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8962,6 +8962,11 @@ typescript@3.5.x: version "3.5.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" +typescript@6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + typescript@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" From f3fd978843c4bdc1296df5da3a03e1a471c3f0d2 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 10:10:42 -0400 Subject: [PATCH 2/6] chore(cryptr): update to v6 --- package.json | 2 +- src/lib/crypt.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++++- yarn.lock | 7 +++--- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 907b11b..b642c1a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "body-parser-xml": "^2.0.3", "connect-datadog-graphql": "^0.0.12", "cors": "^2.8.5", - "cryptr": "^4.0.2", + "cryptr": "^6.4.0", "db-migrate": "^0.11.13", "db-migrate-pg": "^1.2.2", "envalid": "^5.0.0", diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index 1da1e84..33a21e3 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -1,5 +1,58 @@ +import crypto from 'crypto'; import cryptr from 'cryptr'; import config from '../config'; -export const crypt = new cryptr(config.applicationSecret); +// Decrypts ciphertexts produced by cryptr v4 (aes-256-ctr + SHA-256 key). +// Kept so existing DB credentials and client tokens remain readable after the +// cryptr v6 upgrade, until Phase 2 (DB re-encryption) is complete. +function decryptV4(value: string): string { + const key = crypto + .createHash('sha256') + .update(config.applicationSecret) + .digest(); + const stringValue = String(value); + const iv = Buffer.from(stringValue.slice(0, 32), 'hex'); + const encrypted = stringValue.slice(32); + let legacyValue = false; + let decipher: crypto.Decipher | undefined; + + try { + decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); + } catch (exception: any) { + if (exception.message === 'Invalid IV length') { + legacyValue = true; + } else { + throw exception; + } + } + + if (!legacyValue) { + return decipher!.update(encrypted, 'hex', 'utf8') + decipher!.final('utf8'); + } + + const legacyIv = stringValue.slice(0, 16); + const legacyEncrypted = stringValue.slice(16); + const legacyDecipher = crypto.createDecipheriv( + 'aes-256-ctr', + key, + Buffer.from(legacyIv, 'hex') + ); + return ( + legacyDecipher.update(legacyEncrypted, 'hex', 'utf8') + + legacyDecipher.final('utf8') + ); +} + +const cryptV6 = new cryptr(config.applicationSecret); + +export const crypt = { + encrypt: (value: string) => cryptV6.encrypt(value), + decrypt: (value: string): string => { + try { + return cryptV6.decrypt(value); + } catch { + return decryptV4(value); + } + }, +}; diff --git a/yarn.lock b/yarn.lock index 3761281..2ac461d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2921,9 +2921,10 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -cryptr@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cryptr/-/cryptr-4.0.2.tgz#8a93b5ca7667d1a6131e396bab23a134ff1f5dc6" +cryptr@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/cryptr/-/cryptr-6.4.0.tgz#a5f49b77679d411ceeb6cbf0acd51a94f3d050a9" + integrity sha512-9jpMU9HMt1vhMUqNO+MPuGEpbh/f7HHZdxrd6L2DMwTuYGyt9pgUJfQyTS1Ei4/sn7qPM4FkjxUoiW79k0x8sA== currently-unhandled@^0.4.1: version "0.4.1" From 55d4d67e7ad31363e70c111dacf01850529d102e Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 10:22:37 -0400 Subject: [PATCH 3/6] chore: update types --- src/lib/crypt.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index 33a21e3..ea59727 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -7,12 +7,11 @@ import config from '../config'; // Kept so existing DB credentials and client tokens remain readable after the // cryptr v6 upgrade, until Phase 2 (DB re-encryption) is complete. function decryptV4(value: string): string { - const key = crypto - .createHash('sha256') - .update(config.applicationSecret) - .digest(); + const key = Uint8Array.from( + crypto.createHash('sha256').update(config.applicationSecret).digest() + ); const stringValue = String(value); - const iv = Buffer.from(stringValue.slice(0, 32), 'hex'); + const iv = Uint8Array.from(Buffer.from(stringValue.slice(0, 32), 'hex')); const encrypted = stringValue.slice(32); let legacyValue = false; let decipher: crypto.Decipher | undefined; @@ -31,13 +30,11 @@ function decryptV4(value: string): string { return decipher!.update(encrypted, 'hex', 'utf8') + decipher!.final('utf8'); } - const legacyIv = stringValue.slice(0, 16); - const legacyEncrypted = stringValue.slice(16); - const legacyDecipher = crypto.createDecipheriv( - 'aes-256-ctr', - key, - Buffer.from(legacyIv, 'hex') + const legacyIv = Uint8Array.from( + Buffer.from(stringValue.slice(0, 16), 'hex') ); + const legacyEncrypted = stringValue.slice(16); + const legacyDecipher = crypto.createDecipheriv('aes-256-ctr', key, legacyIv); return ( legacyDecipher.update(legacyEncrypted, 'hex', 'utf8') + legacyDecipher.final('utf8') From 6ac44df6c133d97831c0f15e813aa7da8d3278d8 Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 11:26:55 -0400 Subject: [PATCH 4/6] chore: run install --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2ac461d..51fabbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8963,11 +8963,6 @@ typescript@3.5.x: version "3.5.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" -typescript@6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" - integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== - typescript@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" From 250429133876bbebe918f4e3613d6cdba1f60baf Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 11:30:52 -0400 Subject: [PATCH 5/6] chore: update decipher type --- src/lib/crypt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index ea59727..78eb9fa 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -14,7 +14,7 @@ function decryptV4(value: string): string { const iv = Uint8Array.from(Buffer.from(stringValue.slice(0, 32), 'hex')); const encrypted = stringValue.slice(32); let legacyValue = false; - let decipher: crypto.Decipher | undefined; + let decipher: crypto.Decipheriv | undefined; try { decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); From 5d220de9e3f1360d07862fd65f42df1ab3edd41a Mon Sep 17 00:00:00 2001 From: Aashish John Date: Fri, 19 Jun 2026 11:50:31 -0400 Subject: [PATCH 6/6] chore: clean up crypt branches --- src/lib/crypt.ts | 45 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/src/lib/crypt.ts b/src/lib/crypt.ts index 78eb9fa..717bf38 100644 --- a/src/lib/crypt.ts +++ b/src/lib/crypt.ts @@ -7,38 +7,14 @@ import config from '../config'; // Kept so existing DB credentials and client tokens remain readable after the // cryptr v6 upgrade, until Phase 2 (DB re-encryption) is complete. function decryptV4(value: string): string { - const key = Uint8Array.from( - crypto.createHash('sha256').update(config.applicationSecret).digest() - ); - const stringValue = String(value); - const iv = Uint8Array.from(Buffer.from(stringValue.slice(0, 32), 'hex')); - const encrypted = stringValue.slice(32); - let legacyValue = false; - let decipher: crypto.Decipheriv | undefined; - - try { - decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); - } catch (exception: any) { - if (exception.message === 'Invalid IV length') { - legacyValue = true; - } else { - throw exception; - } - } - - if (!legacyValue) { - return decipher!.update(encrypted, 'hex', 'utf8') + decipher!.final('utf8'); - } - - const legacyIv = Uint8Array.from( - Buffer.from(stringValue.slice(0, 16), 'hex') - ); - const legacyEncrypted = stringValue.slice(16); - const legacyDecipher = crypto.createDecipheriv('aes-256-ctr', key, legacyIv); - return ( - legacyDecipher.update(legacyEncrypted, 'hex', 'utf8') + - legacyDecipher.final('utf8') - ); + const key = crypto + .createHash('sha256') + .update(config.applicationSecret) + .digest(); + const iv = Buffer.from(value.slice(0, 32), 'hex'); + const encrypted = value.slice(32); + const decipher = crypto.createDecipheriv('aes-256-ctr', key, iv); + return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8'); } const cryptV6 = new cryptr(config.applicationSecret); @@ -48,7 +24,10 @@ export const crypt = { decrypt: (value: string): string => { try { return cryptV6.decrypt(value); - } catch { + } catch (err: any) { + if (err?.message?.includes('unable to authenticate data')) { + throw err; + } return decryptV4(value); } },