import { cipherKey, CryptoEngine } from '@cyklang/core'; import * as CryptoJS from 'crypto-js'; import pako from 'pako'; const IV_LENGTH = 16; // bytes pour AES‑CBC export class JSCrypto implements CryptoEngine { async encrypt(plain: string): Promise { // 1. Compression const compressed = pako.deflate(plain); // 2. Génération d’un IV aléatoire (16 octets) const iv = CryptoJS.lib.WordArray.random(IV_LENGTH); // 3. Préparation de la clé (suppose que cipherKey est une chaîne UTF‑8 de 16 octets) const key = CryptoJS.enc.Utf8.parse(cipherKey); // 4. Chiffrement AES‑CBC avec padding PKCS7 const encrypted = CryptoJS.AES.encrypt( CryptoJS.lib.WordArray.create(compressed), // données compressées key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); // 5. Récupération du texte chiffré (hex) et de l’IV (hex) const ciphertextHex = encrypted.ciphertext.toString(CryptoJS.enc.Hex); const ivHex = iv.toString(CryptoJS.enc.Hex); // 6. Assemblage : ciphertext + iv return ciphertextHex + ivHex; } async decrypt(encoded: string): Promise { // 1. Extraction de l’IV et du ciphertext const ivHex = encoded.slice(-2 * IV_LENGTH); const ciphertextHex = encoded.slice(0, -2 * IV_LENGTH); const iv = CryptoJS.enc.Hex.parse(ivHex); const key = CryptoJS.enc.Utf8.parse(cipherKey); // 2. Déchiffrement const decrypted = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Hex.parse(ciphertextHex) } as any, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); // 3. Conversion du résultat (WordArray) en Uint8Array const decryptedBytes = this.wordArrayToUint8Array(decrypted); // 4. Décompression (si nécessaire) let inflated = decryptedBytes; if (decryptedBytes[0] === 0x78 && decryptedBytes[1] === 0x9c) { inflated = pako.inflate(decryptedBytes); } // 5. Décodage UTF‑8 const decoder = new TextDecoder(); return decoder.decode(inflated); } // utilitaire : conversion d’un WordArray crypto‑js vers Uint8Array private wordArrayToUint8Array(wordArray: CryptoJS.lib.WordArray): Uint8Array { const bytes = new Uint8Array(wordArray.sigBytes); const words = wordArray.words; for (let i = 0; i < wordArray.sigBytes; i++) { bytes[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; } return bytes; } } // // class WebCrypto // class WebCrypto implements CryptoEngine { iv_length = 16 last_ciphertext: ArrayBuffer | undefined byteToHex: string[] = [] constructor() { for (let n = 0; n <= 0xff; ++n) { const hexOctet = n.toString(16).padStart(2, '0'); this.byteToHex.push(hexOctet); } } async getCryptoKey(): Promise { const encoder = new TextEncoder() const result = await window.crypto.subtle.importKey('raw', encoder.encode(cipherKey), 'AES-CBC', true, ['encrypt', 'decrypt']) return result } // convert to hex // https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex byteArrayToHexString(arrayBuffer: ArrayBuffer): string { const buff = new Uint8Array(arrayBuffer); const hexOctets = []; // new Array(buff.length) is even faster (preallocates necessary array size), then use hexOctets[i] instead of .push() for (let i = 0; i < buff.length; ++i) hexOctets.push(this.byteToHex[buff[i]]); return hexOctets.join(''); } // http://www.java2s.com/example/nodejs/string/convert-hex-string-to-byte-array.html hexStringToByteArray(hexString: string): Uint8Array { if (hexString.length % 2 !== 0) { throw 'Must have an even number of hex digits to convert to bytes'; } const numBytes = hexString.length / 2; const byteArray = new Uint8Array(numBytes); for (let i = 0; i < numBytes; i++) { byteArray[i] = parseInt(hexString.substr(i * 2, 2), 16); } return byteArray; } // method encrypt async encrypt(plain: string): Promise { const iv = window.crypto.getRandomValues(new Uint8Array(this.iv_length)); const cryptoKey = await this.getCryptoKey() const encoder = new TextEncoder() const ciphertext = await window.crypto.subtle.encrypt( { name: 'AES-CBC', iv: iv }, cryptoKey, encoder.encode(plain) ); let result = this.byteArrayToHexString(ciphertext) result += this.byteArrayToHexString(iv.buffer) this.last_ciphertext = ciphertext return result } // method decrypt async decrypt(encoded: string): Promise { const iv_hex = encoded.substring(encoded.length - 2 * this.iv_length) const iv = new Uint8Array( this.hexStringToByteArray(iv_hex)) const encryptedData_hex = encoded.substring(0, encoded.length - 2 * this.iv_length) const encryptedData = new Uint8Array( this.hexStringToByteArray(encryptedData_hex)) const cryptoKey = await this.getCryptoKey() const deflatedData = await window.crypto.subtle.decrypt({ name: 'AES-CBC', iv: iv }, cryptoKey, encryptedData) let inflatedData: Uint8Array = new Uint8Array(deflatedData) if (inflatedData[0] === 0x78 && inflatedData[1] === 0x9c) { inflatedData = pako.inflate(deflatedData) } const decoder = new TextDecoder() const result = decoder.decode(inflatedData) return result } }