fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
1,020 lines (1,016 loc) • 37.7 kB
JavaScript
'use strict';
var randomCore = require('../core/random/random-core.js');
require('../core/random/random-types.js');
require('crypto');
require('../core/random/random-sources.js');
require('nehonix-uri-processor');
var encoding = require('../utils/encoding.js');
require('../utils/memory/index.js');
require('../types.js');
var hashCore = require('../core/hash/hash-core.js');
require('../core/hash/hash-types.js');
require('argon2');
require('../algorithms/hash-algorithms.js');
/**
* Post-Quantum Cryptography Module
*
* This module provides cryptographic primitives that are believed to be
* resistant to attacks by quantum computers. It implements versions
* of lattice-based, hash-based, and code-based cryptography.
*
* Where possible, it uses standardized libraries for implementations.
* Fallback simplified implementations are provided for educational purposes and
* environments where the libraries are not available.
*/
/**
* Implements a simplified Lamport one-time signature scheme
* This is a hash-based signature scheme resistant to quantum attacks
*
* @param message - Message to sign
* @param privateKey - Private key (hex encoded)
* @returns Signature (hex encoded)
*/
function lamportSign(message, privateKey) {
// Convert message to bytes if it's a string
const messageBytes = typeof message === "string"
? new TextEncoder().encode(message)
: message;
// Hash the message to get a fixed-length digest
const messageDigest = simpleHash(messageBytes, 32);
// Convert private key from hex to bytes
const privateKeyBytes = encoding.hexToBuffer(privateKey);
// Lamport private key consists of two random values for each bit
// For a 256-bit message digest, we need 512 values
if (privateKeyBytes.length !== 32 * 512) {
throw new Error("Invalid private key length");
}
// Generate signature by selecting the appropriate private key parts
const signature = new Uint8Array(32 * 256);
for (let i = 0; i < 32; i++) {
for (let bit = 0; bit < 8; bit++) {
const bitValue = (messageDigest[i] >> bit) & 1;
const privateKeyOffset = (i * 8 + bit) * 64 + bitValue * 32;
// Copy the corresponding private key part to the signature
for (let j = 0; j < 32; j++) {
signature[(i * 8 + bit) * 32 + j] =
privateKeyBytes[privateKeyOffset + j];
}
}
}
return encoding.bufferToHex(signature);
}
/**
* Verifies a Lamport signature
*
* @param message - Message that was signed
* @param signature - Signature to verify (hex encoded)
* @param publicKey - Public key (hex encoded)
* @returns True if the signature is valid, false otherwise
*/
function lamportVerify(message, signature, publicKey) {
// Convert message to bytes if it's a string
const messageBytes = typeof message === "string"
? new TextEncoder().encode(message)
: message;
// Hash the message to get a fixed-length digest
const messageDigest = simpleHash(messageBytes, 32);
// Convert signature and public key from hex to bytes
const signatureBytes = encoding.hexToBuffer(signature);
const publicKeyBytes = encoding.hexToBuffer(publicKey);
// Check lengths
if (signatureBytes.length !== 32 * 256) {
return false;
}
if (publicKeyBytes.length !== 32 * 512) {
return false;
}
// Verify each bit of the message digest
for (let i = 0; i < 32; i++) {
for (let bit = 0; bit < 8; bit++) {
const bitValue = (messageDigest[i] >> bit) & 1;
const publicKeyOffset = (i * 8 + bit) * 64 + bitValue * 32;
const signatureOffset = (i * 8 + bit) * 32;
// Hash the signature part
const hashedSignaturePart = simpleHash(signatureBytes.slice(signatureOffset, signatureOffset + 32), 32);
// Compare with the corresponding public key part
for (let j = 0; j < 32; j++) {
if (hashedSignaturePart[j] !==
publicKeyBytes[publicKeyOffset + j]) {
return false;
}
}
}
}
return true;
}
/**
* Generates a Lamport key pair
*
* @returns Public and private key pair (hex encoded)
*/
function lamportGenerateKeypair() {
// Generate 512 random values (two for each bit of a 256-bit message digest)
const privateKeyBytes = randomCore.SecureRandom.getRandomBytes(32 * 512);
const publicKeyBytes = new Uint8Array(32 * 512);
// Public key is the hash of each private key part
for (let i = 0; i < 512; i++) {
const privateKeyPart = privateKeyBytes.slice(i * 32, (i + 1) * 32);
const publicKeyPart = simpleHash(privateKeyPart, 32);
publicKeyBytes.set(publicKeyPart, i * 32);
}
return {
publicKey: encoding.bufferToHex(publicKeyBytes),
privateKey: encoding.bufferToHex(privateKeyBytes),
};
}
/**
* Implements a simplified Ring-LWE encryption scheme
* This is a lattice-based encryption scheme resistant to quantum attacks
*
* Note: This is a very simplified version for educational purposes
*
* @param message - Message to encrypt (must be 32 bytes or less)
* @param publicKey - Public key (hex encoded)
* @returns Encrypted message (hex encoded)
*/
function ringLweEncrypt(message, publicKey) {
// Convert message to bytes if it's a string
const messageBytes = typeof message === "string"
? new TextEncoder().encode(message)
: message;
if (messageBytes.length > 32) {
throw new Error("Message too long (maximum 32 bytes)");
}
// Pad message to 32 bytes
const paddedMessage = new Uint8Array(32);
paddedMessage.set(messageBytes);
// Convert public key from hex to bytes
const publicKeyBytes = encoding.hexToBuffer(publicKey);
if (publicKeyBytes.length !== 1024) {
throw new Error("Invalid public key length");
}
// Generate random error vector (simplified)
const error = randomCore.SecureRandom.getRandomBytes(32);
// Generate random polynomial a (simplified)
const a = randomCore.SecureRandom.getRandomBytes(1024);
// Compute b = a*s + e + m (simplified)
const b = new Uint8Array(1024);
for (let i = 0; i < 1024; i++) {
// Simplified polynomial multiplication
let sum = 0;
for (let j = 0; j < 32; j++) {
sum += a[(i + j) % 1024] * publicKeyBytes[j];
}
// Add error and message (for the first 32 coefficients)
if (i < 32) {
sum += error[i] * 4 + paddedMessage[i] * 128;
}
b[i] = sum & 0xff;
}
// Ciphertext is (a, b)
const ciphertext = new Uint8Array(2048);
ciphertext.set(a, 0);
ciphertext.set(b, 1024);
return encoding.bufferToHex(ciphertext);
}
/**
* Decrypts a message encrypted with Ring-LWE
*
* @param ciphertext - Encrypted message (hex encoded)
* @param privateKey - Private key (hex encoded)
* @returns Decrypted message
*/
function ringLweDecrypt(ciphertext, privateKey) {
// Convert ciphertext and private key from hex to bytes
const ciphertextBytes = encoding.hexToBuffer(ciphertext);
const privateKeyBytes = encoding.hexToBuffer(privateKey);
if (ciphertextBytes.length !== 2048) {
throw new Error("Invalid ciphertext length");
}
if (privateKeyBytes.length !== 32) {
throw new Error("Invalid private key length");
}
// Extract a and b from ciphertext
const a = ciphertextBytes.slice(0, 1024);
const b = ciphertextBytes.slice(1024, 2048);
// Compute m = b - a*s (simplified)
const decrypted = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
let sum = b[i];
// Simplified polynomial multiplication
for (let j = 0; j < 32; j++) {
sum -= a[(i + j) % 1024] * privateKeyBytes[j];
}
// Round to recover message
decrypted[i] = Math.round((sum & 0xff) / 128) & 0xff;
}
return decrypted;
}
/**
* Generates a Ring-LWE key pair
*
* @returns Public and private key pair (hex encoded)
*/
function ringLweGenerateKeypair() {
// Generate private key (small random polynomial s)
const privateKeyBytes = randomCore.SecureRandom.getRandomBytes(32);
// Generate random polynomial a
const a = randomCore.SecureRandom.getRandomBytes(1024);
// Generate small error vector e
const error = randomCore.SecureRandom.getRandomBytes(1024);
for (let i = 0; i < 1024; i++) {
error[i] = error[i] % 5; // Small error values
}
// Compute public key b = a*s + e
const publicKeyBytes = new Uint8Array(1024);
for (let i = 0; i < 1024; i++) {
let sum = error[i];
// Simplified polynomial multiplication
for (let j = 0; j < 32; j++) {
sum += a[(i + j) % 1024] * privateKeyBytes[j];
}
publicKeyBytes[i] = sum & 0xff;
}
return {
publicKey: encoding.bufferToHex(publicKeyBytes),
privateKey: encoding.bufferToHex(privateKeyBytes),
};
}
/**
* Implements the Kyber key encapsulation mechanism (KEM)
*
* Kyber is a lattice-based key encapsulation mechanism that is believed to be
* secure against quantum computer attacks.
*
* This implementation uses the crystals-kyber library, which provides a
* implementation of the Kyber algorithm.
*
* @param params - Parameters for the key generation
* @returns Key pair with public and private keys
*/
function generateKyberKeyPair(params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
try {
// Import the crystals-kyber library
const kyber = require("crystals-kyber");
// Map our security level to the Kyber variant
let kyberVariant;
switch (securityLevel) {
case 1:
kyberVariant = kyber.kyber512;
break;
case 3:
kyberVariant = kyber.kyber768;
break;
case 5:
kyberVariant = kyber.kyber1024;
break;
default:
kyberVariant = kyber.kyber768; // Default to Kyber-768 (security level 3)
}
// Generate a key pair using the library
const keyPair = kyberVariant.keypair();
// Extract the public and private keys
const publicKey = Buffer.from(keyPair.public_key);
const privateKey = Buffer.from(keyPair.private_key);
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
return {
publicKey: encoding.bufferToHex(publicKey),
privateKey: encoding.bufferToHex(privateKey),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
},
};
}
catch (error) {
console.warn("Error using crystals-kyber library:", error);
console.warn("Falling back to simplified Kyber implementation");
// Fallback to a simplified implementation
return generateKyberKeyPairFallback(params);
}
}
/**
* Fallback implementation of Kyber key generation
* Used when the crystals-kyber library is not available
*
* @param params - Parameters for the key generation
* @returns Key pair with public and private keys
*/
function generateKyberKeyPairFallback(params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
const eta1 = securityLevel === 1 ? 3 : 2; // Noise parameter for secret key
const eta2 = 2; // Noise parameter for error
// Generate a random seed for key generation
const seed = randomCore.SecureRandom.getRandomBytes(32);
// Use the seed to derive three seeds for different purposes
const seedA = hashCore.Hash.create(Buffer.concat([Buffer.from([0x00]), Buffer.from(seed)]), {
algorithm: "sha256",
outputFormat: "buffer",
});
const seedE = hashCore.Hash.create(Buffer.concat([Buffer.from([0x01]), Buffer.from(seed)]), {
algorithm: "sha256",
outputFormat: "buffer",
});
const seedS = hashCore.Hash.create(Buffer.concat([Buffer.from([0x02]), Buffer.from(seed)]), {
algorithm: "sha256",
outputFormat: "buffer",
});
// Generate the public matrix A using seedA
const A = generateMatrix(seedA, k, k, n, q);
// Generate the secret vector s with small coefficients using seedS
const s = generateNoiseVector(seedS, k, n, eta1, q);
// Generate the error vector e with small coefficients using seedE
const e = generateNoiseVector(seedE, k, n, eta2, q);
// Compute the public key t = A·s + e
const t = new Array(k);
for (let i = 0; i < k; i++) {
t[i] = new Uint16Array(n);
// Initialize with error
for (let j = 0; j < n; j++) {
t[i][j] = e[i][j];
}
// Add A·s
for (let j = 0; j < k; j++) {
// Polynomial multiplication in NTT domain
const product = polyMul(A[i][j], s[j], n, q);
// Add to result
for (let l = 0; l < n; l++) {
t[i][l] = (t[i][l] + product[l]) % q;
}
}
}
// Serialize the keys
// Public key: seedA + serialized t
const publicKeySize = 32 + k * n * 2; // 32 bytes for seed, 2 bytes per coefficient
const publicKeyData = new Uint8Array(publicKeySize);
publicKeyData.set(seedA, 0);
let offset = 32;
for (let i = 0; i < k; i++) {
for (let j = 0; j < n; j++) {
// Store each coefficient as a 16-bit value
publicKeyData[offset++] = t[i][j] & 0xff;
publicKeyData[offset++] = (t[i][j] >> 8) & 0xff;
}
}
// Private key: seedA + serialized s + serialized t
const privateKeySize = 32 + k * n + k * n * 2; // 32 bytes for seed, 1 byte per s coeff, 2 bytes per t coeff
const privateKeyData = new Uint8Array(privateKeySize);
privateKeyData.set(seedA, 0);
offset = 32;
for (let i = 0; i < k; i++) {
for (let j = 0; j < n; j++) {
// Store each coefficient as a byte (small values)
privateKeyData[offset++] = s[i][j] & 0xff;
}
}
for (let i = 0; i < k; i++) {
for (let j = 0; j < n; j++) {
// Store each coefficient as a 16-bit value
privateKeyData[offset++] = t[i][j] & 0xff;
privateKeyData[offset++] = (t[i][j] >> 8) & 0xff;
}
}
return {
publicKey: encoding.bufferToHex(publicKeyData),
privateKey: encoding.bufferToHex(privateKeyData),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
eta1,
eta2,
},
};
}
/**
* Generate a matrix of polynomials using a seed
*
* @param seed - Seed for generation
* @param rows - Number of rows
* @param cols - Number of columns
* @param n - Polynomial degree
* @param q - Modulus
* @returns Matrix of polynomials
*/
function generateMatrix(seed, rows, cols, n, q) {
const matrix = new Array(rows);
for (let i = 0; i < rows; i++) {
matrix[i] = new Array(cols);
for (let j = 0; j < cols; j++) {
// Generate a unique seed for each position
const positionSeed = hashCore.Hash.create(Buffer.concat([seed, Buffer.from([i, j])]), { algorithm: "sha256", outputFormat: "buffer" });
// Generate a polynomial with coefficients in [0, q-1]
matrix[i][j] = new Uint16Array(n);
// Use the seed to generate coefficients
let byteCounter = 0;
let seedIndex = 0;
let currentSeed = positionSeed;
while (byteCounter < n) {
// Generate more bytes if needed
if (seedIndex >= currentSeed.length) {
currentSeed = hashCore.Hash.create(currentSeed, {
algorithm: "sha256",
outputFormat: "buffer",
});
seedIndex = 0;
}
// Extract a 16-bit value
const val = currentSeed[seedIndex++] | (currentSeed[seedIndex++] << 8);
// Only use values less than q
if (val < q) {
matrix[i][j][byteCounter++] = val;
}
}
}
}
return matrix;
}
/**
* Generate a vector of polynomials with small coefficients
*
* @param seed - Seed for generation
* @param size - Vector size
* @param n - Polynomial degree
* @param eta - Noise parameter
* @param q - Modulus
* @returns Vector of polynomials
*/
function generateNoiseVector(seed, size, n, eta, q) {
const vector = new Array(size);
for (let i = 0; i < size; i++) {
// Generate a unique seed for each position
const positionSeed = hashCore.Hash.create(Buffer.concat([seed, Buffer.from([i])]), { algorithm: "sha256", outputFormat: "buffer" });
// Generate a polynomial with small coefficients in [-eta, eta]
vector[i] = new Uint16Array(n);
// Use the seed to generate coefficients
let byteCounter = 0;
let seedIndex = 0;
let currentSeed = positionSeed;
while (byteCounter < n) {
// Generate more bytes if needed
if (seedIndex >= currentSeed.length) {
currentSeed = hashCore.Hash.create(currentSeed, {
algorithm: "sha256",
outputFormat: "buffer",
});
seedIndex = 0;
}
// Extract a value in the range [0, 2*eta]
const val = currentSeed[seedIndex++] % (2 * eta + 1);
// Center around zero and ensure it's positive modulo q
vector[i][byteCounter++] = (val <= eta ? val : q - (val - eta)) % q;
}
}
return vector;
}
/**
* Multiply two polynomials modulo x^n + 1 and coefficient-wise modulo q
*
* @param a - First polynomial
* @param b - Second polynomial
* @param n - Polynomial degree
* @param q - Modulus
* @returns Product polynomial
*/
function polyMul(a, b, n, q) {
const result = new Uint16Array(n);
// Naive polynomial multiplication
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
// Calculate the target index with reduction modulo x^n + 1
const idx = (i + j) % n;
// If i+j >= n, we need to negate the coefficient due to x^n = -1
const coef = i + j >= n ? q - ((a[i] * b[j]) % q) : (a[i] * b[j]) % q;
// Add to the result
result[idx] = (result[idx] + coef) % q;
}
}
return result;
}
/**
* Encapsulates a shared secret using a Kyber public key
*
* @param publicKey - Recipient's public key (hex encoded)
* @param params - Optional parameters
* @returns Encapsulated shared secret and ciphertext
*/
function kyberEncapsulate(publicKey, params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
try {
// Try to import the kyber-crystals library
const kyber = require("kyber-crystals");
// Map our security level to the Kyber variant
let kyberVariant;
switch (securityLevel) {
case 1:
kyberVariant = kyber.kyber512;
break;
case 3:
kyberVariant = kyber.kyber768;
break;
case 5:
kyberVariant = kyber.kyber1024;
break;
default:
kyberVariant = kyber.kyber768; // Default to Kyber-768 (security level 3)
}
// Parse the public key
const publicKeyBuffer = encoding.hexToBuffer(publicKey);
// Encapsulate using the library
const encapsulation = kyberVariant.encap(publicKeyBuffer);
// Extract the ciphertext and shared secret
const ciphertext = Buffer.from(encapsulation.ciphertext);
const sharedSecret = Buffer.from(encapsulation.shared_secret);
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
return {
ciphertext: encoding.bufferToHex(ciphertext),
sharedSecret: encoding.bufferToHex(sharedSecret),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
},
};
}
catch (error) {
console.warn("Error using crystals-kyber library:", error);
console.warn("Falling back to simplified Kyber implementation");
// Fallback to a simplified implementation
return kyberEncapsulateFallback(publicKey, params);
}
}
/**
* Fallback implementation of Kyber encapsulation
* Used when the kyber-crystals library is not available
*
* @param publicKey - Recipient's public key (hex encoded)
* @param params - Optional parameters
* @returns Encapsulated shared secret and ciphertext
*/
function kyberEncapsulateFallback(publicKey, params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
const eta1 = securityLevel === 1 ? 3 : 2; // Noise parameter for secret key
const eta2 = 2; // Noise parameter for error
try {
// Try to use the pqc-kyber library as an alternative
const pqcKyber = require("pqc-kyber");
// Parse the public key
const publicKeyBuffer = encoding.hexToBuffer(publicKey);
// Map our security level to the Kyber variant
let kyberVariant;
switch (securityLevel) {
case 1:
kyberVariant = pqcKyber.kyber512;
break;
case 3:
kyberVariant = pqcKyber.kyber768;
break;
case 5:
kyberVariant = pqcKyber.kyber1024;
break;
default:
kyberVariant = pqcKyber.kyber768; // Default to Kyber-768 (security level 3)
}
// Encapsulate using the library
const result = kyberVariant.encap(publicKeyBuffer);
return {
ciphertext: encoding.bufferToHex(result.ciphertext),
sharedSecret: encoding.bufferToHex(result.sharedSecret),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
eta1,
eta2,
},
};
}
catch (error) {
console.warn("Error using pqc-kyber library:", error);
console.warn("Using our own Kyber implementation");
}
// Parse public key
const publicKeyData = encoding.hexToBuffer(publicKey);
// Extract seed and public polynomials from the public key
const seed = publicKeyData.slice(0, 32);
const t = new Array(k);
let offset = 32;
for (let i = 0; i < k; i++) {
t[i] = new Uint16Array(n);
for (let j = 0; j < n; j++) {
t[i][j] = publicKeyData[offset++] | (publicKeyData[offset++] << 8);
}
}
// Generate the public matrix A using the seed
const A = generateMatrix(seed, k, k, n, q);
// Generate a random message m
const m = randomCore.SecureRandom.getRandomBytes(32);
// Hash the message to get noise seeds
const noiseHash = hashCore.Hash.create(m, {
algorithm: "sha512", // Using SHA-512 instead of SHA3-512
outputFormat: "buffer",
});
const r_seed = noiseHash.slice(0, 32);
const e1_seed = noiseHash.slice(32, 64);
// Generate the noise vector r with small coefficients
const r = generateNoiseVector(r_seed, k, n, eta1, q);
// Generate the error vector e1 with small coefficients
const e1 = generateNoiseVector(e1_seed, k, n, eta2, q);
// Compute u = A^T·r + e1
const u = new Array(k);
for (let i = 0; i < k; i++) {
u[i] = new Uint16Array(n);
// Initialize with error
for (let j = 0; j < n; j++) {
u[i][j] = e1[i][j];
}
// Add A^T·r
for (let j = 0; j < k; j++) {
// Polynomial multiplication in NTT domain
const product = polyMul(A[j][i], r[j], n, q);
// Add to result
for (let l = 0; l < n; l++) {
u[i][l] = (u[i][l] + product[l]) % q;
}
}
}
// Generate another error e2
const e2_seed = hashCore.Hash.create(Buffer.concat([Buffer.from(m), Buffer.from(seed)]), {
algorithm: "sha256",
outputFormat: "buffer",
});
const e2 = new Uint16Array(n);
for (let i = 0; i < n; i++) {
e2[i] = e2_seed[i % e2_seed.length] % (2 * eta2 + 1);
e2[i] = (e2[i] <= eta2 ? e2[i] : q - (e2[i] - eta2)) % q;
}
// Compute v = t^T·r + e2 + encode(m)
const v = new Uint16Array(n);
// Initialize with error e2
for (let i = 0; i < n; i++) {
v[i] = e2[i];
}
// Add t^T·r
for (let i = 0; i < k; i++) {
const product = polyMul(t[i], r[i], n, q);
for (let j = 0; j < n; j++) {
v[j] = (v[j] + product[j]) % q;
}
}
// Encode the message m into the polynomial
for (let i = 0; i < n; i++) {
if (i < m.length * 8) {
const bytePos = Math.floor(i / 8);
const bitPos = i % 8;
const bit = (m[bytePos] >> bitPos) & 1;
// Add q/2 if the bit is 1
if (bit === 1) {
v[i] = (v[i] + Math.floor(q / 2)) % q;
}
}
}
// Serialize the ciphertext (u, v)
const ciphertextSize = k * n * 2 + n * 2; // 2 bytes per coefficient
const ciphertextData = new Uint8Array(ciphertextSize);
offset = 0;
// Serialize u
for (let i = 0; i < k; i++) {
for (let j = 0; j < n; j++) {
ciphertextData[offset++] = u[i][j] & 0xff;
ciphertextData[offset++] = (u[i][j] >> 8) & 0xff;
}
}
// Serialize v
for (let i = 0; i < n; i++) {
ciphertextData[offset++] = v[i] & 0xff;
ciphertextData[offset++] = (v[i] >> 8) & 0xff;
}
// Derive the shared secret from the message
const sharedSecret = hashCore.Hash.create(m, {
algorithm: "sha256", // Using SHA-256 instead of SHA3-256
outputFormat: "buffer",
});
return {
ciphertext: encoding.bufferToHex(ciphertextData),
sharedSecret: encoding.bufferToHex(sharedSecret),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
eta1,
eta2,
},
};
}
/**
* Decapsulates a shared secret using a Kyber private key and ciphertext
*
* @param privateKey - Recipient's private key (hex encoded)
* @param ciphertext - Ciphertext from encapsulation (hex encoded)
* @param params - Optional parameters
* @returns Decapsulated shared secret
*/
function kyberDecapsulate(privateKey, ciphertext, params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
try {
// Try to import the kyber-crystals library
const kyber = require("kyber-crystals");
// Map our security level to the Kyber variant
let kyberVariant;
switch (securityLevel) {
case 1:
kyberVariant = kyber.kyber512;
break;
case 3:
kyberVariant = kyber.kyber768;
break;
case 5:
kyberVariant = kyber.kyber1024;
break;
default:
kyberVariant = kyber.kyber768; // Default to Kyber-768 (security level 3)
}
// Parse the private key and ciphertext
const privateKeyBuffer = encoding.hexToBuffer(privateKey);
const ciphertextBuffer = encoding.hexToBuffer(ciphertext);
// Decapsulate using the library
const sharedSecret = kyberVariant.decap(privateKeyBuffer, ciphertextBuffer);
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
return {
sharedSecret: encoding.bufferToHex(Buffer.from(sharedSecret)),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
},
};
}
catch (error) {
console.warn("Error using crystals-kyber library:", error);
console.warn("Falling back to simplified Kyber implementation");
// Fallback to a simplified implementation
return kyberDecapsulateFallback(privateKey, ciphertext, params);
}
}
/**
* Fallback implementation of Kyber decapsulation
* Used when the kyber-crystals library is not available
*
* @param privateKey - Recipient's private key (hex encoded)
* @param ciphertext - Ciphertext from encapsulation (hex encoded)
* @param params - Optional parameters
* @returns Decapsulated shared secret
*/
function kyberDecapsulateFallback(privateKey, ciphertext, params = {}) {
// Parse parameters
const securityLevel = params.securityLevel || 3; // 1, 3, or 5
// Determine parameters based on security level
const n = 256; // Polynomial degree (fixed for Kyber)
const k = securityLevel === 1 ? 2 : securityLevel === 3 ? 3 : 4; // Module rank
const q = 3329; // Modulus
const eta1 = securityLevel === 1 ? 3 : 2; // Noise parameter for secret key
const eta2 = 2; // Noise parameter for error
try {
// Try to use the pqc-kyber library as an alternative
const pqcKyber = require("pqc-kyber");
// Parse the private key and ciphertext
const privateKeyBuffer = encoding.hexToBuffer(privateKey);
const ciphertextBuffer = encoding.hexToBuffer(ciphertext);
// Map our security level to the Kyber variant
let kyberVariant;
switch (securityLevel) {
case 1:
kyberVariant = pqcKyber.kyber512;
break;
case 3:
kyberVariant = pqcKyber.kyber768;
break;
case 5:
kyberVariant = pqcKyber.kyber1024;
break;
default:
kyberVariant = pqcKyber.kyber768; // Default to Kyber-768 (security level 3)
}
// Decapsulate using the library
const sharedSecret = kyberVariant.decap(privateKeyBuffer, ciphertextBuffer);
return {
sharedSecret: encoding.bufferToHex(sharedSecret),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
eta1,
eta2,
},
};
}
catch (error) {
console.warn("Error using pqc-kyber library:", error);
console.warn("Using our own Kyber implementation");
}
// Parse private key
const privateKeyData = encoding.hexToBuffer(privateKey);
// Extract secret key s and public key t (seed is at the beginning but not needed for decapsulation)
privateKeyData.slice(0, 32); // Skip the seed
const s = new Array(k);
const t = new Array(k);
// Parse the secret key s
let offset = 32;
for (let i = 0; i < k; i++) {
s[i] = new Uint16Array(n);
for (let j = 0; j < n; j++) {
s[i][j] = privateKeyData[offset++];
}
}
// Parse the public key t
for (let i = 0; i < k; i++) {
t[i] = new Uint16Array(n);
for (let j = 0; j < n; j++) {
t[i][j] =
privateKeyData[offset++] | (privateKeyData[offset++] << 8);
}
}
// Parse ciphertext
const ciphertextData = encoding.hexToBuffer(ciphertext);
// Extract ciphertext components u and v
const u = new Array(k);
offset = 0;
// Parse u
for (let i = 0; i < k; i++) {
u[i] = new Uint16Array(n);
for (let j = 0; j < n; j++) {
u[i][j] =
ciphertextData[offset++] | (ciphertextData[offset++] << 8);
}
}
// Parse v
const v = new Uint16Array(n);
for (let i = 0; i < n; i++) {
v[i] = ciphertextData[offset++] | (ciphertextData[offset++] << 8);
}
// Compute v - s^T·u
const mp = new Uint16Array(n);
// Initialize with v
for (let i = 0; i < n; i++) {
mp[i] = v[i];
}
// Subtract s^T·u
for (let i = 0; i < k; i++) {
const product = polyMul(s[i], u[i], n, q);
for (let j = 0; j < n; j++) {
mp[j] = (mp[j] + q - product[j]) % q; // Subtract in modular arithmetic
}
}
// Decode the message from mp
const m = new Uint8Array(32);
for (let i = 0; i < 32; i++) {
m[i] = 0;
}
for (let i = 0; i < Math.min(n, 32 * 8); i++) {
const bytePos = Math.floor(i / 8);
const bitPos = i % 8;
// Check if the coefficient is closer to q/2 (bit = 1) or 0 (bit = 0)
if (mp[i] > q / 4 && mp[i] < (3 * q) / 4) {
m[bytePos] |= 1 << bitPos;
}
}
// Derive the shared secret from the message
const sharedSecret = hashCore.Hash.create(m, {
algorithm: "sha256",
outputFormat: "buffer",
});
return {
sharedSecret: encoding.bufferToHex(sharedSecret),
algorithm: "kyber",
params: {
securityLevel,
n,
k,
q,
eta1,
eta2,
},
};
}
/**
* Real cryptographic hash function using SHA-256
*
* @param data - Data to hash
* @param outputLength - Desired output length
* @returns Hash value
*/
function simpleHash(data, outputLength) {
// Use the secure hash function from the Hash module
const hashResult = hashCore.Hash.create(data, {
algorithm: "sha256",
outputFormat: "buffer",
});
// Convert the result to Uint8Array
let hashBuffer;
if (typeof hashResult === "string") {
// If the result is a string (shouldn't happen with outputFormat: "buffer")
// Convert it to a buffer
const encoder = new TextEncoder();
hashBuffer = encoder.encode(hashResult);
}
else {
// It's already a Uint8Array
hashBuffer = hashResult;
}
// If the requested output length matches the hash length, return it directly
if (outputLength === hashBuffer.length) {
return hashBuffer;
}
// Otherwise, truncate or extend as needed
const result = new Uint8Array(outputLength);
if (outputLength < hashBuffer.length) {
// Truncate
result.set(hashBuffer.subarray(0, outputLength));
}
else {
// Extend by hashing repeatedly
result.set(hashBuffer);
let offset = hashBuffer.length;
let counter = 1;
while (offset < outputLength) {
// Create a new buffer with the original hash and a counter
const counterBuffer = new Uint8Array(4);
counterBuffer[0] = (counter >> 24) & 0xff;
counterBuffer[1] = (counter >> 16) & 0xff;
counterBuffer[2] = (counter >> 8) & 0xff;
counterBuffer[3] = counter & 0xff;
// Concatenate the hash and counter
const extendBuffer = new Uint8Array(hashBuffer.length + 4);
extendBuffer.set(hashBuffer);
extendBuffer.set(counterBuffer, hashBuffer.length);
// Hash the extended buffer
const extendedHashResult = hashCore.Hash.create(extendBuffer, {
algorithm: "sha256",
outputFormat: "buffer",
});
// Convert the result to Uint8Array
let extendedHash;
if (typeof extendedHashResult === "string") {
const encoder = new TextEncoder();
extendedHash = encoder.encode(extendedHashResult);
}
else {
extendedHash = extendedHashResult;
}
// Copy as much as needed to the result
const bytesToCopy = Math.min(extendedHash.length, outputLength - offset);
result.set(extendedHash.subarray(0, bytesToCopy), offset);
offset += bytesToCopy;
counter++;
}
}
return result;
}
exports.generateKyberKeyPair = generateKyberKeyPair;
exports.kyberDecapsulate = kyberDecapsulate;
exports.kyberEncapsulate = kyberEncapsulate;
exports.lamportGenerateKeypair = lamportGenerateKeypair;
exports.lamportSign = lamportSign;
exports.lamportVerify = lamportVerify;
exports.ringLweDecrypt = ringLweDecrypt;
exports.ringLweEncrypt = ringLweEncrypt;
exports.ringLweGenerateKeypair = ringLweGenerateKeypair;
//# sourceMappingURL=post-quantum.js.map