Tom Butcher 8f2d3a2489 Add initial project structure for DES Key Cracker
- Created .gitignore to exclude build artifacts and dependencies.
- Added package.json and package-lock.json for project dependencies and scripts.
- Included pnpm workspace configuration for managing packages.
- Implemented TypeScript configuration in tsconfig.json.
- Added README.md with project description and usage instructions.
- Introduced native code for DES encryption and decryption in C/C++.
- Created initial decoded data structure for handling scan results.
- Established basic file structure for decoded outputs and native builds.
2026-09-19 16:10:35 +01:00

100 lines
3.3 KiB
TypeScript

import fs from "fs";
import path from "path";
import { HEADER_MAGICS } from "./constants";
import { desDecryptBlock, desDecryptFile, hexKeyToBuffer } from "./des";
export interface HitRecord {
cipher: "des";
password: string;
derivation: string;
key_hex: string;
pad_fill: string;
first_pt_hex: string | null;
backend: string;
payload_ok?: boolean;
payload_reason?: string;
first64_ascii?: string;
decrypt_error?: string;
}
export function payloadLooksReal(first64: Buffer, more?: Buffer): { ok: boolean; reason: string } {
for (const magic of HEADER_MAGICS) {
if (first64.subarray(0, magic.length).equals(magic) || first64.includes(magic)) {
return { ok: true, reason: "magic" };
}
}
let printable = 0;
for (const b of first64) {
if ((b >= 32 && b < 127) || b === 9 || b === 10 || b === 13) {
printable++;
}
}
if (printable >= 48) {
return { ok: true, reason: "printable_header" };
}
const blob = Buffer.concat([first64, more ?? Buffer.alloc(0)]);
if (blob.includes(Buffer.from("Butcher")) || blob.includes(Buffer.from("Thomas")) ||
blob.includes(Buffer.from("Pectus")) || blob.includes(Buffer.from("Torso"))) {
return { ok: true, reason: "patient_string" };
}
let inRange = 0;
for (let i = 0; i + 4 <= first64.length; i += 4) {
const f = first64.readFloatLE(i);
if (Number.isFinite(f) && Math.abs(f) < 5000) {
inRange++;
}
}
if (inRange >= 12) {
return { ok: true, reason: "float32_scan_range" };
}
const count = first64.readUInt32LE(0);
if (count >= 1000 && count <= 200_000) {
return { ok: true, reason: `vertex_count_${count}` };
}
return { ok: false, reason: "unstructured" };
}
export function asciiPreview(buf: Buffer): string {
return [...buf].map((b) => (b >= 32 && b < 127 ? String.fromCharCode(b) : ".")).join("");
}
export function saveHit(hit: HitRecord, data: Buffer, outDir: string): string | null {
fs.mkdirSync(outDir, { recursive: true });
const key = hexKeyToBuffer(hit.key_hex);
try {
const plain = desDecryptFile(key, data);
const first64 = plain.subarray(0, 64);
const more = plain.subarray(0, 4096);
const check = payloadLooksReal(first64, more);
hit.payload_ok = check.ok;
hit.payload_reason = check.reason;
hit.first64_ascii = asciiPreview(first64);
const stamp = `${hit.cipher}_${hit.derivation}_${hit.key_hex.slice(0, 16)}`;
const binPath = path.join(outDir, `decrypted_${stamp}.bin`);
fs.writeFileSync(binPath, plain);
fs.writeFileSync(path.join(outDir, `hit_${stamp}.json`), JSON.stringify(hit, null, 2) + "\n");
return binPath;
} catch (err) {
hit.decrypt_error = err instanceof Error ? err.message : String(err);
return null;
}
}
export function firstPlainHex(keyHex: string, firstCt: Buffer): string | null {
try {
const pt = desDecryptBlock(hexKeyToBuffer(keyHex), firstCt);
return pt.toString("hex");
} catch {
return null;
}
}
export function writeResults(outDir: string, elapsedSec: number, hits: HitRecord[]): void {
fs.mkdirSync(outDir, { recursive: true });
const confirmed = hits.filter((h) => h.payload_ok);
fs.writeFileSync(
path.join(outDir, "results.json"),
JSON.stringify({ elapsed_sec: Math.round(elapsedSec * 1000) / 1000, pad_matches: hits, confirmed }, null, 2) + "\n",
);
}