RSA-OAEP Encrypt: OperationError on File upload but not strings using SubtleCrypto

huangapple go评论85阅读模式
英文:

RSA-OAEP Encrypt: OperationError on File upload but not strings using SubtleCrypto

问题

我正在尝试允许客户端上传文件,然后使用服务器提供的公钥对文件进行RSA-OAEP加密,然后上传文件并使服务器完全能够读取文件。然而,我的当前代码在Safari和Firefox上出现“操作错误:由于操作特定原因而失败”(OperationError)。以下是我的代码:

document.getElementById("input").addEventListener('change', async event => {
    if (event.target.files[0]) {
        const reader = new FileReader();
        reader.addEventListener('load', async (event2) => {
            const res = await fetch("/key");
            const exported = await res.text();
            const key = await importRSAPublicKey(exported);
            const arrayBuffer = event2.target.result;
            console.log(arrayBuffer);
            const encrypted = await encryptRSA(key, arrayBuffer); // 在调用window.crypto.subtle.encrypt()时失败
            console.log(encrypted);
            await fetch(`/upload`, {method: "POST", body: encrypted});
        });
        reader.addEventListener('error', () => {
            reject(new Error("读取插入的文件时出现错误。"));
        })
        /**
         * @type {File}
         */
        const file = event.target.files[0];
        reader.readAsArrayBuffer(file);
    }
});

async function importRSAPublicKey(key) {
    return new Promise(async (resolve, reject) => {
        try {
            const imported = await window.crypto.subtle.importKey(
                "jwk",
                JSON.parse(atob(key)),
                {
                    name: "RSA-OAEP",
                    modulusLength: 4096,
                    publicExponent: new Uint8Array([1, 0, 1]),
                    hash: "SHA-256",
                },
                true,
                ["encrypt"]
            );
            return resolve(imported);
        } catch (error) {
            reject(error);
        }
    })
}

async function encryptRSA(key, data) {
    return new Promise(async (resolve, reject) => {
        try {        
            const encryptedData = await window.crypto.subtle.encrypt(
                { name: "RSA-OAEP" },
                key,
                data
            )
    
            const uintArray = new Uint8Array(encryptedData);
    
            const string = String.fromCharCode.apply(null, uintArray);
    
            const base64Data = btoa(string);
    
            return resolve(base64Data);
        } catch (error) {
            return reject(error);
        }
    });
}

有趣的是,相同的操作,无论是对文本还是通过类似的AES-GCM代码对相同文件进行操作,都能正常工作。以下是用于明文应用的(正常工作的)代码:

document.getElementById("input").addEventListener('change', async event => {
    const res = await fetch("/key");
    const exported = await res.text();
    const key = await importRSAPublicKey(exported);
    const encrypted = await encryptRSA(key, new TextEncoder().encode("Test"));
    console.log(encrypted);
    await fetch(`/upload`, {method: "POST", body: encrypted});
});
// ... 从前一个代码块中未更改的辅助函数

为什么在两种情况下都传入ArrayBuffer,但一种有效而另一种无效?

如果有人告诉我如何在不使用btoa()对明文进行加密(从那里将其转换回实际二进制是一件麻烦的事情)的情况下加密这些内容,我会非常感激。

更新:如果我使用File.prototype.arrayBuffer()加载文件而不是使用FileReader读取文件,会出现相同的问题。

英文:

I am trying to allow the client to upload a file, have the file be encrypted with RSA-OAEP using a public key provided by the server, then have the file be uploaded and completely readable by the server.
However, my current code gives me OperationError: The operation failed for an operation-specific reason (on Safari and Firefox). Here is my code:

document.getElementById("input").addEventListener('change', async event => {
	if (event.target.files[0]) {
		const reader = new FileReader();
		reader.addEventListener('load', async (event2) => {
			const res = await fetch("/key");
			const exported = await res.text();
			const key = await importRSAPublicKey(exported);
			const arrayBuffer = event2.target.result;
			console.log(arrayBuffer);
			const encrypted = await encryptRSA(key, arrayBuffer); // Fails here at the call to window.crypto.subtle.encrypt()
			console.log(encrypted);
			await fetch(`/upload`, {method: "POST", body: encrypted});
		});
		reader.addEventListener('error', () => {
			reject(new Error("There was an error reading the inserted file as text."));
		})
		/**
		 * @type {File}
		 */
		const file = event.target.files[0];
		reader.readAsArrayBuffer(file);
	}
});

async function importRSAPublicKey(key) {
	return new Promise(async (resolve, reject) => {
		try {
			const imported = await window.crypto.subtle.importKey(
				"jwk",
				JSON.parse(atob(key)),
				{
					name: "RSA-OAEP",
					modulusLength: 4096,
					publicExponent: new Uint8Array([1, 0, 1]),
					hash: "SHA-256",
				},
				true,
				["encrypt"]
			);
			return resolve(imported);
		} catch (error) {
			reject(error);
		}
	})
}

async function encryptRSA(key, data) {
	return new Promise(async (resolve, reject) => {
		try {		
			const encryptedData = await window.crypto.subtle.encrypt(
				{ name: "RSA-OAEP" },
				key,
				data
			)
	
			const uintArray = new Uint8Array(encryptedData);
	
			const string = String.fromCharCode.apply(null, uintArray);
	
			const base64Data = btoa(string);
	
			return resolve(base64Data);
		} catch (error) {
			return reject(error);
		}
	});
}

Interestingly, the same operation, when preformed either on text or via similar code for AES-GCM on the same file, works perfectly. Here is my (working) code for the plaintext application:

document.getElementById("input").addEventListener('change', async event => {
	const res = await fetch("/key");
	const exported = await res.text();
	const key = await importRSAPublicKey(exported);
	const encrypted = await encryptRSA(key, new TextEncoder().encode("Test"));
	console.log(encrypted);
	await fetch(`/upload`, {method: "POST", body: encrypted});
});
// ... Helper functions from the previous codeblock unchanged

Why, since both times an ArrayBuffer is being passed into the function, does one work and the other not?

I would love it if someone could tell me a way I can encrypt these without using btoa() on the cleartext (which is a pain to convert back into actual binary from there).

UPDATE: The same issue occurs if I load the file using File.prototype.arrayBuffer() instead of reading it with a FileReader.

答案1

得分: 0

多亏了@Ebbe M. Pedersen的评论,我意识到我的角度完全错误。使用RSA加密较大的文件是不可能的,而我的方法应该是使用AES-GCM加密文件,然后使用RSA加密这个AES密钥,然后再发送。

英文:

Thanks to a comment by @Ebbe M. Pedersen, it was brought to my attention that my angle was all wrong. It is not possible to encrypt larger files using RSA, and instead my approach should be to encrypt the file using AES-GCM, then encrypt this AES key with RSA, before sending it.

huangapple
  • 本文由 发表于 2023年6月1日 14:43:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76379292.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定