UNPKG

symmetricmorph

Version:

High-performance symmetric stream cipher with dynamic masking, cascading feedback, built-in MAC, and chunked encryption support. Lightweight and dependency-free.

246 lines (245 loc) 8.97 kB
class y { constructor(t, e) { this.key = t, this.salt = e; } /** * @returns Returns the salt used for key derivation, if available. */ getSalt() { return this.salt; } /** * Creates a `SymmetricMorph` instance from a password. * @param password - The password to derive the encryption key. * @param iterations - Number of iterations for key derivation (default: 20000). * @param keyLength - Length of the derived key in bytes (default: 64). * @returns A new `SymmetricMorph` instance. */ static fromPassword(t, e = 2e4, c = 64) { const n = this.generateSalt(24), s = this.deriveKey(this.strToBytes(t), n, e, c); return new y(s, n); } /** * Creates a `SymmetricMorph` instance from a password and salt. * @param password - The password to derive the encryption key. * @param salt - The salt as an array of bytes or a number array. * @param iterations - Number of iterations for key derivation (default: 20000). * @param keyLength - Length of the derived key in bytes (default: 64). * @returns A new `SymmetricMorph` instance. */ static fromPasswordWithSalt(t, e, c = 2e4, n = 64) { const s = e instanceof Uint8Array ? e : Uint8Array.from(e), o = this.deriveKey(this.strToBytes(t), s, c, n); return new y(o, s); } /** * Creates a `SymmetricMorph` instance from a raw key. * @param key - The encryption key as an array of bytes. * @returns A new `SymmetricMorph` instance. */ static fromKey(t) { return new y(t); } /** * Generates a random encryption key of the specified length. * @param length - Length of the key in bytes (default: 64). * @returns A randomly generated key as an array of bytes. */ static generateKey(t = 64) { const e = this.createPrng([Date.now() & 255]); return Uint8Array.from({ length: t }, (c, n) => e()); } /** * Encrypts an array of plaintext bytes. * @param plainBytes - The plaintext data as an array of bytes. * @returns The encrypted data as an array of bytes. */ encrypt(t) { const e = y.generateNonce(), c = y.createPrng([...this.key, ...e]), n = new Uint8Array(t.length), s = this.initState(); let o = 165, i = 108, a = 58, l = 145, h = 157; for (let r = 0; r < t.length; r++) { const u = c(), g = (r + o + i + a) % s.length, d = (s[g] ^ o ^ i ^ a ^ l) & 255, w = (t[r] ^ d ^ u) & 255, p = (w << r % 5 | w >> 8 - r % 5) & 255; n[r] = p, this.updateState(s, p, o, u, r, i, a, l), l = a, a = i, i = p, o = (o ^ p ^ u ^ r * 13) & 255, h = (h + p + o + r * 31 ^ u + i + a) & 255; } const f = y.generateMac(h, this.key); return Uint8Array.from([...e, ...f, ...n]); } /** * Decrypts an array of encrypted bytes. * @param encryptedBytes - The encrypted data as an array of bytes. * @returns The decrypted plaintext data as an array of bytes. * @throws Error if MAC verification fails. */ decrypt(t) { const e = t.slice(0, 8), c = t.slice(8, 40), n = t.slice(40), s = y.createPrng([...this.key, ...e]), o = this.initState(); let i = 165, a = 108, l = 58, h = 145, f = 157; const r = new Uint8Array(n.length); for (let g = 0; g < n.length; g++) { const d = s(), w = (g + i + a + l) % o.length, p = (o[w] ^ i ^ a ^ l ^ h) & 255, A = n[g], k = ((A >> g % 5 | A << 8 - g % 5) & 255 ^ p ^ d) & 255; r[g] = k, this.updateState(o, A, i, d, g, a, l, h), h = l, l = a, a = A, i = (i ^ A ^ d ^ g * 13) & 255, f = (f + A + i + g * 31 ^ d + a + l) & 255; } const u = y.generateMac(f, this.key); if (!y.constantTimeCompare(c, u)) throw new Error("MAC verification failed (integrity broken)"); return r; } /** * Encrypts multiple chunks of plaintext data. * @param chunks - An array of plaintext byte arrays. * @returns An array of encrypted byte arrays. */ encryptChunks(t) { return t.map((e) => this.encrypt(e)); } /** * Decrypts multiple chunks of encrypted data. * @param chunks - An array of encrypted byte arrays. * @returns An array of decrypted plaintext byte arrays. */ decryptChunks(t) { return t.map((e) => this.decrypt(e)); } /** * Updates the internal state during encryption or decryption. * @param state - The current state array. * @param rotated - The rotated byte value. * @param feedback - The feedback byte. * @param r - The random byte from the PRNG. * @param iteration - The current iteration index. * @param prev1 - The previous byte value (1 step back). * @param prev2 - The previous byte value (2 steps back). * @param prev3 - The previous byte value (3 steps back). */ updateState(t, e, c, n, s, o, i, a) { const l = t.length, h = ~e & 255, f = (o ^ i ^ a ^ c ^ n) & 255; for (let r = 0; r < l; r++) t[r] ^= e + f + r * 31 & 255, t[r] = (t[r] << r % 5 | t[r] >> 8 - r % 5) & 255; if (s % 4 === 3) { const r = [...t]; for (let u = 0; u < l; u++) { const g = (u * 13 + s) % l; t[u] = (r[g] ^ h) & 255; } } for (let r = l - 1; r >= 0; r--) t[r] = t[r] + (h ^ r * 17 ^ c) & 255, t[r] = (t[r] >>> r % 7 | t[r] << 8 - r % 7) & 255; } /** * Initializes the internal state array based on the encryption key. * @returns The initialized state array. */ initState() { const t = new Array(64).fill(0); for (let e = 0; e < t.length; e++) t[e] = (this.key[e % this.key.length] ^ e * 67 + 19) & 255; return t; } /** * Creates a pseudo-random number generator (PRNG) based on a seed. * @param seed - The seed array for the PRNG. * @returns A function that generates random bytes. */ static createPrng(t) { const e = new Array(64).fill(0).map((n, s) => t[s % t.length] ^ s * 97 & 255); let c = 0; return () => { const n = c % 64, s = (c * 5 + 11) % 64, o = (c * 7 + 17) % 64, i = e[n] + (e[s] ^ e[o] >>> 3) + c & 255; return e[n] = (e[n] ^ i << c % 7) & 255, c++, i; }; } /** * Derives a cryptographic key from a password and salt using iterative hashing. * @param pass - The password as an array of bytes. * @param salt - The salt as an array of bytes. * @param rounds - The number of hashing iterations. * @param length - The desired key length in bytes. * @returns The derived key as an array of bytes. */ static deriveKey(t, e, c, n) { let s = Array.from(t).concat(Array.from(e)), o = 0; for (let a = 0; a < c; a++) { const l = s.map((h, f) => (h ^ o + f * 7 + a) % 256); o = (o + l.reduce((h, f) => h ^ f, 0)) % 256, s = l; } const i = new Uint8Array(n); for (let a = 0; a < n; a++) i[a] = s[a % s.length] ^ (a * 37 + o) % 256; return i; } /** * Generates a Message Authentication Code (MAC) for data integrity verification. * @param data - The data to generate the MAC for. * @param key - The encryption key. * @returns The MAC as an array of bytes. */ static mac(t, e) { const c = new Uint8Array(32); let n = 137; for (let s = 0; s < 32; s++) { const o = t[s % t.length], i = e[s % e.length]; n = (n + o + i * (s + 1)) % 256, c[s] = n ^ (s * 11 + o) % 256; } return c; } /** * Generates a MAC based on accumulated MAC state and the encryption key. * @param macAcc - The accumulated MAC state. * @param key - The encryption key. * @returns The generated MAC as an array of bytes. */ static generateMac(t, e) { const c = new Uint8Array(32); for (let n = 0; n < 32; n++) c[n] = (t ^ e[n % e.length] ^ n * 19) & 255; return c; } /** * Generates a random salt for key derivation. * @param length - The length of the salt in bytes. * @returns The generated salt as an array of bytes. */ static generateSalt(t) { const e = performance.now() | 0, c = [e & 255, e >> 8 & 255, e >> 16 & 255], n = this.createPrng(c); return Uint8Array.from({ length: t }, (s, o) => n()); } /** * Generates a random nonce for encryption. * @returns The generated nonce as an array of bytes. */ static generateNonce() { const t = Date.now(); return Uint8Array.from([ t >> 0 & 255, t >> 8 & 255, t >> 16 & 255, t >> 24 & 255, t % 251, t % 241, t % 239, t % 233 ]); } /** * Converts a string to an array of bytes. * @param str - The input string. * @returns The string as an array of bytes. */ static strToBytes(t) { return Array.from(t).map((e) => e.charCodeAt(0)); } /** * Compares two byte arrays in constant time to prevent timing attacks. * @param a - The first byte array. * @param b - The second byte array. * @returns `true` if the arrays are equal, otherwise `false`. */ static constantTimeCompare(t, e) { if (t.length !== e.length) return !1; let c = 0; for (let n = 0; n < t.length; n++) c |= t[n] ^ e[n]; return c === 0; } } export { y as default };