ts-sql-query
Version:
Type-safe SQL query builder like QueryDSL or JOOQ in Java or Linq in .Net for TypeScript with MariaDB, MySql, Oracle, PostgreSql, Sqlite and SqlServer support.
219 lines (218 loc) • 9.2 kB
JavaScript
;
/**
* IDEncrypter allows to encrypt and decrypt a bigint using the aes-128-ctr
* and complemented with some checksum validations:
* - one checksum included inside the encrypted data
* - a second one public, at the end of the returned string
*
* IDEncrypter returns a URL-safe base64-like string of 16 chars; that string
* is composed of the chars: [A-Za-z0-9] - _ .
* The last two chars represent a public checksum that you can validate using the
* function isValidEncryptedID. If you copy the functions isValidEncryptedID and
* checksumString outside of this file, you can use them in the user interface side.
*
* IDEncrypter constructor requires two strings of 16 chars of [A-Za-z0-9].
* These strings work as a template for the passwords required during the encrypting
* process. These strings are shuffles in order to generate very different encrypted
* id even when they are sequential.
*
* For example, for: new IDEncrypter('3zTvzr3p67VC61jm', '60iP0h6vJoEaJo8c')
* There are listed some numbers with the encrypted correspondent value:
*
* Without prefix | With prefix: 'co'
* ID Encrypted ID | Encrypted ID ID
* 1n uftSdCUhUTBQ0111 | coFJL3xTJZvP6Kd30d 1n
* 2n dmY1mZ8zdxsw0210 | coXnBpNk2GaRnwd40c 2n
* 3n RYG2E7kLCEQh030b | cogvVpexP66WnXd512 3n
* 4n YAuzxMU1mdYn0408 | coYR20bAjjtT3Yd610 4n
* 5n BQjHWTD6_ulK0507 | coAkgqXiypO9Wsd70e 5n
* 6n J_BFtuk1cz1D0609 | copLy6ZQrq6aktd807 6n
* 7n EHT8AO2zDvi0070d | coFA-nlK3evyRed90a 7n
* 8n pd3iGJLINuEC0811 | coJaspx_Bi47BZda0e 8n
* 9n Q3qCqYo7hGUP0909 | coDqHHZ_1MLgSldb0a 9n
* 10n uftSdCUhUTtQ010d | coFJL3xTJZvPWKd30a 10n
* 1678954n VoQvLVEEAyqk280b | coJyEYGGtpm-m5fa11 1678954n
* 1678955n LLdjjJzrxIEc2909 | coAfwfrdzjN0-pfb0b 1678955n
* 1678956n w_EEBjsfTlWs2a0d | coa3UoLoopbf4lfc0d 1678956n
* 1678957n hWIKAYeoyBhs2b11 | coVBfzQXg6H1Jwfd0f 1678957n
* 1678958n isfB5yGaL2Zy2c0a | cogAmxyPGYYlgmfe0e 1678958n
* 1678959n KPVmBdcpMaGq2d0b | coWBdisK5-GT_Tff11 1678959n
* 1678960n TP4jFWNGkf9M250c | cokPI55Y1We2Znf70d 1678960n
* 1678961n o4VoK2Eb2FMQ2608 | co9VkYrYEr3RNxf80b 1678961n
* 1678962n 5zI__UMdYO7d270c | coTq_qC7JLcCGVf913 1678962n
* 1678963n VoQvLVEEAzOk280e | coJyEYGGtpm_C5fa10 1678963n
* 1678964n LLdjjJzrxJ4c290b | coAfwfrdzjN1Cpfb10 1678964n
* 9223372036854775707n bEG1TRHA6k0P5718 | coU0icc2MNpXEc290c 9223372036854775707n
* 9223372036854775708n jsh8ZwcIP43X5808 | covA77-cT5L6Ws2a0c 9223372036854775708n
* 9223372036854775709n atgiDIJ_-vcJ5910 | co-p31_nhOqehs2b0e 9223372036854775709n
* 9223372036854775710n PloRFyQJQ2hI510c | cot1a7Hhycn98x2311 9223372036854775710n
* 9223372036854775711n cLNdf2e01Q-U5209 | coLV6Cv_kEuUsO240c 9223372036854775711n
* 9223372036854775712n sw93YzZF-Exw5309 | coMwHc6pyg8C9M250d 9223372036854775712n
* 9223372036854775713n H6UG5ukZZv9i5408 | co3HqX1J79uYMQ260d 9223372036854775713n
* 9223372036854775714n caogdvGry6mN550c | comM3AArz7AT7d2711 9223372036854775714n
* 9223372036854775715n l-qteIF3fLyB560f | coKXvQ0q7iYuOk280e 9223372036854775715n
* 9223372036854775716n bEG1TRHA6nIP5707 | coU0icc2MNpU4c290a 9223372036854775716n
*
* Be aware: If you try to order the encrypted id, you will not get
* the same order if you order the id.
*
* IDEncrypter support int64/long numbers generating an encrypted string
* of 16 chars (plus prefix if you specify one); if you introduce numbers bigger
* than the maximum of int64 (9_223_372_036_854_775_807) you will get encrypted
* string even longer.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidEncryptedID = exports.IDEncrypter = void 0;
const crypto = require("crypto");
const algorithm = 'aes-128-ctr';
const ENC = {
'+': '-',
'/': '_',
'=': '.'
};
const DEC = {
'-': '+',
_: '/',
'.': '='
};
class IDEncrypter {
/**
* Create a new password encrypter
*
* @param password Must be a string of 16 chars of [A-Za-z0-9]
* @param initializationVector Must be a string of 16 chars of [A-Za-z0-9]
*/
constructor(password, initializationVector) {
this.password = password;
this.initializationVector = initializationVector;
}
/**
* Encrypt the ID and returns an URL-safe base64
*
* @param id ID to encrypt
*/
encrypt(id, prefix = '') {
let idHex = id.toString(16);
if (idHex.length < 16) {
idHex = '0'.repeat(16 - idHex.length) + idHex;
}
const cs = checksumValue(id, prefix);
let csHex = cs.toString(16);
if (csHex.length < 2) {
csHex = '0' + csHex;
}
const hex = idHex + csHex;
const cipher = crypto.createCipheriv(algorithm, this.shufflePassword(cs), this.shuffleIV(cs));
const buffer = Buffer.from(hex, 'hex');
const encrypted = cipher.update(buffer);
const final = cipher.final();
// Transform to base64 url safe
const result = Buffer.concat([encrypted, final]).toString('base64').replace(/[+/=]/g, c => ENC[c]) + csHex;
return prefix + result + checksumString(result, prefix);
}
/**
* Decrypt an ID
*
* @param encryptedID ID encrypted
*/
decrypt(encryptedID, prefix = '') {
let encrypted = encryptedID;
if (encrypted.length < (16 + prefix.length)) {
throw new Error('Invalid id: ' + encryptedID);
}
if (!encrypted.startsWith(prefix)) {
throw new Error('Invalid prefix for id: ' + encryptedID);
}
encrypted = encrypted.substring(prefix.length);
const csString = encrypted.substring(encrypted.length - 2);
encrypted = encrypted.substring(0, encrypted.length - 2);
if (checksumString(encrypted, prefix) !== csString) {
throw new Error('Invalid id: ' + encryptedID);
}
const pcsHex = encrypted.substring(encrypted.length - 2);
const pcs = BigInt('0x' + pcsHex);
encrypted = encrypted.substring(0, encrypted.length - 2);
// Transform from base64 url safe
encrypted = encrypted.replace(/[-_.]/g, c => DEC[c]);
const decipher = crypto.createDecipheriv(algorithm, this.shufflePassword(pcs), this.shuffleIV(pcs));
const buffer = Buffer.from(encrypted, 'base64');
const decrypted = decipher.update(buffer);
const final = decipher.final();
const hex = Buffer.concat([decrypted, final]).toString('hex');
const icsHex = hex.substring(hex.length - 2);
const idHex = hex.substring(0, hex.length - 2);
const ics = BigInt('0x' + icsHex);
const id = BigInt('0x' + idHex);
const cs = checksumValue(id, prefix);
if (ics !== cs || pcs !== cs) {
throw new Error('Invalid id: ' + encryptedID);
}
return id;
}
shufflePassword(cs) {
const password = this.password;
const n = Number(cs) % password.length;
return password.substring(n) + password.substring(0, n);
}
shuffleIV(cs) {
const iv = this.initializationVector;
const n = Math.floor(Number(cs) / iv.length);
return iv.substring(n) + iv.substring(0, n);
}
}
exports.IDEncrypter = IDEncrypter;
function checksumValue(value, prefix) {
let sum = 0n;
if (prefix) {
let charSum = 0;
for (let i = 0; i < prefix.length; i++) {
charSum += prefix.charCodeAt(i);
}
sum = BigInt(charSum);
}
while (value) {
sum += value % 10n;
value = value / 10n;
}
return sum % 256n;
}
/*
* If you copy the next functions to the user interface, you can validate if
* an encrypted id looks ok without decrypting it. This validation is useful
* if, for some reason, you need the user input the encrypted id, allowing cath
* wrong inputs from the user without sending the information to the server.
*/
function checksumString(value, prefix) {
let charSum = 0;
if (prefix) {
for (let i = 0; i < prefix.length; i++) {
charSum += prefix.charCodeAt(i);
}
}
for (let i = 0; i < value.length; i++) {
charSum += value.charCodeAt(i);
}
let sum = 0;
while (charSum) {
sum += charSum % 10;
charSum = Math.floor(charSum / 10);
}
let hex = (sum % 256).toString(16);
if (hex.length < 2) {
hex = '0' + hex;
}
return hex;
}
function isValidEncryptedID(encryptedID, prefix = '') {
let encrypted = encryptedID;
if (encrypted.length < (16 + prefix.length)) {
return false;
}
const csString = encrypted.substring(encrypted.length - 2);
encrypted = encrypted.substring(0, encrypted.length - 2);
if (checksumString(encrypted, prefix) !== csString) {
return false;
}
return true;
}
exports.isValidEncryptedID = isValidEncryptedID;