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
JavaScript
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
};