feat: gerador de chave de instalacao (aluguel/compra) na tela /gerador-chave
Adiciona a segunda rotina recuperada do Chave.exe original: chave de
instalacao (8 caracteres tipicos), ao lado da chave de reativacao ja
existente na mesma tela, com um seletor de tipo.
Algoritmo (confirmado matematicamente contra os 3 exemplos reais
fornecidos, byte a byte):
- codigoCliente * 19 + 31 vira um numero inserido sem cifrar no meio
da chave final.
- A modalidade ("30"=ALUGUEL / "00"=COMPRA) e cifrada com o MESMO
algoritmo/chave interna ja usado na reativacao (soma modular + XOR),
gerando 6 hex (3 bytes: 1 aleatorio + 2 cifrados).
- Chave final = 3 primeiros hex + valor do cliente + 3 ultimos hex.
100% client-side (mesmo js puro da reativacao), nunca grava no banco.
Verifiquei os 3 exemplos (DC45092C, 2E35083D, 3D25090C) reproduzindo
o algoritmo em Python antes de trocar o JS - bateram exatamente.
106/106 testes passando.
This commit is contained in:
@@ -1,15 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% block titulo %}Gerador de Chave de Reativação{% endblock %}
|
||||
{% block titulo %}Gerador de Chave{% endblock %}
|
||||
{% block conteudo %}
|
||||
<div style="max-width:420px;margin:0 auto;">
|
||||
<img src="/static/logo-gti-256x256.png" alt="GTI Grupo" style="width:72px;height:72px;border-radius:14px;display:block;margin:0 auto 16px;">
|
||||
<h1 style="text-align:center;">Gerador de Chave de Reativação</h1>
|
||||
<h1 style="text-align:center;">Gerador de Chave</h1>
|
||||
|
||||
<label for="tipo">Tipo de chave</label>
|
||||
<select id="tipo">
|
||||
<option value="reativacao">Reativação (mês/ano)</option>
|
||||
<option value="instalacao">Instalação (aluguel/compra)</option>
|
||||
</select>
|
||||
|
||||
<label for="codigo">Código do cliente</label>
|
||||
<input type="text" id="codigo" placeholder="Ex: 000123" maxlength="6" inputmode="numeric">
|
||||
|
||||
<label for="referencia">Mês/ano de referência</label>
|
||||
<input type="month" id="referencia" min="2000-01" max="2100-12">
|
||||
<div id="campos-reativacao">
|
||||
<label for="referencia">Mês/ano de referência</label>
|
||||
<input type="month" id="referencia" min="2000-01" max="2100-12">
|
||||
</div>
|
||||
|
||||
<div id="campos-instalacao" style="display:none;">
|
||||
<label for="modalidade">Modalidade</label>
|
||||
<select id="modalidade">
|
||||
<option value="ALUGUEL">Aluguel</option>
|
||||
<option value="COMPRA">Compra</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button id="gerar">Gerar chave</button>
|
||||
|
||||
@@ -23,6 +39,9 @@ const CHAVE_INTERNA =
|
||||
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347" +
|
||||
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ";
|
||||
|
||||
// ==========================
|
||||
// REATIVAÇÃO — mês/cliente/ano cifrados por inteiro (22 caracteres)
|
||||
// ==========================
|
||||
function gerarReativacao(codigoCliente, mes, ano, byteInicial = null) {
|
||||
const cliente = String(codigoCliente).padStart(6, '0');
|
||||
const texto = String(mes).padStart(2, '0') + cliente + String(ano).slice(-2);
|
||||
@@ -40,8 +59,39 @@ function gerarReativacao(codigoCliente, mes, ano, byteInicial = null) {
|
||||
return resultado.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// INSTALAÇÃO — só a modalidade ("30"=ALUGUEL / "00"=COMPRA) é cifrada
|
||||
// (mesmo algoritmo de cifra da reativação, texto diferente); o valor
|
||||
// derivado do código do cliente (cliente×19+31) entra sem cifrar, no
|
||||
// meio dos 6 caracteres cifrados (3 antes + 3 depois).
|
||||
// ==========================
|
||||
function gerarInstalacao(codigoCliente, modalidade, byteInicial = null) {
|
||||
const clienteNum = parseInt(String(codigoCliente), 10) || 0;
|
||||
const valorCliente = clienteNum * 19 + 31;
|
||||
const textoModalidade = modalidade === 'ALUGUEL' ? '30' : '00';
|
||||
|
||||
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
|
||||
let anterior = aleatorio;
|
||||
const bytes = [aleatorio];
|
||||
for (let i = 0; i < textoModalidade.length; i++) {
|
||||
const valor = (textoModalidade.charCodeAt(i) + anterior) % 255;
|
||||
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
|
||||
bytes.push(cifrado);
|
||||
anterior = cifrado;
|
||||
}
|
||||
|
||||
const criptografado = bytes.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
||||
const parte1 = criptografado.substring(0, 3);
|
||||
const parte2 = criptografado.substring(3, 6);
|
||||
return `${parte1}${valorCliente}${parte2}`;
|
||||
}
|
||||
|
||||
const $tipo = document.getElementById('tipo');
|
||||
const $codigo = document.getElementById('codigo');
|
||||
const $referencia = document.getElementById('referencia');
|
||||
const $modalidade = document.getElementById('modalidade');
|
||||
const $camposReativacao = document.getElementById('campos-reativacao');
|
||||
const $camposInstalacao = document.getElementById('campos-instalacao');
|
||||
const $erro = document.getElementById('erro');
|
||||
const $resultado = document.getElementById('resultado');
|
||||
const $copiar = document.getElementById('copiar');
|
||||
@@ -51,6 +101,12 @@ const $copiar = document.getElementById('copiar');
|
||||
$referencia.value = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;
|
||||
})();
|
||||
|
||||
$tipo.addEventListener('change', () => {
|
||||
const ehReativacao = $tipo.value === 'reativacao';
|
||||
$camposReativacao.style.display = ehReativacao ? 'block' : 'none';
|
||||
$camposInstalacao.style.display = ehReativacao ? 'none' : 'block';
|
||||
});
|
||||
|
||||
function mostrarErro(msg) {
|
||||
$erro.textContent = msg;
|
||||
$erro.style.display = 'block';
|
||||
@@ -63,13 +119,19 @@ document.getElementById('gerar').addEventListener('click', () => {
|
||||
const codigoCliente = String($codigo.value || '').trim();
|
||||
if (!/^[0-9]{1,6}$/.test(codigoCliente)) return mostrarErro('Código do cliente deve ter até 6 dígitos numéricos.');
|
||||
const codigoFormatado = codigoCliente.padStart(6, '0');
|
||||
if (!/^\d{4}-\d{2}$/.test($referencia.value)) return mostrarErro('Selecione o mês/ano de referência.');
|
||||
const [anoStr, mesStr] = $referencia.value.split('-');
|
||||
const mesNum = Number(mesStr), anoNum = Number(anoStr);
|
||||
if (!(mesNum >= 1 && mesNum <= 12)) return mostrarErro('Mês fora do intervalo 1-12.');
|
||||
if (!(anoNum >= 2000 && anoNum <= 2100)) return mostrarErro('Ano inválido (use entre 2000 e 2100).');
|
||||
|
||||
const chave = gerarReativacao(codigoFormatado, mesNum, anoNum);
|
||||
let chave;
|
||||
if ($tipo.value === 'reativacao') {
|
||||
if (!/^\d{4}-\d{2}$/.test($referencia.value)) return mostrarErro('Selecione o mês/ano de referência.');
|
||||
const [anoStr, mesStr] = $referencia.value.split('-');
|
||||
const mesNum = Number(mesStr), anoNum = Number(anoStr);
|
||||
if (!(mesNum >= 1 && mesNum <= 12)) return mostrarErro('Mês fora do intervalo 1-12.');
|
||||
if (!(anoNum >= 2000 && anoNum <= 2100)) return mostrarErro('Ano inválido (use entre 2000 e 2100).');
|
||||
chave = gerarReativacao(codigoFormatado, mesNum, anoNum);
|
||||
} else {
|
||||
chave = gerarInstalacao(codigoFormatado, $modalidade.value);
|
||||
}
|
||||
|
||||
$resultado.textContent = chave;
|
||||
$resultado.style.display = 'block';
|
||||
$copiar.style.display = 'inline-block';
|
||||
|
||||
@@ -14,7 +14,8 @@ def test_pagina_gerador_carrega():
|
||||
resposta = client.get("/gerador-chave")
|
||||
assert resposta.status_code == 200
|
||||
assert "gerarReativacao" in resposta.text
|
||||
assert "Gerador de Chave de Reativação" in resposta.text
|
||||
assert "gerarInstalacao" in resposta.text
|
||||
assert "Gerador de Chave" in resposta.text
|
||||
|
||||
|
||||
def test_pagina_gerador_nao_grava_nada():
|
||||
|
||||
Reference in New Issue
Block a user