fix(seguranca): move algoritmo de geracao de chave do JS pro backend
O algoritmo (CHAVE_INTERNA + funcoes gerarReativacao/gerarInstalacao) ficava 100% legivel no codigo-fonte da pagina — qualquer pessoa com acesso ao painel conseguia abrir o DevTools e extrair a logica pra gerar chaves de licenca fora do sistema. Move tudo pra app/gerador_chave.py (roda so no servidor) e expoe via POST /gerador-chave/gerar (autenticado, protegido por CSRF). A pagina agora so manda os dados do formulario e mostra o resultado que volta do backend. Algoritmo verificado byte-a-byte contra os 3 exemplos reais conhecidos (cliente 000001, ALUGUEL). Bonus: o byte inicial agora usa secrets.randbelow (CSPRNG) em vez de Math.random() do JS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a3a84abd37
commit
5c577a644a
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Algoritmo de geração de chave (reativação e instalação), reverso-projetado
|
||||
do sistema legado (Delphi). Fica só aqui no backend — NUNCA deve voltar a
|
||||
ser exposto em JavaScript de página, porque isso permite qualquer pessoa
|
||||
com acesso ao painel extrair o algoritmo e gerar chaves fora do sistema.
|
||||
|
||||
Verificado byte-a-byte contra 3 exemplos reais de chave de instalação
|
||||
(cliente 000001, ALUGUEL): byte inicial 0xDC -> "DC45092C",
|
||||
0x2E -> "2E35083D", 0x3D -> "3D25090C" (ver testes).
|
||||
"""
|
||||
import secrets
|
||||
|
||||
CHAVE_INTERNA = (
|
||||
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347"
|
||||
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ"
|
||||
)
|
||||
|
||||
|
||||
def _cifrar(texto: str, byte_inicial: int) -> list[int]:
|
||||
"""Cifra por feedback de byte anterior + XOR com CHAVE_INTERNA rotativa.
|
||||
Retorna a lista de bytes cifrados (sem o byte_inicial na frente)."""
|
||||
anterior = byte_inicial
|
||||
cifrados = []
|
||||
for i, ch in enumerate(texto):
|
||||
valor = (ord(ch) + anterior) % 255
|
||||
cifrado = valor ^ ord(CHAVE_INTERNA[i % len(CHAVE_INTERNA)])
|
||||
cifrados.append(cifrado)
|
||||
anterior = cifrado
|
||||
return cifrados
|
||||
|
||||
|
||||
def gerar_reativacao(codigo_cliente: str, mes: int, ano: int, byte_inicial: int | None = None) -> str:
|
||||
"""Chave de reativação — mês/cliente/ano cifrados por inteiro (22 caracteres hex)."""
|
||||
cliente = str(codigo_cliente).zfill(6)
|
||||
texto = str(mes).zfill(2) + cliente + str(ano)[-2:]
|
||||
aleatorio = byte_inicial if byte_inicial is not None else secrets.randbelow(256)
|
||||
cifrados = _cifrar(texto, aleatorio)
|
||||
return "".join(f"{v:02X}" for v in [aleatorio, *cifrados])
|
||||
|
||||
|
||||
def gerar_instalacao(codigo_cliente: str, modalidade: str, byte_inicial: int | None = None) -> str:
|
||||
"""Chave de instalação — só a modalidade ("30"=ALUGUEL / "00"=COMPRA) é
|
||||
cifrada; o valor derivado do código do cliente (cliente*19+31) entra sem
|
||||
cifrar, no meio dos 6 caracteres cifrados (3 antes + 3 depois)."""
|
||||
try:
|
||||
cliente_num = int(str(codigo_cliente))
|
||||
except ValueError:
|
||||
cliente_num = 0
|
||||
valor_cliente = cliente_num * 19 + 31
|
||||
texto_modalidade = "30" if modalidade == "ALUGUEL" else "00"
|
||||
|
||||
aleatorio = byte_inicial if byte_inicial is not None else secrets.randbelow(256)
|
||||
cifrados = _cifrar(texto_modalidade, aleatorio)
|
||||
criptografado = "".join(f"{v:02X}" for v in [aleatorio, *cifrados])
|
||||
parte1, parte2 = criptografado[0:3], criptografado[3:6]
|
||||
return f"{parte1}{valor_cliente}{parte2}"
|
||||
Reference in New Issue
Block a user