This commit is contained in:
Manav Rathi
2024-11-20 13:02:52 +05:30
parent b21ab6779a
commit 76dca8e5f6
2 changed files with 41 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
import {
ENCRYPTION_CHUNK_SIZE,
streamEncryptionChunkSize,
type B64EncryptionResult,
} from "@/base/crypto/libsodium";
import { type CryptoWorker } from "@/base/crypto/worker";
@@ -68,16 +68,17 @@ interface FileStream {
/**
* A stream of the file's contents
*
* This stream is guaranteed to emit data in ENCRYPTION_CHUNK_SIZE chunks
* (except the last chunk which can be smaller since a file would rarely
* align exactly to a ENCRYPTION_CHUNK_SIZE multiple).
* This stream is guaranteed to emit data in
* {@link streamEncryptionChunkSize} sized chunks (except the last chunk
* which can be smaller since a file would rarely align exactly to a
* {@link streamEncryptionChunkSize} multiple).
*
* Note: A stream can only be read once!
*/
stream: ReadableStream<Uint8Array>;
/**
* Number of chunks {@link stream} will emit, each ENCRYPTION_CHUNK_SIZE
* sized (except the last one).
* Number of chunks {@link stream} will emit, each
* {@link streamEncryptionChunkSize} sized (except the last one).
*/
chunkCount: number;
/**
@@ -95,11 +96,12 @@ interface FileStream {
}
/**
* If the stream we have is more than 5 ENCRYPTION_CHUNK_SIZE chunks, then use
* multipart uploads for it, with each multipart-part containing 5 chunks.
* If the stream we have is more than 5 {@link streamEncryptionChunkSize}
* chunks, then use multipart uploads for it, with each multipart-part
* containing 5 chunks.
*
* ENCRYPTION_CHUNK_SIZE is 4 MB, and the number of chunks in a single upload
* part is 5, so each part is (up to) 20 MB.
* {@link streamEncryptionChunkSize} is 4 MB, and the number of chunks in a
* single upload part is 5, so each part is (up to) 20 MB.
*/
const multipartChunksPerPart = 5;
@@ -244,14 +246,15 @@ interface EncryptedFileStream {
/**
* A stream of the file's encrypted contents
*
* This stream is guaranteed to emit data in ENCRYPTION_CHUNK_SIZE chunks
* (except the last chunk which can be smaller since a file would rarely
* align exactly to a ENCRYPTION_CHUNK_SIZE multiple).
* This stream is guaranteed to emit data in
* {@link streamEncryptionChunkSize} chunks (except the last chunk which can
* be smaller since a file would rarely align exactly to a
* {@link streamEncryptionChunkSize} multiple).
*/
stream: ReadableStream<Uint8Array>;
/**
* Number of chunks {@link stream} will emit, each ENCRYPTION_CHUNK_SIZE
* sized (except the last one).
* Number of chunks {@link stream} will emit, each
* {@link streamEncryptionChunkSize} sized (except the last one).
*/
chunkCount: number;
}
@@ -769,11 +772,11 @@ const readUploadItem = async (uploadItem: UploadItem): Promise<FileStream> => {
lastModifiedMs = file.lastModified;
}
const N = ENCRYPTION_CHUNK_SIZE;
const chunkCount = Math.ceil(fileSize / ENCRYPTION_CHUNK_SIZE);
const N = streamEncryptionChunkSize;
const chunkCount = Math.ceil(fileSize / streamEncryptionChunkSize);
// Pipe the underlying stream through a transformer that emits
// ENCRYPTION_CHUNK_SIZE-ed chunks (except the last one, which can be
// streamEncryptionChunkSize-ed chunks (except the last one, which can be
// smaller).
let pending: Uint8Array | undefined;
const transformer = new TransformStream<Uint8Array, Uint8Array>({

View File

@@ -9,7 +9,6 @@
* To see where this code fits, see [Note: Crypto code hierarchy].
*/
import { mergeUint8Arrays } from "@/utils/array";
import { CustomError } from "@ente/shared/error";
import sodium, { type StateAddress } from "libsodium-wrappers-sumo";
import type {
BytesOrB64,
@@ -244,6 +243,11 @@ export const generateNewBlobOrStreamKey = async () => {
* associated with an Ente object, and Box for the other cases.
*
* 3. Box returns a "nonce", while Blob returns a "header".
*
* The difference between case 2 and 3 (Blob vs Stream) is that while both use
* the same algorithms, in case of Blob the entire data is encrypted / decrypted
* in one go, whilst the *Stream routines first break it into
* {@link streamEncryptionChunkSize} chunks.
*/
export const encryptBoxB64 = async (
data: BytesOrB64,
@@ -315,7 +319,18 @@ export const encryptBlobB64 = async (
};
};
export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024;
/**
* The various *Stream encryption functions break up the input into chunks of
* {@link streamEncryptionChunkSize} bytes during encryption (except the last
* chunk which can be smaller since a file would rarely align exactly to a
* {@link streamEncryptionChunkSize} multiple).
*
* The various *Stream decryption functions also assume that each potential
* chunk is {@link streamEncryptionChunkSize} long.
*
* This value of this constant is 4 MB (and is unlikely to change).
*/
export const streamEncryptionChunkSize = 4 * 1024 * 1024;
export const encryptChaCha = async (data: Uint8Array) => {
await sodium.ready;
@@ -332,7 +347,7 @@ export const encryptChaCha = async (data: Uint8Array) => {
const encryptedChunks = [];
while (tag !== sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
let chunkSize = ENCRYPTION_CHUNK_SIZE;
let chunkSize = streamEncryptionChunkSize;
if (bytesRead + chunkSize >= data.length) {
chunkSize = data.length - bytesRead;
tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
@@ -452,7 +467,7 @@ export const decryptChaCha = async (
await fromB64(key),
);
const decryptionChunkSize =
ENCRYPTION_CHUNK_SIZE +
streamEncryptionChunkSize +
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
let bytesRead = 0;
const decryptedChunks = [];
@@ -486,7 +501,7 @@ export async function initChunkDecryption(header: Uint8Array, key: Uint8Array) {
key,
);
const decryptionChunkSize =
ENCRYPTION_CHUNK_SIZE +
streamEncryptionChunkSize +
sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
const tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
return { pullState, decryptionChunkSize, tag };