63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import { Solitaire } from "./crypto";
|
|
import { createHash } from "crypto";
|
|
import type { integer } from "types/misc";
|
|
|
|
/* eslint-disable no-bitwise */
|
|
export function CalculateCardKey(serial: integer, key: string) {
|
|
const keyBuffer = Buffer.from(key, "hex");
|
|
const paddedSerial = serial.toString().padStart(8, "0");
|
|
const digest = createHash("md5").update(paddedSerial).digest();
|
|
|
|
let hash = 0;
|
|
let num = 0;
|
|
let storedBits = 0;
|
|
|
|
for (let i = 0; i < 16; i++) {
|
|
// Extract the i-th hexadecimal digit of the key...
|
|
const byte = keyBuffer[Math.trunc(i / 2)];
|
|
|
|
if (byte === undefined) {
|
|
throw new Error("Buffer.from returned an undefined value in a Buffer?");
|
|
}
|
|
|
|
// Extract the upper byte and lower byte respectively...
|
|
const idx = i % 2 === 0 ? byte >>> 4 : byte & 0xf;
|
|
|
|
// Using it as the index to shuffle the serial MD5...
|
|
const nib = digest[idx];
|
|
|
|
if (nib === undefined) {
|
|
throw new Error(
|
|
"crypto.createHash returned an undefined value in a Buffer. this should not happen."
|
|
);
|
|
}
|
|
|
|
// Store the byte into a temporary little-endian number...
|
|
num = num | (nib << storedBits);
|
|
storedBits = storedBits + 8;
|
|
|
|
// XOR every 23 bits or if it's the final iteration
|
|
if (storedBits > 23 || i === 15) {
|
|
// 0x7fffff is mask to extract the last 23 bits of num.
|
|
hash = hash ^ (num & 0x7fffff);
|
|
|
|
// Removed the bits we worked on
|
|
num = num >>> 23;
|
|
|
|
storedBits = storedBits - 23;
|
|
}
|
|
}
|
|
|
|
return hash.toString().padStart(7, "0");
|
|
}
|
|
/* eslint-enable no-bitwise */
|
|
|
|
export function CalculateAccessCode(serial: integer, prefix: string, key: string): string {
|
|
const digest = CalculateCardKey(serial, key);
|
|
|
|
const paddedSerial = serial.toString().padStart(8, "0");
|
|
const hashedId = Solitaire.encrypt(paddedSerial, digest);
|
|
|
|
return `${prefix}${hashedId}${digest}`;
|
|
}
|