commit 3c308ccb2107ae68f1660f3a0676d532c1b2ddc1 Author: afonya2 Date: Thu May 15 17:24:37 2025 +0200 hello git diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f634cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +package-lock.json +node_modules/ +test.txt \ No newline at end of file diff --git a/IGA.ts b/IGA.ts new file mode 100644 index 0000000..a616cb1 --- /dev/null +++ b/IGA.ts @@ -0,0 +1,209 @@ +type padding = { + padding: number[] + seed: number +} + +type verifyOut = { + valid: boolean + reason: string +} + +function modMul(n: number, x: number, m: number): number { + let out = 0 + for (let i = 0; i < x; i++) { + out = out + n + out = out % m + } + return out +} + +class randomGenerator { + state = 0 + seen: number[] = [] + constructor(seed: number) { + this.state = seed + } + + next(): number { + let temp = modMul(this.state, 6967, 65536) + if (this.seen.length >= 65536) { + this.seen = [] + } + while (this.seen.includes(temp)) { + temp++ + temp = temp % 65536 + } + this.seen.push(temp) + this.state = temp + return this.state + } +} + +function generatePadding(len: number, seed?: number): padding { + if (seed == undefined) { + seed = Math.floor(Math.random() * 65535) + } + let out: number[] = [] + let generator = new randomGenerator(seed) + for (let i = 0; i < len; i++) { + let n = generator.next() + out.push(n) + } + return { + padding: out, + seed: seed + } +} + +function copy(input: number[]): number[] { + let out: number[] = [] + for (let i = 0; i < input.length; i++) { + out[i] = input[i] + } + return out +} + +function dec2bin(dec: number): string { + return (dec >>> 0).toString(2); +} +function bin2dec(bin: string): number { + return parseInt(bin, 2) +} +function fixbin(bin: string, len: number): string { + let out = bin + while (out.length < len) { + out = "0" + out + } + return out.substring(out.length-len, out.length) +} +function bin2hex(bin: string): string { + let chars = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] + let hex = "" + while (bin.length > 3) { + let n = bin2dec(bin.substring(0, 4)) + hex += chars[n] + bin = bin.substring(4, bin.length) + } + return hex +} +function hex2bin(hex: string): string { + let chars = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] + let bin = "" + for (let i = 0; i < hex.length; i++) { + bin += fixbin(dec2bin(chars.indexOf(hex[i].toUpperCase())), 4) + } + return bin +} + +function checksum(input: string) { + let state: string[] = [] + for (let i = 0; i < 16; i++) { + state.push("0") + } + let bin = "" + for (let i = 0; i < input.length; i++) { + bin += dec2bin(input.charCodeAt(i)) + } + while (bin.length > 0) { + state.splice(0, 1) + state.push(bin[0]) + if (state[0] == "1") { + state[8] = state[8] == "1" ? "0" : "1" + state[6] = state[6] == "1" ? "0" : "1" + state[1] = state[1] == "1" ? "0" : "1" + } + bin = bin.substring(1, bin.length) + } + return state.join("") +} + +function hash(input: string, salt: number = 0, iterations: number = 32, len: number = 32, seed?: number): string { + if (len*4%16 != 0) { + throw new Error("Invalid length") + } + if (len < 16) { + throw new Error("Length must be at least 16") + } + const padding = generatePadding(len, seed); + let hash: number[] = [] + for (let i = 0; i < padding.padding.length; i++) { + hash[i] = padding.padding[i] + } + let plus = hash.length + for (let i = 0; i < input.length; i++) { + hash[plus+i] = input.charCodeAt(i) + } + for (let i = 0; i < iterations; i++) { + let state = copy(hash) + for (let ii = 0; ii < hash.length; ii++) { + let temp = state[ii] + (state[ii+1] ? state[ii+1] : state[0]) + salt + while (temp > 65536) { + temp -= i==0 ? 2411 : (state[ii-1] ? state[ii-1] : state[state.length-1]) + } + hash[ii] = temp + } + } + const pSeed = fixbin(dec2bin(padding.seed), 16) + let sumAdd = "" + for (let i = 0; sumAdd.length < 16 && i < padding.padding.length; i++) { + sumAdd += padding.padding[i].toString(16) + } + sumAdd = sumAdd.substring(0, 16).toUpperCase() + const sum = checksum(input + sumAdd) + let bin = "" + for (let i = 0; bin.length < len*4-32; i++) { + bin += hash[i] ? fixbin(dec2bin(hash[i]), 16) : "0000000000000000" + } + const outBin = pSeed + bin + sum + const out = bin2hex(outBin) + return out +} + +function verifyHash(input: string, inHash: string, salt: number = 0, iterations: number = 32, len: number = 32): verifyOut { + if (len*4%16 != 0) { + throw new Error("Invalid length") + } + if (len < 16) { + throw new Error("Length must be at least 16") + } + if (inHash.length != len) { + throw new Error("The hash is not " + len + " long") + } + const hBin = hex2bin(inHash) + const pSeed = bin2dec(hBin.substring(0, 16)) + const inSum = hBin.substring(hBin.length-16, hBin.length) + const padding = generatePadding(len, pSeed); + let sumAdd = "" + for (let i = 0; sumAdd.length < 16 && i < padding.padding.length; i++) { + sumAdd += padding.padding[i].toString(16) + } + sumAdd = sumAdd.substring(0, 16).toUpperCase() + const sum = checksum(input + sumAdd) + if (inSum != sum) { + return { + valid: false, + reason: "Checksum does not match" + } + } + const verifyHash = hash(input, salt, iterations, len, pSeed) + if (inHash == verifyHash) { + return { + valid: true, + reason: "" + } + } else { + return { + valid: false, + reason: "Hash does not match" + } + } +} + +export default { + hash, + verifyHash +} + +export type { + verifyOut +} \ No newline at end of file diff --git a/hash.ts b/hash.ts new file mode 100644 index 0000000..97ef32f --- /dev/null +++ b/hash.ts @@ -0,0 +1,62 @@ +import IGA from './IGA'; +import fs from 'fs'; + +const args = process.argv.slice(2) +const version = "1.1" + +function printHelp() { + console.log("IGA commandline tool v" + version) + console.log("Usage: tsx hash.ts [] [options]"); + console.log("Options:"); + console.log("[] [] []"); +} + +if (args[0] == "hash") { + if (args.length < 2) { + console.log("Not enough arguments"); + printHelp() + process.exit(0) + } + if (!fs.existsSync(args[1])) { + console.log("File not found: " + args[1]) + process.exit(0) + } + let fileData = fs.readFileSync(args[1], 'utf8'); + let salt = args[2] ? parseInt(args[2]) : 0 + let iterations = args[3] ? parseInt(args[3]) : 32 + let len = args[4] ? parseInt(args[4]) : 32 + try { + let hash = IGA.hash(fileData, salt, iterations, len) + console.log(hash) + } catch (e) { + console.error("Error while creating hash: " + e.stack) + } +} else if (args[0] == "verify") { + if (args.length < 3) { + console.log("Not enough arguments"); + printHelp() + process.exit(0) + } + if (!fs.existsSync(args[1])) { + console.log("File not found: " + args[1]) + process.exit(0) + } + let fileData = fs.readFileSync(args[1], 'utf8'); + let hashToVerify = args[2] + let salt = args[3] ? parseInt(args[3]) : 0 + let iterations = args[4] ? parseInt(args[4]) : 32 + let len = args[5] ? parseInt(args[5]) : 32 + try { + let verify = IGA.verifyHash(fileData, hashToVerify, salt, iterations, len) + if (verify.valid) { + console.log("Hash is valid!") + } else { + console.log("Hash is invalid!") + console.log("Reason: " + verify.reason) + } + } catch (e) { + console.error("Error while verifying hash: " + e.stack) + } +} else { + printHelp() +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..38561a6 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@types/node": "^22.15.17" + } +}