@sousafrc/cipherkey-lib
Version:
Biblioteca de criptografia do CipherKey
160 lines (159 loc) • 8.24 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCipherKey = exports.sanitize = void 0;
const scrypt_js_1 = require("scrypt-js");
const jssha_1 = __importDefault(require("jssha"));
const bigint_1 = require("./bigint");
const debug_1 = require("debug");
const _debug = (0, debug_1.debug)('cipherkey');
// Variáveis para hashing Scrypt.
const CPU_COST = 1 << 15; // 32768
const BLOCK_SIZE = 8;
const PARALLELIZATION_COST = 1;
const ALLOWED_CHARACTERS = '@#$%&*._!0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const UPPERCASE_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const LOWERCASE_LETTERS = 'abcdefghijklmnopqrstuvwxyz';
const NUMBERS = '1234567890';
const VALID_SYMBOLS = '@#$%&*._!';
// Recebe uma string e retorna um array de bytes.
const encodeUtf8 = (str) => {
return new TextEncoder().encode(str);
};
// Remove números ou símbolos do CipherKey.
const sanitize = (cipherkey, no_symbols, no_numbers) => {
const _ = _debug.extend('sanitize');
// Inicializa o SHAKE256 para o PRNG.
/* DEBUG */ _('Inicializando SHAKE256');
const prng_obj = new jssha_1.default('SHAKE256', 'TEXT', { encoding: 'UTF8' });
// Converte o CipherKey para um Array de Strings.
/* DEBUG */ _('Dividindo a senha por caracteres');
const cipherkey_array = cipherkey.split('');
// Gera um número aleatório de 0 ao tamanho do alfabeto - 1.
const generateIndex = (hashThis) => {
prng_obj.update(hashThis);
// Mesmo processo que generateIndex() em generateCipherKey().
const prng = prng_obj.getHash('UINT8ARRAY', { outputLen: 256 });
const result = (0, bigint_1.intArrToBigint)(prng);
return Number(result % BigInt(ALPHABET.length));
};
// Se os símbolos precisam ser removidos.
if (no_symbols) {
/* DEBUG */ _('no_symbols é verdadeiro, removendo símbolos');
for (let i = 0; i < cipherkey_array.length; i++) {
// Se o caractere é um símbolo.
if (VALID_SYMBOLS.includes(cipherkey_array[i])) {
// Gera um índice aleatório usando (index + symbol) e substitui o símbolo por um caractere do alfabeto no índice gerado.
cipherkey_array[i] = ALPHABET[generateIndex(i.toString() + cipherkey_array[i])];
}
}
}
// Se os números precisam ser removidos.
if (no_numbers) {
/* DEBUG */ _('no_numbers é verdadeiro, removendo números');
for (let i = 0; i < cipherkey_array.length; i++) {
// Se o caractere é um número.
if (parseInt(cipherkey_array[i]) >= 0 && parseInt(cipherkey_array[i]) <= 9) {
// Gera um índice aleatório usando (index + number) e substitui o número por um caractere do alfabeto no índice gerado.
cipherkey_array[i] = ALPHABET[generateIndex(i.toString() + cipherkey_array[i])];
}
}
}
// Retorna o CipherKey como uma string.
/* DEBUG */ _('Retornando o array de caracteres do CipherKey como uma string');
return cipherkey_array.join('');
};
exports.sanitize = sanitize;
const generateCipherKey = async (to_hash, cipherkey_length, website, username) => {
const _ = _debug.extend('generateCipherKey');
/* DEBUG */ _('Inicializando SHAKE256 e SHA3-512');
// Inicializa o SHAKE256 para geração de números pseudoaleatórios.
const sha_obj = new jssha_1.default('SHAKE256', 'UINT8ARRAY');
// Inicializa o SHA3-512.
const sha3_obj = new jssha_1.default('SHA3-512', 'TEXT', { encoding: 'UTF8' });
// Converte o objeto JSON to_hash para seu próprio hash SHA3-512 em formato HEX.
/* DEBUG */ _('Fazendo o hash de to_hash');
sha3_obj.update(to_hash);
to_hash = sha3_obj.getHash('HEX');
// Gera Hash Scrypt.
/* DEBUG */ _('Gerando o Hash Scrypt do to_hash já hasheado, concatenando e salgando website + username');
const scrypt_hash = await (0, scrypt_js_1.scrypt)(encodeUtf8(to_hash), encodeUtf8(website + username), CPU_COST, BLOCK_SIZE, PARALLELIZATION_COST, 32);
/* DEBUG */ _('Hash Scrypt gerado');
// Gera um número aleatório de 0 a modulo - 1.
const generateIndex = (modulo) => {
// Alimenta o scrypt_hash como seed para o SHAKE256.
sha_obj.update(scrypt_hash);
// PRNG é um array de 256 bits gerado pelo SHAKE256.
const prng = sha_obj.getHash('UINT8ARRAY', { outputLen: 256 });
// Converte o array de 256 bits em um BigInt de 256 bits.
const result = (0, bigint_1.intArrToBigint)(prng);
return Number(result % BigInt(modulo));
};
// Gera um índice aleatório de 0 a characterSet.length e retorna o caractere nesse índice.
const pickCharacter = (char_set) => {
return char_set[generateIndex(char_set.length)];
};
// Gera Array: 0,1,2,3,4,5,6...cipherkey_length - 1.
let pick_index = [];
// Verificar https://jsben.ch/3YHpR
// O Chrome parece lidar com operações de array de forma significativamente mais rápida do que o Firefox, então usar Array.from() com um "iterável" personalizado é uma boa ideia em termos de desempenho, mesmo que seja negligente.
if (globalThis?.navigator?.userAgent?.includes('Firefox')) {
/* DEBUG */ _('Firefox detectado, usando Array.from com length e modificador de entrada');
pick_index = Array.from({ length: cipherkey_length }, (_, i) => i);
}
else {
/* DEBUG */ _('Não é o Firefox, usando o método "Set tmp[i] = i" dentro do loop for');
for (let i = 0; i < cipherkey_length; i++) {
// Definir o índice é muito mais rápido do que adicionar ao array, e como estamos operando em ordem, é relativamente seguro fazer isso.
pick_index[i] = i;
}
}
/* DEBUG */ _('Gerando a CipherKey a partir do alfabeto');
// Gera um índice aleatório de 0 a pickIndex.length.
let remove_index = generateIndex(pick_index.length);
// index1 é o elemento no índice gerado e o valor está entre 0 e cipherkey_length - 1.
const index1 = pick_index[remove_index];
// Remove esse elemento do array pickIndex.
// Nunca podemos escolher o mesmo elemento de pickIndex novamente e o tamanho de pickIndex diminui em 1.
pick_index.splice(remove_index, 1);
// Repete mais 3 vezes para index2, index3 e index4.
remove_index = generateIndex(pick_index.length);
const index2 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
remove_index = generateIndex(pick_index.length);
const index3 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
remove_index = generateIndex(pick_index.length);
const index4 = pick_index[remove_index];
pick_index.splice(remove_index, 1);
let cipherkey = '';
// Constrói uma CipherKey de 0 a cipherkey_length - 1.
for (let i = 0; i < cipherkey_length; i++) {
if (i === index1) {
// Se index1, escolha um caractere de letra minúscula.
cipherkey += pickCharacter(LOWERCASE_LETTERS);
}
else if (i === index2) {
// Se se index2, escolha um caractere de letra maiúscula.
cipherkey += pickCharacter(UPPERCASE_LETTERS);
}
else if (i === index3) {
// Se index3, escolha um caractere de símbolo válido.
cipherkey += pickCharacter(VALID_SYMBOLS);
}
else if (i === index4) {
// Se index4, escolha um caractere de número.
cipherkey += pickCharacter(NUMBERS);
}
else {
// Escolha um caractere de qualquer tipo se não for index1, index2, index3 ou index4.
cipherkey += pickCharacter(ALLOWED_CHARACTERS);
}
}
/* DEBUG */ _('Retornando a CipherKey');
return cipherkey;
};
exports.generateCipherKey = generateCipherKey;