k9crypt
Version:
A special encryption algorithm created for K9Crypt.
152 lines (128 loc) • 5.38 kB
JavaScript
const crypto = require('crypto');
const { compress, decompress } = require('./utils/compression');
const { deriveKey } = require('./utils/keyDerivation');
const { encrypt, decrypt } = require('./utils/encryption');
const { hash, verifyHash } = require('./utils/hashing');
const { encryptFile, decryptFile } = require('./utils/streamCrypto');
const { encryptMany, decryptMany, encryptManyParallel, decryptManyParallel } = require('./utils/batchOperations');
const { SALT_SIZE, IV_SIZE, TAG_SIZE, ARGON2_SALT_SIZE, ARGON2_HASH_LENGTH } = require('./constants');
class K9crypt {
constructor(secretKey, options = {}) {
if (!secretKey) {
this.secretKey = crypto.randomBytes(50);
this._autoGenerated = true;
}
if (secretKey) {
this._autoGenerated = false;
this.secretKey = secretKey;
}
this.defaultCompressionLevel = options.compressionLevel || 3;
}
getGenerated() {
return this._autoGenerated ? this.secretKey : null;
}
async encrypt(plaintext, options = {}) {
try {
const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
const compressed = await compress(plaintext, compressionLevel);
const salt = crypto.randomBytes(SALT_SIZE);
const key = await deriveKey(this.secretKey, salt);
const { iv1, iv2, iv3, iv4, iv5, encrypted, tag1 } = await encrypt(compressed, key);
const dataToHash = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1]);
const argon2Salt = crypto.randomBytes(ARGON2_SALT_SIZE);
const dataHash = await hash(dataToHash, argon2Salt);
const result = Buffer.concat([salt, iv1, iv2, iv3, iv4, iv5, encrypted, tag1, argon2Salt, dataHash]);
return result.toString('base64');
} catch (error) {
throw new Error(`Encryption failed: ${error.message}`);
}
}
async decrypt(ciphertext) {
try {
const data = Buffer.from(ciphertext, 'base64');
const salt = data.slice(0, SALT_SIZE);
const iv1 = data.slice(SALT_SIZE, SALT_SIZE + IV_SIZE);
const iv2 = data.slice(SALT_SIZE + IV_SIZE, SALT_SIZE + 2 * IV_SIZE);
const iv3 = data.slice(SALT_SIZE + 2 * IV_SIZE, SALT_SIZE + 3 * IV_SIZE);
const iv4 = data.slice(SALT_SIZE + 3 * IV_SIZE, SALT_SIZE + 4 * IV_SIZE);
const iv5 = data.slice(SALT_SIZE + 4 * IV_SIZE, SALT_SIZE + 5 * IV_SIZE);
const dataHash = data.slice(-ARGON2_HASH_LENGTH);
const argon2Salt = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE, -ARGON2_HASH_LENGTH);
const tag1 = data.slice(-ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
const encrypted = data.slice(SALT_SIZE + 5 * IV_SIZE, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE - TAG_SIZE);
const dataToVerify = data.slice(0, -ARGON2_HASH_LENGTH - ARGON2_SALT_SIZE);
if (!(await verifyHash(dataToVerify, dataHash, argon2Salt))) {
throw new Error('Data integrity check failed');
}
const key = await deriveKey(this.secretKey, salt);
const decrypted = await decrypt(encrypted, key, iv1, iv2, iv3, iv4, iv5, tag1);
const decompressed = await decompress(decrypted);
return decompressed.toString('utf8');
} catch (error) {
throw new Error(`Decryption failed: ${error.message}`);
}
}
async encryptFile(plaintext, options = {}) {
try {
const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
const onProgress = options.onProgress || null;
return await encryptFile(plaintext, this.secretKey, {
compressionLevel,
onProgress
});
} catch (error) {
throw new Error(`File encryption failed: ${error.message}`);
}
}
async decryptFile(ciphertext, options = {}) {
try {
const onProgress = options.onProgress || null;
return await decryptFile(ciphertext, this.secretKey, {
onProgress
});
} catch (error) {
throw new Error(`File decryption failed: ${error.message}`);
}
}
async encryptMany(dataArray, options = {}) {
try {
const compressionLevel = options.compressionLevel || this.defaultCompressionLevel;
const onProgress = options.onProgress || null;
const parallel = options.parallel || false;
const batchSize = options.batchSize || 10;
if (parallel) {
return await encryptManyParallel(dataArray, this.secretKey, {
compressionLevel,
batchSize
});
}
return await encryptMany(dataArray, this.secretKey, {
compressionLevel,
onProgress
});
} catch (error) {
throw new Error(`Multiple encryption failed: ${error.message}`);
}
}
async decryptMany(ciphertextArray, options = {}) {
try {
const onProgress = options.onProgress || null;
const skipInvalid = options.skipInvalid || false;
const parallel = options.parallel || false;
const batchSize = options.batchSize || 10;
if (parallel) {
return await decryptManyParallel(ciphertextArray, this.secretKey, {
skipInvalid,
batchSize
});
}
return await decryptMany(ciphertextArray, this.secretKey, {
skipInvalid,
onProgress
});
} catch (error) {
throw new Error(`Multiple decryption failed: ${error.message}`);
}
}
}
module.exports = K9crypt;