Fix file metadata decryption: use secretstream blob, not secretbox

File metadata is encrypted as a single-chunk secretstream blob (the
'decryptionHeader' is the secretstream init header, not a secretbox
nonce). Collection keys and names correctly use secretbox.

Adds decryptBlob(ciphertext, header, key) to the crypto module as a
convenience wrapper for single-chunk secretstream decryption (init +
pull + verify TAG_FINAL).

Live-tested: collection names and file metadata (titles, types, dates)
decrypt correctly from the real Ente API.
This commit is contained in:
2026-05-13 17:38:18 -07:00
parent f81216333e
commit 44718a92a9
5 changed files with 170 additions and 41 deletions

View File

@@ -23,6 +23,22 @@ export const initStreamPull = (
// Decrypt one ciphertext chunk. Returns the plaintext and the secretstream
// tag (0=MESSAGE, 1=PUSH, 2=REKEY, 3=FINAL). The caller should verify the
// stream ended on TAG_FINAL to detect truncation.
// Decrypt a small blob that was encrypted as a single secretstream chunk
// with TAG_FINAL. Ente uses this form ("blob") for file metadata and
// magic metadata — anything under ~1 MiB that isn't chunked.
export const decryptBlob = (
ciphertext: Uint8Array,
header: Uint8Array,
key: Uint8Array,
): Uint8Array => {
const state = initStreamPull(header, key);
const { plaintext, tag } = pullStreamChunk(state, ciphertext);
if (tag !== sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
throw new Error(`decryptBlob: expected TAG_FINAL (3), got tag ${tag}`);
}
return plaintext;
};
export const pullStreamChunk = (
state: StreamPullState,
ciphertext: Uint8Array,