128 lines
4.5 KiB
JavaScript
128 lines
4.5 KiB
JavaScript
import { Buffer } from 'buffer';
|
|
import { sha256 } from "js-sha256";
|
|
import JSChaCha20 from 'js-chacha20';
|
|
|
|
function bigIntToUint8Array(bigInt, byteLength) {
|
|
const hex = bigInt.toString(16).padStart(byteLength * 2, '0');
|
|
const byteArray = new Uint8Array(byteLength);
|
|
for (let i = 0; i < byteLength; i++) {
|
|
byteArray[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
}
|
|
return byteArray;
|
|
}
|
|
|
|
export function bigIntToBase64(bigInt) {
|
|
const hexString = bigInt.toString(16).padStart(bigInt.toString(16).length + (bigInt.toString(16).length % 2), '0');
|
|
return Buffer.from(hexString, 'hex').toString('base64');
|
|
}
|
|
|
|
export function base64ToBigInt(base64) {
|
|
return BigInt('0x' + Buffer.from(base64, 'base64').toString('hex'));
|
|
}
|
|
|
|
const pBase64 = "2IGKThw2+H+X0Mc5ZxIfOGX1b3lLc/mDB2Ne73FdoEiJRRfWaETlLUg8dAFUWn4Jxg/QSbP7+f/Q0dZbPCShAXrqWsqxScNk+XvFRTUAhqq1h82Bh4Puok2P1ke6sPR/k+LCt9uHMlt/X0TodDonFIMr87KjB9bQ+zysfPXU2G9chMjqPpY2AkInPHfaMtKtIfBslXKbhGwtaK6t0h0GGJ7W3GU8e1dzo/JgwVDnqoTryAKdoFmrpjg41naQOC5Hl59i+Ik5yEL+NCvSis7IYo55sM6cbu8B4N4wNwDKkefgElADvhSKQJirbmSpPXs5Lr7GXgRBR6t/AGrYTgahPw==";
|
|
const gBase64 = "5";
|
|
const p = base64ToBigInt(pBase64);
|
|
const g = BigInt(gBase64);
|
|
|
|
export function generatePrivateKey() {
|
|
return generateRandomBigInt(p - 1n);
|
|
}
|
|
|
|
function generateRandomBigInt(max) {
|
|
let randomHex = '';
|
|
while (randomHex.length < max.toString(16).length) {
|
|
randomHex += Math.floor(Math.random() * 16).toString(16);
|
|
}
|
|
return BigInt('0x' + randomHex.slice(0, max.toString(16).length));
|
|
}
|
|
|
|
export function calculatePublicKey(privateKey) {
|
|
return modExponentiation(g, privateKey, p);
|
|
}
|
|
|
|
function modExponentiation(base, exponent, modulus) {
|
|
let result = BigInt(1);
|
|
base = base % modulus;
|
|
while (exponent > 0n) {
|
|
if (exponent % 2n === 1n) result = (result * base) % modulus;
|
|
exponent /= 2n;
|
|
base = (base * base) % modulus;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function computeSharedSecret(publicKey, privateKey) {
|
|
return modExponentiation(publicKey, privateKey, p);
|
|
}
|
|
|
|
export function chacha20Encrypt(jsonobj) {
|
|
try {
|
|
let { message, receiver, pubkey } = JSON.parse(jsonobj);
|
|
message = new TextEncoder().encode(message);
|
|
|
|
let storedUsers = JSON.parse(localStorage.getItem("privateKeys")) || [];
|
|
const user = storedUsers.find(user => user.username === receiver);
|
|
if (!user) return console.error("User not found in localStorage");
|
|
|
|
const clientPrivKey = base64ToBigInt(user.private64);
|
|
const secret = computeSharedSecret(base64ToBigInt(pubkey), clientPrivKey);
|
|
|
|
const secretArray = bigIntToUint8Array(secret);
|
|
const chachaKeyUint8 = new Uint8Array(sha256.create().update(secretArray).digest());
|
|
|
|
const nonce = generateNonce();
|
|
let nonceB64 = Buffer.from(nonce).toString("base64");
|
|
|
|
const chacha = new JSChaCha20(chachaKeyUint8, nonce);
|
|
const encrypted = chacha.encrypt(message);
|
|
const encryptedArray = new Uint8Array(encrypted);
|
|
const encryptedB64 = Buffer.from(encryptedArray).toString("base64");
|
|
|
|
return JSON.stringify({
|
|
encrypted: encryptedB64,
|
|
nonce: nonceB64,
|
|
pubKey: pubkey,
|
|
receiverUsername: receiver
|
|
});
|
|
} catch (error) {
|
|
console.error("Error during encryption:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function chacha20Decrypt(data) {
|
|
try {
|
|
const { content, nonce, pubKey, receiverUsername } = data;
|
|
|
|
let nonceArray = new Uint8Array(Buffer.from(nonce, 'base64'));
|
|
if (nonceArray.length !== 12) throw new Error("Nonce should be a 12 byte array!");
|
|
|
|
let storedUsers = JSON.parse(localStorage.getItem("privateKeys")) || [];
|
|
const user = storedUsers.find(user => user.username === receiverUsername);
|
|
if (!user) throw new Error("User not found in localStorage for decryption");
|
|
|
|
const clientPrivKey = base64ToBigInt(user.private64);
|
|
const secret = computeSharedSecret(base64ToBigInt(pubKey), clientPrivKey);
|
|
|
|
const secretArray = bigIntToUint8Array(secret);
|
|
const chachaKeyUint8 = new Uint8Array(sha256.create().update(secretArray).digest());
|
|
|
|
let encryptedArray = new Uint8Array(Buffer.from(content, 'base64'));
|
|
|
|
const chacha = new JSChaCha20(chachaKeyUint8, nonceArray);
|
|
const decrypted = chacha.decrypt(encryptedArray);
|
|
|
|
return new TextDecoder().decode(decrypted);
|
|
} catch (error) {
|
|
console.error("Decryption Error:", error);
|
|
return "Error decrypting message: " + error.message;
|
|
}
|
|
}
|
|
|
|
export function generateNonce(length = 12) {
|
|
const nonce = new Uint8Array(length);
|
|
for (let i = 0; i < length; i++) nonce[i] = Math.floor(Math.random() * 256);
|
|
return nonce;
|
|
}
|