fix(db): use random per-encryption salt in crypto.ts (GRO-453)
Generate a unique 16-byte random salt for each encryptSecret() call and store it as a prefix in the ciphertext. Format changed from iv:ciphertext:authTag → salt:iv:ciphertext:authTag decryptSecret() detects legacy 3-part format and uses the fixed package salt for backward compatibility with existing encrypted rows. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -24,11 +24,11 @@ describe("encryptSecret / decryptSecret", () => {
|
||||
expect(decrypted).toBe(plaintext);
|
||||
});
|
||||
|
||||
it("produces output in iv:ciphertext:authTag format", () => {
|
||||
it("produces output in salt:iv:ciphertext:authTag format", () => {
|
||||
const encrypted = encryptSecret("test");
|
||||
const parts = encrypted.split(":");
|
||||
|
||||
expect(parts).toHaveLength(3);
|
||||
expect(parts).toHaveLength(4);
|
||||
// Each part should be valid base64
|
||||
parts.forEach((part) => {
|
||||
expect(() => Buffer.from(part, "base64")).not.toThrow();
|
||||
@@ -61,12 +61,11 @@ describe("encryptSecret / decryptSecret", () => {
|
||||
});
|
||||
|
||||
it("throws when decrypting invalid format (wrong number of parts)", () => {
|
||||
const encrypted = encryptSecret("test");
|
||||
// Replace the last ":authTag" part by matching colon + non-colon chars at the end
|
||||
const invalid = encrypted.replace(/:[^:]+$/, "");
|
||||
// 2 parts is invalid for both legacy (3) and new (4) format
|
||||
const invalid = "not-enough-parts";
|
||||
|
||||
expect(() => decryptSecret(invalid)).toThrow(
|
||||
"Invalid encrypted value format: expected iv:ciphertext:authTag"
|
||||
"Invalid encrypted value format: expected salt:iv:ciphertext:authTag or iv:ciphertext:authTag"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user