remotely-save/src/encryptOpenSSL.ts

187 lines
4.3 KiB
TypeScript
Raw Permalink Normal View History

2021-12-30 17:00:52 +00:00
import { base32, base64url } from "rfc4648";
2021-12-31 15:46:52 +00:00
import { bufferToArrayBuffer, hexStringToTypedArray } from "./misc";
2021-10-27 17:53:16 +00:00
2021-11-14 16:26:40 +00:00
const DEFAULT_ITER = 20000;
2021-10-26 16:33:35 +00:00
2021-11-07 16:39:17 +00:00
// base32.stringify(Buffer.from('Salted__'))
export const MAGIC_ENCRYPTED_PREFIX_BASE32 = "KNQWY5DFMRPV";
2021-12-30 17:00:52 +00:00
// base64.stringify(Buffer.from('Salted__'))
export const MAGIC_ENCRYPTED_PREFIX_BASE64URL = "U2FsdGVkX";
2021-11-07 16:39:17 +00:00
2021-11-04 02:10:05 +00:00
const getKeyIVFromPassword = async (
salt: Uint8Array,
2021-10-26 16:33:35 +00:00
password: string,
rounds: number = DEFAULT_ITER
) => {
2021-11-04 02:10:05 +00:00
const k1 = await window.crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveKey", "deriveBits"]
);
2021-10-26 16:33:35 +00:00
2021-11-04 02:10:05 +00:00
const k2 = await window.crypto.subtle.deriveBits(
{
name: "PBKDF2",
salt: salt,
iterations: rounds,
hash: "SHA-256",
},
k1,
256 + 128
);
return k2;
2021-10-26 16:33:35 +00:00
};
2021-11-02 16:15:23 +00:00
export const encryptArrayBuffer = async (
2021-10-26 16:33:35 +00:00
arrBuf: ArrayBuffer,
password: string,
2021-11-05 16:23:30 +00:00
rounds: number = DEFAULT_ITER,
2024-05-07 16:20:15 +00:00
saltHex = ""
2021-10-26 16:33:35 +00:00
) => {
2021-11-05 16:23:30 +00:00
let salt: Uint8Array;
if (saltHex !== "") {
salt = hexStringToTypedArray(saltHex);
} else {
salt = window.crypto.getRandomValues(new Uint8Array(8));
}
2021-11-04 02:10:05 +00:00
const derivedKey = await getKeyIVFromPassword(salt, password, rounds);
const key = derivedKey.slice(0, 32);
const iv = derivedKey.slice(32, 32 + 16);
const keyCrypt = await window.crypto.subtle.importKey(
"raw",
key,
{ name: "AES-CBC" },
false,
["encrypt", "decrypt"]
2021-11-02 02:11:45 +00:00
);
2021-11-04 02:10:05 +00:00
const enc = (await window.crypto.subtle.encrypt(
{ name: "AES-CBC", iv },
keyCrypt,
arrBuf
)) as ArrayBuffer;
const prefix = new TextEncoder().encode("Salted__");
2021-11-04 17:21:33 +00:00
const res = new Uint8Array([...prefix, ...salt, ...new Uint8Array(enc)]);
2021-11-04 02:10:05 +00:00
return bufferToArrayBuffer(res);
2021-10-26 16:33:35 +00:00
};
2021-11-02 16:15:23 +00:00
export const decryptArrayBuffer = async (
2021-10-26 16:33:35 +00:00
arrBuf: ArrayBuffer,
password: string,
rounds: number = DEFAULT_ITER
) => {
2021-11-04 02:10:05 +00:00
const prefix = arrBuf.slice(0, 8);
const salt = arrBuf.slice(8, 16);
const derivedKey = await getKeyIVFromPassword(
new Uint8Array(salt),
password,
rounds
2021-11-02 02:11:45 +00:00
);
2021-11-04 02:10:05 +00:00
const key = derivedKey.slice(0, 32);
const iv = derivedKey.slice(32, 32 + 16);
const keyCrypt = await window.crypto.subtle.importKey(
"raw",
key,
{ name: "AES-CBC" },
false,
["encrypt", "decrypt"]
);
const dec = (await window.crypto.subtle.decrypt(
{ name: "AES-CBC", iv },
keyCrypt,
arrBuf.slice(16)
)) as ArrayBuffer;
return dec;
2021-10-26 16:33:35 +00:00
};
2021-11-02 16:15:23 +00:00
export const encryptStringToBase32 = async (
2021-10-26 16:33:35 +00:00
text: string,
password: string,
2021-11-05 16:23:30 +00:00
rounds: number = DEFAULT_ITER,
2024-05-07 16:20:15 +00:00
saltHex = ""
2021-10-26 16:33:35 +00:00
) => {
2021-11-05 16:23:30 +00:00
const enc = await encryptArrayBuffer(
bufferToArrayBuffer(new TextEncoder().encode(text)),
password,
rounds,
saltHex
2021-11-02 16:15:23 +00:00
);
2021-12-30 17:00:52 +00:00
return base32.stringify(new Uint8Array(enc), { pad: false });
2021-10-26 16:33:35 +00:00
};
2021-11-02 16:15:23 +00:00
export const decryptBase32ToString = async (
2021-10-26 16:33:35 +00:00
text: string,
password: string,
rounds: number = DEFAULT_ITER
) => {
2021-11-05 16:23:30 +00:00
return new TextDecoder().decode(
2021-11-04 02:10:05 +00:00
await decryptArrayBuffer(
2021-12-30 17:00:52 +00:00
bufferToArrayBuffer(base32.parse(text, { loose: true })),
password,
rounds
)
);
};
export const encryptStringToBase64url = async (
text: string,
password: string,
rounds: number = DEFAULT_ITER,
2024-05-07 16:20:15 +00:00
saltHex = ""
2021-12-30 17:00:52 +00:00
) => {
const enc = await encryptArrayBuffer(
bufferToArrayBuffer(new TextEncoder().encode(text)),
password,
rounds,
saltHex
);
return base64url.stringify(new Uint8Array(enc), { pad: false });
};
export const decryptBase64urlToString = async (
text: string,
password: string,
rounds: number = DEFAULT_ITER
) => {
return new TextDecoder().decode(
await decryptArrayBuffer(
bufferToArrayBuffer(base64url.parse(text, { loose: true })),
2021-11-02 16:15:23 +00:00
password,
rounds
)
2021-11-05 16:23:30 +00:00
);
2021-10-26 16:33:35 +00:00
};
2022-03-20 15:01:32 +00:00
export const getSizeFromOrigToEnc = (x: number) => {
if (x < 0 || Number.isNaN(x) || !Number.isInteger(x)) {
throw Error(`getSizeFromOrigToEnc: x=${x} is not a valid size`);
2022-03-20 15:01:32 +00:00
}
return (Math.floor(x / 16) + 1) * 16 + 16;
};
export const getSizeFromEncToOrig = (x: number) => {
if (x < 32 || Number.isNaN(x) || !Number.isInteger(x)) {
throw Error(`getSizeFromEncToOrig: ${x} is not a valid size`);
2022-03-20 15:01:32 +00:00
}
if (x % 16 !== 0) {
throw Error(
`getSizeFromEncToOrig: ${x} is not a valid encrypted file size`
);
2022-03-20 15:01:32 +00:00
}
return {
minSize: ((x - 16) / 16 - 1) * 16,
maxSize: ((x - 16) / 16 - 1) * 16 + 15,
};
};