kozukata-toa/src/servers/aimedb/utils/crypto.ts

231 lines
4.8 KiB
TypeScript

/* eslint-disable no-bitwise */
import { createCipheriv, createDecipheriv } from "crypto";
import type { integer } from "types/misc";
const ASCII_0 = 48;
export function encryptPacket(packet: Buffer, key: Buffer) {
const cipher = createCipheriv("aes-128-ecb", key, null).setAutoPadding(false);
return Buffer.concat([cipher.update(packet), cipher.final()]);
}
export function decryptPacket(packet: Buffer, key: Buffer) {
const cipher = createDecipheriv("aes-128-ecb", key, null).setAutoPadding(false);
return Buffer.concat([cipher.update(packet), cipher.final()]);
}
function char2num(char: string) {
return char.charCodeAt(0) - ASCII_0 + 1;
}
function num2char(num: integer) {
let inner = num;
while (inner < 1) {
inner = inner + 10;
}
// 48 is ASCII 0
return String.fromCharCode(((inner - 1) % 10) + ASCII_0);
}
/**
* A modified version of the [Solitaire cipher](https://en.wikipedia.org/wiki/Solitaire_(cipher)),
* used for encrypting and decrypting access codes.
*/
export class Solitaire {
private deck: Array<integer>;
private readonly deckSize: integer;
readonly jokerA: integer;
readonly jokerB: integer;
constructor(deckSize: integer, jokerA?: integer, jokerB?: integer) {
if (jokerA && jokerA > deckSize) {
throw new Error(
"Invalid jokerA. jokerA must not be larger than the largest card on deck."
);
}
if (jokerB && jokerB > deckSize) {
throw new Error(
"Invalid jokerB. jokerB must not be larger than the largest card on deck."
);
}
this.jokerA = jokerA ?? deckSize - 1;
this.jokerB = jokerB ?? deckSize;
this.deck = [...Array(deckSize).keys()].map((v) => v + 1);
this.deckSize = deckSize;
}
static encrypt(src: string, key: string) {
const deck = new Solitaire(22, 21, 22);
for (const char of key) {
deck.deckHash();
deck.cutDeck(char2num(char));
}
let output = "";
for (const char of src) {
deck.deckHash();
const p = deck.getTopCardNumber() ?? 0;
output = output + num2char(char2num(char) + p);
}
return output;
}
static decrypt(src: string, key: string) {
const deck = new Solitaire(22, 21, 22);
for (const char of key) {
deck.deckHash();
deck.cutDeck(char2num(char));
}
let output = "";
for (const char of src) {
deck.deckHash();
const p = deck.getTopCardNumber() ?? 0;
output = output + num2char(char2num(char) - p);
}
return output;
}
private moveCardDown(card: integer) {
const cardLocation = this.deck.indexOf(card);
if (cardLocation === -1) {
throw new Error("Card was not in the deck.");
}
if (cardLocation < this.deckSize - 1) {
const nextCard = this.deck[cardLocation + 1];
if (!nextCard) {
throw new Error("Next card is undefined. This should not be possible.");
}
this.deck[cardLocation] = nextCard;
this.deck[cardLocation + 1] = card;
} else {
for (let i = this.deckSize - 1; i > 1; i--) {
const prevCard = this.deck[i - 1];
if (!prevCard) {
throw new Error("Previous card is undefined. This should not be possible.");
}
this.deck[i] = prevCard;
}
this.deck[1] = card;
}
}
private cutDeck(point: integer) {
const tmpDeck = this.deck.slice(0);
let idx = 0;
for (let i = point; i < this.deckSize - 1; i++) {
const item = tmpDeck[i];
if (!item) {
throw new Error("not happening.");
}
this.deck[idx] = item;
idx++;
}
for (let i = 0; i < point; i++) {
const item = tmpDeck[i];
if (!item) {
throw new Error("not happening.");
}
this.deck[idx] = item;
idx++;
}
}
private swapOutsideJoker() {
let joker1 = -1;
let joker1Value = -1;
let joker2 = -1;
let joker2Value = -1;
for (let i = 0; i < this.deckSize; i++) {
const card = this.deck[i];
if (!card) {
continue;
}
if (card === this.jokerA || card === this.jokerB) {
if (joker1 === -1) {
joker1 = i;
joker1Value = card;
} else {
joker2 = i;
joker2Value = card;
}
}
}
this.deck = [
...this.deck.slice(joker2 + 1),
joker1Value,
...this.deck.slice(joker1 + 1, joker2),
joker2Value,
...this.deck.slice(0, joker1),
];
}
private cutByBottomCard() {
const bottom = this.deck[this.deckSize - 1];
if (!bottom) {
throw new Error("bottom card is undefined (not possbile)");
}
this.cutDeck(bottom === this.jokerB ? this.jokerA : bottom);
}
private getTopCardNumber() {
const top = this.deck[0];
if (!top) {
throw new Error("top card is undefined (not possbile)");
}
return this.deck[top === this.jokerB ? this.jokerA : top];
}
private deckHash() {
let p = -1;
do {
this.moveCardDown(this.jokerA);
this.moveCardDown(this.jokerB);
this.moveCardDown(this.jokerB);
this.swapOutsideJoker();
this.cutByBottomCard();
p = this.getTopCardNumber() ?? -1;
} while (p === this.jokerA || p === this.jokerB);
}
}