Crypto.js

Back
  1. if(typeof globalThis.Buffer === "undefined") { globalThis.Buffer = require("buffer/").Buffer; }
  2. const bs58 = require("bs58");
  3. const Stream = require("readable-stream");
  4. const Utils = require("./Utils");
  5. if(!globalThis.process) {
  6. globalThis.process = require("process/browser");
  7. }
  8. if(typeof crypto === "undefined") {
  9. const crypto = require("crypto");
  10. crypto.getRandomValues = arr => crypto.randomBytes(arr.length);
  11. globalThis.crypto = crypto;
  12. }
  13. /**
  14. * @namespace
  15. * @description This namespace contains cryptographic helper methods to encrypt and decrypt
  16. * data with automatic handling of keys
  17. */
  18. const Crypto = {
  19. ElvCrypto: async () => {
  20. try {
  21. if(!Crypto.elvCrypto) {
  22. const ElvCrypto = (await import("@eluvio/crypto")).default;
  23. Crypto.elvCrypto = await new ElvCrypto().init();
  24. }
  25. return Crypto.elvCrypto;
  26. } catch(error) {
  27. // eslint-disable-next-line no-console
  28. console.error("Error initializing ElvCrypto:");
  29. // eslint-disable-next-line no-console
  30. console.error(error);
  31. }
  32. },
  33. EncryptedSize: (clearSize) => {
  34. const clearBlockSize = 1000000;
  35. const blocks = Math.floor(clearSize / clearBlockSize);
  36. let encryptedBlockSize = Crypto.EncryptedBlockSize(clearBlockSize);
  37. let encryptedFileSize = blocks * encryptedBlockSize;
  38. if(clearSize % clearBlockSize !== 0) {
  39. encryptedFileSize += Crypto.EncryptedBlockSize(clearSize % clearBlockSize);
  40. }
  41. return encryptedFileSize;
  42. },
  43. EncryptedBlockSize: (clearSize, reencrypt=false) => {
  44. const primaryEncBlockOverhead = 129;
  45. const targetEncBlockOverhead = 608;
  46. const MODBYTES_384_58 = 48;
  47. const clearElementByteSize = 12 * (MODBYTES_384_58 - 1);
  48. const encElementByteSize = 12 * MODBYTES_384_58;
  49. let encryptedBlockSize = Math.floor((clearSize / clearElementByteSize)) * encElementByteSize;
  50. if(clearSize % clearElementByteSize !== 0) {
  51. encryptedBlockSize += encElementByteSize;
  52. }
  53. return reencrypt ? encryptedBlockSize + targetEncBlockOverhead : encryptedBlockSize + primaryEncBlockOverhead;
  54. },
  55. async EncryptConk(conk, publicKey) {
  56. const elvCrypto = await Crypto.ElvCrypto();
  57. publicKey = new Uint8Array(Buffer.from(publicKey.replace("0x", ""), "hex"));
  58. conk = new Uint8Array(Buffer.from(JSON.stringify(conk)));
  59. const {data, ephemeralKey, tag} = await elvCrypto.encryptECIES(conk, publicKey);
  60. const cap = Buffer.concat([
  61. Buffer.from(ephemeralKey),
  62. Buffer.from(tag),
  63. Buffer.from(data)
  64. ]);
  65. return Utils.B64(cap);
  66. },
  67. async DecryptCap(encryptedCap, privateKey) {
  68. const elvCrypto = await Crypto.ElvCrypto();
  69. privateKey = new Uint8Array(Buffer.from(privateKey.replace("0x", ""), "hex"));
  70. encryptedCap = Buffer.from(encryptedCap, "base64");
  71. const ephemeralKey = encryptedCap.slice(0, 65);
  72. const tag = encryptedCap.slice(65, 81);
  73. const data = encryptedCap.slice(81);
  74. const cap = elvCrypto.decryptECIES(
  75. new Uint8Array(data),
  76. privateKey,
  77. new Uint8Array(ephemeralKey),
  78. new Uint8Array(tag)
  79. );
  80. return JSON.parse(Buffer.from(cap).toString());
  81. },
  82. async GeneratePrimaryConk({spaceId, objectId}) {
  83. const elvCrypto = await Crypto.ElvCrypto();
  84. const {secretKey, publicKey} = elvCrypto.generatePrimaryKeys();
  85. const symmetricKey = (elvCrypto.generateSymmetricKey()).key;
  86. return {
  87. symm_key: `kpsy${bs58.encode(Buffer.from(symmetricKey))}`,
  88. secret_key: `kpsk${bs58.encode(Buffer.from(secretKey))}`,
  89. public_key: `kppk${bs58.encode(Buffer.from(publicKey))}`,
  90. sid: spaceId,
  91. qid: objectId
  92. };
  93. },
  94. async GenerateTargetConk() {
  95. const elvCrypto = await Crypto.ElvCrypto();
  96. const {secretKey, publicKey} = elvCrypto.generateTargetKeys();
  97. return {
  98. secret_key: `kpsk${bs58.encode(Buffer.from(secretKey))}`,
  99. public_key: `ktpk${bs58.encode(Buffer.from(publicKey))}`
  100. };
  101. },
  102. CapToConk(cap) {
  103. const keyToBytes = key => new Uint8Array(bs58.decode(key.slice(4)));
  104. return {
  105. symmetricKey: keyToBytes(cap.symm_key),
  106. secretKey: keyToBytes(cap.secret_key),
  107. publicKey: keyToBytes(cap.public_key)
  108. };
  109. },
  110. async EncryptionContext(cap) {
  111. const elvCrypto = await Crypto.ElvCrypto();
  112. const {symmetricKey, secretKey, publicKey} = Crypto.CapToConk(cap);
  113. let context, type;
  114. if(publicKey.length === elvCrypto.PRIMARY_PK_KEY_SIZE) {
  115. // Primary context
  116. type = elvCrypto.CRYPTO_TYPE_PRIMARY;
  117. context = elvCrypto.newPrimaryContext(
  118. publicKey,
  119. secretKey,
  120. symmetricKey
  121. );
  122. } else {
  123. // Target context
  124. type = elvCrypto.CRYPTO_TYPE_TARGET;
  125. context = elvCrypto.newTargetDecryptionContext(
  126. secretKey,
  127. symmetricKey
  128. );
  129. }
  130. return {context, type};
  131. },
  132. /**
  133. * Encrypt data with headers
  134. *
  135. * @namedParams
  136. * @param {Object} cap - Encryption "capsule" containing keys
  137. * @param {ArrayBuffer | Buffer} data - Data to encrypt
  138. *
  139. * @returns {Promise<Buffer>} - Decrypted data
  140. */
  141. Encrypt: async (cap, data) => {
  142. const stream = await Crypto.OpenEncryptionStream(cap);
  143. // Convert Blob to ArrayBuffer if necessary
  144. if(!Buffer.isBuffer(data) && !(data instanceof ArrayBuffer)) {
  145. data = Buffer.from(await new Response(data).arrayBuffer());
  146. }
  147. const dataArray = new Uint8Array(data);
  148. for(let i = 0; i < dataArray.length; i += 1000000) {
  149. const end = Math.min(dataArray.length, i + 1000000);
  150. stream.write(dataArray.slice(i, end));
  151. }
  152. stream.end();
  153. let encryptedChunks = [];
  154. await new Promise((resolve, reject) => {
  155. stream
  156. .on("data", chunk => {
  157. encryptedChunks.push(chunk);
  158. })
  159. .on("finish", () => {
  160. resolve();
  161. })
  162. .on("error", (e) => {
  163. reject(e);
  164. });
  165. });
  166. return Buffer.concat(encryptedChunks);
  167. },
  168. OpenEncryptionStream: async (cap) => {
  169. const elvCrypto = await Crypto.ElvCrypto();
  170. const {context} = await Crypto.EncryptionContext(cap);
  171. const stream = new Stream.PassThrough();
  172. const cipher = elvCrypto.createCipher(context);
  173. return stream
  174. .pipe(cipher)
  175. .on("finish", () => {
  176. context.free();
  177. })
  178. .on("error", (e) => {
  179. throw Error(e);
  180. });
  181. },
  182. /**
  183. * Decrypt data with headers
  184. *
  185. * @namedParams
  186. * @param {Object} cap - Encryption "capsule" containing keys
  187. * @param {ArrayBuffer | Buffer} encryptedData - Data to decrypt
  188. *
  189. * @returns {Promise<Buffer>} - Decrypted data
  190. */
  191. Decrypt: async (cap, encryptedData) => {
  192. const stream = await Crypto.OpenDecryptionStream(cap);
  193. const dataArray = new Uint8Array(encryptedData);
  194. for(let i = 0; i < dataArray.length; i += 1000000) {
  195. const end = Math.min(dataArray.length, i + 1000000);
  196. stream.write(dataArray.slice(i, end));
  197. }
  198. stream.end();
  199. let decryptedChunks = [];
  200. await new Promise((resolve, reject) => {
  201. stream
  202. .on("data", chunk => {
  203. decryptedChunks.push(chunk);
  204. })
  205. .on("finish", () => {
  206. resolve();
  207. })
  208. .on("error", (e) => {
  209. reject(e);
  210. });
  211. });
  212. return Buffer.concat(decryptedChunks);
  213. },
  214. OpenDecryptionStream: async (cap) => {
  215. const elvCrypto = await Crypto.ElvCrypto();
  216. const {context, type} = await Crypto.EncryptionContext(cap);
  217. const stream = new Stream.PassThrough();
  218. const decipher = elvCrypto.createDecipher(type, context);
  219. return stream
  220. .pipe(decipher)
  221. .on("finish", () => {
  222. context.free();
  223. })
  224. .on("error", (e) => {
  225. throw Error(e);
  226. });
  227. }
  228. };
  229. module.exports = Crypto;