libsodium.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import { CustomError } from "@ente/shared/error";
  2. import sodium, { StateAddress } from "libsodium-wrappers";
  3. import { ENCRYPTION_CHUNK_SIZE } from "../constants";
  4. import { B64EncryptionResult } from "../types";
  5. export async function decryptChaChaOneShot(
  6. data: Uint8Array,
  7. header: Uint8Array,
  8. key: string,
  9. ) {
  10. await sodium.ready;
  11. const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
  12. header,
  13. await fromB64(key),
  14. );
  15. const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
  16. pullState,
  17. data,
  18. null,
  19. );
  20. return pullResult.message;
  21. }
  22. export async function decryptChaCha(
  23. data: Uint8Array,
  24. header: Uint8Array,
  25. key: string,
  26. ) {
  27. await sodium.ready;
  28. const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
  29. header,
  30. await fromB64(key),
  31. );
  32. const decryptionChunkSize =
  33. ENCRYPTION_CHUNK_SIZE +
  34. sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
  35. let bytesRead = 0;
  36. const decryptedData = [];
  37. let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
  38. while (tag !== sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
  39. let chunkSize = decryptionChunkSize;
  40. if (bytesRead + chunkSize > data.length) {
  41. chunkSize = data.length - bytesRead;
  42. }
  43. const buffer = data.slice(bytesRead, bytesRead + chunkSize);
  44. const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
  45. pullState,
  46. buffer,
  47. );
  48. if (!pullResult.message) {
  49. throw new Error(CustomError.PROCESSING_FAILED);
  50. }
  51. for (let index = 0; index < pullResult.message.length; index++) {
  52. decryptedData.push(pullResult.message[index]);
  53. }
  54. tag = pullResult.tag;
  55. bytesRead += chunkSize;
  56. }
  57. return Uint8Array.from(decryptedData);
  58. }
  59. export async function initChunkDecryption(header: Uint8Array, key: Uint8Array) {
  60. await sodium.ready;
  61. const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(
  62. header,
  63. key,
  64. );
  65. const decryptionChunkSize =
  66. ENCRYPTION_CHUNK_SIZE +
  67. sodium.crypto_secretstream_xchacha20poly1305_ABYTES;
  68. const tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
  69. return { pullState, decryptionChunkSize, tag };
  70. }
  71. export async function decryptFileChunk(
  72. data: Uint8Array,
  73. pullState: StateAddress,
  74. ) {
  75. await sodium.ready;
  76. const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
  77. pullState,
  78. data,
  79. );
  80. if (!pullResult.message) {
  81. throw new Error(CustomError.PROCESSING_FAILED);
  82. }
  83. const newTag = pullResult.tag;
  84. return { decryptedData: pullResult.message, newTag };
  85. }
  86. export async function encryptChaChaOneShot(data: Uint8Array, key: string) {
  87. await sodium.ready;
  88. const uintkey: Uint8Array = await fromB64(key);
  89. const initPushResult =
  90. sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
  91. const [pushState, header] = [initPushResult.state, initPushResult.header];
  92. const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
  93. pushState,
  94. data,
  95. null,
  96. sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL,
  97. );
  98. return {
  99. key: await toB64(uintkey),
  100. file: {
  101. encryptedData: pushResult,
  102. decryptionHeader: await toB64(header),
  103. },
  104. };
  105. }
  106. export async function encryptChaCha(data: Uint8Array) {
  107. await sodium.ready;
  108. const uintkey: Uint8Array =
  109. sodium.crypto_secretstream_xchacha20poly1305_keygen();
  110. const initPushResult =
  111. sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
  112. const [pushState, header] = [initPushResult.state, initPushResult.header];
  113. let bytesRead = 0;
  114. let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
  115. const encryptedData = [];
  116. while (tag !== sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
  117. let chunkSize = ENCRYPTION_CHUNK_SIZE;
  118. if (bytesRead + chunkSize >= data.length) {
  119. chunkSize = data.length - bytesRead;
  120. tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL;
  121. }
  122. const buffer = data.slice(bytesRead, bytesRead + chunkSize);
  123. bytesRead += chunkSize;
  124. const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
  125. pushState,
  126. buffer,
  127. null,
  128. tag,
  129. );
  130. for (let index = 0; index < pushResult.length; index++) {
  131. encryptedData.push(pushResult[index]);
  132. }
  133. }
  134. return {
  135. key: await toB64(uintkey),
  136. file: {
  137. encryptedData: new Uint8Array(encryptedData),
  138. decryptionHeader: await toB64(header),
  139. },
  140. };
  141. }
  142. export async function initChunkEncryption() {
  143. await sodium.ready;
  144. const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
  145. const initPushResult =
  146. sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
  147. const [pushState, header] = [initPushResult.state, initPushResult.header];
  148. return {
  149. key: await toB64(key),
  150. decryptionHeader: await toB64(header),
  151. pushState,
  152. };
  153. }
  154. export async function encryptFileChunk(
  155. data: Uint8Array,
  156. pushState: sodium.StateAddress,
  157. isFinalChunk: boolean,
  158. ) {
  159. await sodium.ready;
  160. const tag = isFinalChunk
  161. ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
  162. : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
  163. const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
  164. pushState,
  165. data,
  166. null,
  167. tag,
  168. );
  169. return pushResult;
  170. }
  171. export async function encryptToB64(data: string, key: string) {
  172. await sodium.ready;
  173. const encrypted = await encrypt(await fromB64(data), await fromB64(key));
  174. return {
  175. encryptedData: await toB64(encrypted.encryptedData),
  176. key: await toB64(encrypted.key),
  177. nonce: await toB64(encrypted.nonce),
  178. } as B64EncryptionResult;
  179. }
  180. export async function generateKeyAndEncryptToB64(data: string) {
  181. await sodium.ready;
  182. const key = sodium.crypto_secretbox_keygen();
  183. return await encryptToB64(data, await toB64(key));
  184. }
  185. export async function encryptUTF8(data: string, key: string) {
  186. const b64Data = await toB64(await fromUTF8(data));
  187. return await encryptToB64(b64Data, key);
  188. }
  189. export async function decryptB64(data: string, nonce: string, key: string) {
  190. await sodium.ready;
  191. const decrypted = await decrypt(
  192. await fromB64(data),
  193. await fromB64(nonce),
  194. await fromB64(key),
  195. );
  196. return await toB64(decrypted);
  197. }
  198. export async function decryptToUTF8(data: string, nonce: string, key: string) {
  199. await sodium.ready;
  200. const decrypted = await decrypt(
  201. await fromB64(data),
  202. await fromB64(nonce),
  203. await fromB64(key),
  204. );
  205. return sodium.to_string(decrypted);
  206. }
  207. async function encrypt(data: Uint8Array, key: Uint8Array) {
  208. await sodium.ready;
  209. const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
  210. const encryptedData = sodium.crypto_secretbox_easy(data, nonce, key);
  211. return {
  212. encryptedData,
  213. key,
  214. nonce,
  215. };
  216. }
  217. async function decrypt(data: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
  218. await sodium.ready;
  219. return sodium.crypto_secretbox_open_easy(data, nonce, key);
  220. }
  221. export async function initChunkHashing() {
  222. await sodium.ready;
  223. const hashState = sodium.crypto_generichash_init(
  224. null,
  225. sodium.crypto_generichash_BYTES_MAX,
  226. );
  227. return hashState;
  228. }
  229. export async function hashFileChunk(
  230. hashState: sodium.StateAddress,
  231. chunk: Uint8Array,
  232. ) {
  233. await sodium.ready;
  234. sodium.crypto_generichash_update(hashState, chunk);
  235. }
  236. export async function completeChunkHashing(hashState: sodium.StateAddress) {
  237. await sodium.ready;
  238. const hash = sodium.crypto_generichash_final(
  239. hashState,
  240. sodium.crypto_generichash_BYTES_MAX,
  241. );
  242. const hashString = toB64(hash);
  243. return hashString;
  244. }
  245. export async function deriveKey(
  246. passphrase: string,
  247. salt: string,
  248. opsLimit: number,
  249. memLimit: number,
  250. ) {
  251. await sodium.ready;
  252. return await toB64(
  253. sodium.crypto_pwhash(
  254. sodium.crypto_secretbox_KEYBYTES,
  255. await fromUTF8(passphrase),
  256. await fromB64(salt),
  257. opsLimit,
  258. memLimit,
  259. sodium.crypto_pwhash_ALG_ARGON2ID13,
  260. ),
  261. );
  262. }
  263. export async function deriveSensitiveKey(passphrase: string, salt: string) {
  264. await sodium.ready;
  265. const minMemLimit = sodium.crypto_pwhash_MEMLIMIT_MIN;
  266. let opsLimit = sodium.crypto_pwhash_OPSLIMIT_SENSITIVE;
  267. let memLimit = sodium.crypto_pwhash_MEMLIMIT_SENSITIVE;
  268. while (memLimit > minMemLimit) {
  269. try {
  270. const key = await deriveKey(passphrase, salt, opsLimit, memLimit);
  271. return {
  272. key,
  273. opsLimit,
  274. memLimit,
  275. };
  276. } catch (e) {
  277. opsLimit *= 2;
  278. memLimit /= 2;
  279. }
  280. }
  281. }
  282. export async function deriveInteractiveKey(passphrase: string, salt: string) {
  283. await sodium.ready;
  284. const key = await toB64(
  285. sodium.crypto_pwhash(
  286. sodium.crypto_secretbox_KEYBYTES,
  287. await fromUTF8(passphrase),
  288. await fromB64(salt),
  289. sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
  290. sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
  291. sodium.crypto_pwhash_ALG_ARGON2ID13,
  292. ),
  293. );
  294. return {
  295. key,
  296. opsLimit: sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
  297. memLimit: sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
  298. };
  299. }
  300. export async function generateEncryptionKey() {
  301. await sodium.ready;
  302. return await toB64(sodium.crypto_kdf_keygen());
  303. }
  304. export async function generateSaltToDeriveKey() {
  305. await sodium.ready;
  306. return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
  307. }
  308. export async function generateKeyPair() {
  309. await sodium.ready;
  310. const keyPair: sodium.KeyPair = sodium.crypto_box_keypair();
  311. return {
  312. privateKey: await toB64(keyPair.privateKey),
  313. publicKey: await toB64(keyPair.publicKey),
  314. };
  315. }
  316. export async function boxSealOpen(
  317. input: string,
  318. publicKey: string,
  319. secretKey: string,
  320. ) {
  321. await sodium.ready;
  322. return await toB64(
  323. sodium.crypto_box_seal_open(
  324. await fromB64(input),
  325. await fromB64(publicKey),
  326. await fromB64(secretKey),
  327. ),
  328. );
  329. }
  330. export async function boxSeal(input: string, publicKey: string) {
  331. await sodium.ready;
  332. return await toB64(
  333. sodium.crypto_box_seal(await fromB64(input), await fromB64(publicKey)),
  334. );
  335. }
  336. export async function generateSubKey(
  337. key: string,
  338. subKeyLength: number,
  339. subKeyID: number,
  340. context: string,
  341. ) {
  342. await sodium.ready;
  343. return await toB64(
  344. sodium.crypto_kdf_derive_from_key(
  345. subKeyLength,
  346. subKeyID,
  347. context,
  348. await fromB64(key),
  349. ),
  350. );
  351. }
  352. export async function fromB64(input: string) {
  353. await sodium.ready;
  354. return sodium.from_base64(input, sodium.base64_variants.ORIGINAL);
  355. }
  356. export async function toB64(input: Uint8Array) {
  357. await sodium.ready;
  358. return sodium.to_base64(input, sodium.base64_variants.ORIGINAL);
  359. }
  360. export async function toURLSafeB64(input: Uint8Array) {
  361. await sodium.ready;
  362. return sodium.to_base64(input, sodium.base64_variants.URLSAFE);
  363. }
  364. export async function fromUTF8(input: string) {
  365. await sodium.ready;
  366. return sodium.from_string(input);
  367. }
  368. export async function toUTF8(input: string) {
  369. await sodium.ready;
  370. return sodium.to_string(await fromB64(input));
  371. }
  372. export async function toHex(input: string) {
  373. await sodium.ready;
  374. return sodium.to_hex(await fromB64(input));
  375. }
  376. export async function fromHex(input: string) {
  377. await sodium.ready;
  378. return await toB64(sodium.from_hex(input));
  379. }