EonaCatCipher/Javascript/EonaCatCipher.js

269 lines
9.1 KiB
JavaScript
Raw Permalink Normal View History

2024-09-25 19:22:33 +02:00
class EonaCatCipher {
constructor(password, saltSize = 2048, ivSize = 2048, keySize = 2048, rounds = 2048, blockSize = 8192) {
if (!password) {
throw new Error("Password cannot be null or empty.");
}
this.ivSize = ivSize;
this.keySize = keySize;
this.rounds = rounds;
this.blockSize = blockSize;
// Derive encryption key and HMAC key
[this.derivedKey, this.hmacKey] = this.deriveKeyAndHMAC(password, saltSize);
}
/*
* EonaCatCipher - Because security is key!
*
* Copyright (c) 2024 EonaCat (Jeroen Saey)
*
* https://eonacat.com/license
*
* TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
* OF SOFTWARE BY EONACAT (JEROEN SAEY)
*
* This software is provided "as is", without any express or implied warranty.
* In no event shall the authors or copyright holders be liable for any claim,
* damages or other liability, whether in an action of contract, tort or otherwise,
* arising from, out of or in connection with the software or the use or other
* dealings in the software.
*
* You may use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* 1. The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 2. The software must not be used for any unlawful purpose.
*
* For any inquiries, please contact: eonacat@gmail.com
*/
static generateRandomBytes(size) {
const randomBytes = new Uint8Array(size);
crypto.getRandomValues(randomBytes);
return randomBytes;
}
deriveKeyAndHMAC(password, saltSize) {
const salt = EonaCatCipher.generateRandomBytes(saltSize);
const encryptionKey = this.pbkdf2(password, salt, this.keySize, this.rounds);
// Derive separate key for HMAC
const hmacKey = this.pbkdf2(password, salt, 32, this.rounds);
const keyWithSalt = new Uint8Array(saltSize + this.keySize);
keyWithSalt.set(salt, 0);
keyWithSalt.set(encryptionKey, saltSize);
return [keyWithSalt, hmacKey];
}
pbkdf2(password, salt, keyLength, iterations) {
const hmac = new Uint8Array(64); // HMAC length (SHA512)
const hashLength = hmac.length;
const blocksNeeded = Math.ceil(keyLength / hashLength);
const derivedKey = new Uint8Array(keyLength);
for (let blockIndex = 1; blockIndex <= blocksNeeded; blockIndex++) {
const currentBlock = new Uint8Array(salt.length + 4);
currentBlock.set(salt, 0);
currentBlock.set(new Uint8Array(new Uint32Array([blockIndex]).buffer), salt.length);
let u = this.hmacSha512(password, currentBlock);
const block = new Uint8Array(hashLength);
block.set(u);
// Step 4: Derived key starts with U1
derivedKey.set(u.subarray(0, Math.min(hashLength, keyLength)), (blockIndex - 1) * hashLength);
// Step 4: Iterations
for (let iteration = 1; iteration < iterations; iteration++) {
u = this.hmacSha512(password, u);
// Step 5: XOR U2 with previous result
for (let i = 0; i < hashLength; i++) {
block[i] ^= u[i];
}
// Step 6: Append result to derived key
derivedKey.set(block.subarray(0, Math.min(hashLength, keyLength)), (blockIndex - 1) * hashLength);
}
}
return derivedKey;
}
encrypt(plaintext) {
const iv = EonaCatCipher.generateRandomBytes(this.ivSize);
const plaintextBytes = new TextEncoder().encode(plaintext);
const ciphertext = new Uint8Array(plaintextBytes.length);
const cipher = new EonaCatCrypto(this.derivedKey, iv, this.blockSize, this.rounds);
cipher.generate(plaintextBytes, ciphertext, true);
// Combine IV and ciphertext
const result = new Uint8Array(this.ivSize + ciphertext.length);
result.set(iv, 0);
result.set(ciphertext, this.ivSize);
// Generate HMAC for integrity check
const hmac = this.generateHMAC(result);
// Combine result and HMAC
const finalResult = new Uint8Array(result.length + hmac.length);
finalResult.set(result, 0);
finalResult.set(hmac, result.length);
return finalResult;
}
decrypt(ciphertextWithHMAC) {
const hmacOffset = ciphertextWithHMAC.length - 32; // HMAC length (SHA256)
// Separate HMAC from the ciphertext
const providedHMAC = ciphertextWithHMAC.subarray(hmacOffset, ciphertextWithHMAC.length);
const ciphertext = ciphertextWithHMAC.subarray(0, hmacOffset);
// Verify HMAC before decrypting
const calculatedHMAC = this.generateHMAC(ciphertext);
if (!this.areEqual(providedHMAC, calculatedHMAC)) {
throw new Error("EonaCatCipher: HMAC validation failed. Data may have been tampered with.");
}
// Extract IV
const iv = ciphertext.subarray(0, this.ivSize);
// Extract encrypted data
const encryptedData = ciphertext.subarray(this.ivSize);
// Decrypt
const decryptedData = new Uint8Array(encryptedData.length);
const cipher = new EonaCatCrypto(this.derivedKey, iv, this.blockSize, this.rounds);
cipher.generate(encryptedData, decryptedData, false);
return new TextDecoder().decode(decryptedData);
}
generateHMAC(data) {
return this.hmacSha256(this.hmacKey, data);
}
hmacSha512(key, data) {
const hmac = new HMAC(key, "SHA-512");
return hmac.compute(data);
}
hmacSha256(key, data) {
const hmac = new HMAC(key, "SHA-256");
return hmac.compute(data);
}
areEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
}
class EonaCatCrypto {
constructor(keyWithSalt, nonce, blockSize, rounds) {
this.rounds = rounds;
this.blockSize = blockSize > 0 ? blockSize : 128;
this.key = new Uint32Array(keyWithSalt.length / 4);
this.key.set(new Uint32Array(keyWithSalt.buffer));
this.nonce = new Uint32Array(nonce.length / 4);
this.nonce.set(new Uint32Array(nonce.buffer));
this.state = new BigUint64Array(this.blockSize / 4);
this.blockCounter = 0;
}
generateBlock(output) {
// Initialize state using a combined operation
for (let i = 0; i < this.state.length; i++) {
this.state[i] = (this.key[i % this.key.length] ^ this.nonce[i % this.nonce.length]) + BigInt(i) * BigInt(0x5DEECE66D);
}
// Mix the states according to the rounds
for (let round = 0; round < this.rounds; round++) {
for (let i = 0; i < this.state.length; i++) {
this.state[i] = (this.state[i] + BigInt(round)) ^ (BigInt(round) * BigInt(0x5DEECE66D)) + BigInt(i + this.blockCounter);
}
}
// Output block
output.set(new Uint8Array(this.state.buffer));
this.blockCounter++;
}
generate(input, output, encrypt) {
const totalBlocks = Math.ceil(input.length / this.blockSize);
for (let blockIndex = 0; blockIndex < totalBlocks; blockIndex++) {
const inputOffset = blockIndex * this.blockSize;
const outputOffset = blockIndex * this.blockSize;
const block = new Uint8Array(this.blockSize);
// Generate a block based on the input
this.generateBlock(block);
// Perform XOR for encryption or decryption
for (let i = 0; i < block.length && inputOffset + i < input.length; i++) {
output[outputOffset + i] = input[inputOffset + i] ^ block[i];
}
}
}
}
class HMAC {
constructor(key, algorithm) {
this.key = key;
this.algorithm = algorithm;
}
compute(data) {
const cryptoKey = crypto.subtle.importKey(
"raw",
this.key,
{ name: "HMAC", hash: { name: this.algorithm } },
false,
["sign", "verify"]
);
return crypto.subtle.sign("HMAC", cryptoKey, data).then(signature => new Uint8Array(signature));
}
}
// Usage Example
(async () => {
const password = "securePassword123!@#$";
const plaintext = "Thank you for using EonaCatCipher!";
console.log(`Encrypting '${plaintext}' with password '${password}' (we do this 5 times)`);
console.log("================");
for (let i = 0; i < 5; i++) {
console.log(`Encryption round ${i + 1}: `);
console.log("================");
const cipher = new EonaCatCipher(password);
const encrypted = await cipher.encrypt(plaintext);
console.log("Encrypted (byte array): " + Array.from(encrypted).join(', '));
const decrypted = await cipher.decrypt(encrypted);
console.log("Decrypted: " + decrypted);
console.log("================");
}
})();