Se você já ouviu que números primos são importantes na criptografia, mas nunca entendeu direito o motivo, relaxa: você não está sozinho.
Durante muito tempo, isso também parecia abstrato demais para mim. Afinal, uma coisa é ouvir que “os primos são fundamentais para a segurança digital”. Outra, bem diferente, é entender como isso vira código de verdade, como o navegador participa disso e como o servidor consegue descriptografar a mensagem depois.
Foi justamente por isso que resolvi montar um exemplo simples, didático e sem firula. E, neste artigo, eu vou te mostrar exatamente isso. Depois de entendermos “criptografia RSA” vou mostrar como fazer na prática.
A ideia aqui não é complicar. Pelo contrário. Quero te explicar como os números primos na criptografia fazem sentido na prática, usando um exemplo com JavaScript no front-end e PHP no back-end.
Antes de tudo: o que são números primos?
De forma bem direta, número primo é aquele que só pode ser dividido por 1 e por ele mesmo.
Por exemplo: [2, 3, 5, 7, 11, 13, 17, 19, … ]
Curiosidade: Até a data de escrita deste post, o maior número primo conhecido foi descorberto em outubro de 2024, o maior número primo conhecido é um número primo de Mersenne com 41 024 320 algarismos.
Até aí, tudo bem. No entanto, a parte interessante começa quando esses números entram em um tipo específico de conta que é fácil de fazer, mas difícil de desfazer.
E é justamente aí que mora a mágica da criptografia RSA.
Por que os números primos são importantes na criptografia?
A lógica é simples.
Se eu pegar dois números primos e multiplicar, o resultado sai rápido.
Por exemplo:
- 17 × 19 = 323
Agora, se eu te entregar apenas o número 323 e perguntar quais primos geraram esse valor, você até consegue descobrir. Só que já ficou mais difícil.
Com números pequenos, isso é tranquilo. Porém, quando estamos falando de números gigantescos, com centenas ou milhares de bits, descobrir os fatores primos se torna um problema matemático muito mais pesado.
Em outras palavras:
- multiplicar dois primos é fácil
- descobrir os primos a partir do resultado é difícil
E essa diferença de dificuldade é uma das bases do RSA, um dos algoritmos mais conhecidos da criptografia assimétrica.
O que é RSA?
O RSA é um sistema de criptografia que trabalha com duas chaves:
- chave pública
- chave privada
A chave pública pode ficar exposta. Sem problema.
Já a chave privada precisa ficar guardada no servidor.
Na prática, funciona mais ou menos assim:
- o navegador recebe ou já possui a chave pública;
- a mensagem é criptografada no front-end;
- os dados são enviados ao servidor;
- o servidor usa a chave privada para descriptografar.
Ou seja, uma pessoa consegue “trancar” a mensagem com a chave pública, mas só quem tem a chave privada consegue “abrir”.
As variáveis do RSA, traduzidas sem enrolação
Quando a gente começa a estudar RSA, aparecem algumas letras que assustam. Só que, na verdade, elas são bem mais simples do que parecem.
p = Primeiro número primo secreto.
q = Segundo número primo secreto.
n = Resultado da multiplicação de p × q.
φ(n) = Lê-se “fi de ene”. É um valor auxiliar usado para montar a relação matemática entre a chave pública e a privada.
Quando p e q são primos:
φ(n) = (p - 1)(q - 1)
e = Expoente público. Faz parte da chave pública.
d = Expoente privado. Faz parte da chave privada.
m = Mensagem original.
c = Mensagem criptografada.
Em resumo, fica assim:
peq: primos secretosn: produto dos doisφ(n): valor interno de apoioe: número públicod: número privadom: mensagemc: mensagem criptografada
Exemplo simples de RSA com números pequenos
Para não transformar este artigo em uma aula impossível de acompanhar, eu usei números pequenos só para fins didáticos.
Escolhi:
p = 17q = 19
Então:
n = p × q = 323
Depois:
φ(n) = (17 - 1)(19 - 1) = 16 × 18 = 288
Agora escolhemos um valor público:
e = 5
A partir daí, precisamos descobrir d, que é o número que satisfaz esta condição:
e × d ≡ 1 mod φ(n)
Substituindo:
5 × d ≡ 1 mod 288
O valor correto é:
d = 173
Inclusive, esse ponto é importante. Em uma conta inicial, eu havia chegado a um valor errado. Depois, revisando a matemática, o valor certo ficou claro: 173.
E isso é bom porque mostra uma coisa importante: em criptografia, um detalhe errado derruba o sistema inteiro. Não existe “quase certo”.
Inclusive a forma correta de se chegar a d é usando o raciocínio “Aritmética do relógio”.
Nesse caso, imagine um relógio com 288 horas. Se você der x voltas de 5 horas cada, você quer parar exatamente na hora 1.
Como o 5 e o 288 são “primos entre si” (não compartilham divisores além do 1), sabemos que existe uma resposta única para x dentro desse intervalo.
O Método Prático
Para resolver isso, queremos que 5x seja igual a algum múltiplo de 288 mais 1. Ou seja: 5x = 288k + 1
Vamos testar valores para k (número de voltas no “relógio” de 288):
- Se k = 1: 288 x 1 + 1 = 289.
- 289 é divisível por 5? Não (termina em 9).
- Se k = 2: 288 x 2 + 1 = 576 + 1 = 577.
- 577 é divisível por 5? Não (termina em 7).
- Se k = 3: 288 x 3 + 1 = 864 + 1 = 865.
- 865 é divisível por 5? Sim! (termina em 5).
O Resultado
Agora, basta dividir o resultado por 5:
Resposta: .
Então a chave fica assim
Chave pública
e = 5n = 323
Chave privada
d = 173n = 323
Perceba uma coisa: o valor n aparece nas duas chaves. O que muda mesmo é o expoente.
Como a mensagem vira números?
Aqui entra um ponto que costuma confundir muita gente.
Você não criptografa texto puro diretamente como se o computador entendesse letras do mesmo jeito que a gente entende. Na prática, a mensagem precisa ser transformada em números.
No meu exemplo, a solução mais limpa foi trabalhar com bytes UTF-8.
Então, em vez de inventar uma tabela tipo:
- a = 1
- b = 2
- c = 3
eu deixei o navegador converter o texto em bytes.
Por exemplo, a letra A vira 65. Já um caractere com acento pode virar mais de um byte, porque depende da codificação UTF-8.
Isso é melhor porque deixa o exemplo mais próximo do mundo real e permite trabalhar com texto normal, incluindo acentos.
Como o JavaScript criptografa a mensagem
No front-end, o processo foi este:
- pegar o texto digitado no input;
- converter para bytes com
TextEncoder; - aplicar a fórmula do RSA em cada byte;
- enviar os números para o PHP.
A fórmula usada para criptografar é:
c = m^e mod n
Ou seja, para cada byte da mensagem, o JavaScript faz uma exponenciação modular usando a chave pública.
Na prática, a chave pública pode ficar no JavaScript sem problema. Isso, por si só, não é falha. O que não pode vazar é a chave privada.
Como o PHP descriptografa a mensagem
Do outro lado, no servidor, o PHP recebe a sequência numérica e faz o caminho inverso.
A fórmula da descriptografia é:
m = c^d mod n
Então, para cada valor criptografado, o PHP aplica a chave privada, recupera o byte original e depois reconstrói a string.
Ou seja, o fluxo completo fica assim:
- usuário digita a mensagem;
- o JavaScript criptografa no navegador;
- o servidor recebe os números;
- o PHP descriptografa com a chave privada;
- a mensagem original volta a aparecer.
Onde os números primos entram nisso tudo?
Agora a resposta fica muito mais clara.
Os números primos na criptografia são importantes porque ajudam a montar uma estrutura matemática em que:
- é fácil gerar a chave;
- é fácil criptografar;
- é fácil descriptografar para quem tem a chave privada;
- mas é difícil quebrar o sistema sem as informações secretas.
No RSA, os primos p e q são a base de tudo. A segurança nasce justamente do fato de que fatorar o produto de dois primos grandes é um problema difícil.
Claro: no meu exemplo, usei números pequenos. Portanto, ele serve para estudar, não para proteger dados reais.
Este exemplo é seguro?
NÃO. E aqui eu preciso ser honesto.
Esse tipo de implementação é didática, não profissional para produção.
Ela tem várias limitações:
- usa números muito pequenos;
- criptografa byte por byte;
- não usa padding;
- não substitui HTTPS;
- não deve ser usada para proteger dados reais.
Se você quiser segurança de verdade, o caminho correto é outro:
- usar HTTPS/TLS;
- usar OpenSSL ou libsodium;
- trabalhar com criptografia híbrida;
- deixar a troca de chaves e a proteção do tráfego nas mãos de soluções maduras.
Ou seja, estudar RSA na unha é excelente para entender o conceito. Mas inventar criptografia própria em produção é um erro.
Então vale a pena estudar isso?
Com certeza.
Inclusive, eu diria que entender RSA, chave pública, chave privada e o papel dos números primos ajuda muito a amadurecer como desenvolvedor.
Primeiro, porque você deixa de tratar segurança como magia.
Segundo, porque começa a enxergar melhor o que o navegador, o servidor e os protocolos realmente estão fazendo por baixo dos panos.
E, por fim, porque entender a base te ajuda a tomar decisões melhores na hora de projetar sistemas reais.
Conclusão
Quando alguém diz que os números primos são importantes para a criptografia, isso não é papo acadêmico solto. Existe uma aplicação prática e concreta por trás.
No RSA, os primos ajudam a criar uma estrutura matemática que permite:
- expor uma chave pública;
- manter outra privada;
- criptografar dados no cliente;
- descriptografar no servidor.
No meu exemplo, eu usei JavaScript no front-end e PHP no back-end para mostrar isso de forma simples. Não é um sistema seguro para produção, mas é um ótimo laboratório mental para entender o mecanismo.
E, sinceramente, quando a ficha cai, a criptografia para de parecer um bicho de sete cabeças.
Ela continua complexa, claro. Só que deixa de ser abstrata.
Se você curte desenvolvimento web, segurança, PHP, JavaScript e quer entender melhor o que realmente acontece nos bastidores, estudar esse tipo de exemplo vale muito a pena.
Criando criptografia RSA na prática
Para nosso exemplo você precisa ter PHP instalado em sua máquina, caso contrário não vai funcionar.
1º vamos criar o nosso index.html, ele vai ser a base para criptografar a mensagem e enviar para o servidor descriptografar.
Crie uma pasta chamada, por exemplo, “cripyt_rsa” e dentro dela um arquivo “index.html” e cole o código abaixo.
Para facilitar, vou usar a lib Bootstrap, assim a gente não perde tempo com HTML/CSS
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Criptografia RSA - Versão de brinquedo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="./">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container my-5">
<h1>RSA didático: JS criptografa e PHP descriptografa</h1>
<div class="col-lg-8 px-0">
<p class="fs-5"></p>
<p>Sistema de criptografia RSA para estudos - Versão de brinquedo.</p>
<hr class="col-1 my-4">
<form id="cryptoForm" action="decrypt.php" method="POST">
<div class="mb-3">
<label for="message" class="form-label">Texto a ser criptografado</label>
<input type="text" class="form-control" id="message" name="inputText" required>
</div>
<input type="hidden" name="encrypted_message" id="encrypted_message">
<button type="submit" class="btn btn-primary">Criptografar e Enviar</button>
</form>
<div class="box">
<strong>Chave pública no JavaScript:</strong>
<div><code>e = 5, n = 323</code></div>
</div>
<div class="box">
<strong>Prévia da mensagem criptografada:</strong>
<div id="preview">(ainda vazia)</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="main.js"></script>
</body>
</html>
2º Crie o arquivo main.js
Reposável por coletar a mensagem do formulário, criptografar e enviar para o servidor
// Chave pública
const publicKey = {
e: 5,
n: 323
};
/**
* Calcula (base^exp) % mod usando o método de exponenciação rápida.
*
* @param mixed base
* @param mixed exp
* @param mixed mod
*
* @return [type]
*
*/
function modPow(base, exp, mod) {
let result = 1;
base = base % mod;
while (exp > 0) {
if (exp % 2 === 1) {
result = (result * base) % mod;
}
exp = Math.floor(exp / 2);
base = (base * base) % mod;
}
return result;
}
function encryptText(text) {
// converte texto em bytes UTF-88
const encoder = new TextEncoder();
const bytes = Array.from(encoder.encode(text));
// Criptografa byte por byte: c = (m^e) % n
return bytes.map(byte => modPow(byte, publicKey.e, publicKey.n));
}
document.getElementById('cryptoForm').addEventListener('submit', function(event) {
event.preventDefault();
const messageInput = document.getElementById('message');
const hiddenInput = document.getElementById('encrypted_message');
const preview = document.getElementById('preview');
const text = messageInput.value;
try {
const encryptedArray = encryptText(text);
const payload = JSON.stringify(encryptedArray);
hiddenInput.value = payload;
preview.textContent = `Encrypted Message: ${payload}`;
this.submit();
} catch (error) {
console.error("Error encrypting message:", error);
alert("An error occurred while encrypting the message. Please try again.");
}
});
3º Agora crie o arquivo decrypt.php
Esse, por sua vez, vai receber a mensagem, descriptografar e mostrar a mensgem original.
<?php
$privateKey = [
'd' => 29,
'n' => 323,
];
function modPow(int $base, int $exp, int $mod): int
{
$result = 1;
$base = $base % $mod;
while ($exp > 0) {
if ($exp % 2 === 1) {
$result = ($result * $base) % $mod;
}
$exp = intdiv($exp, 2);
$base = ($base * $base) % $mod;
}
return $result;
}
$rawEncrypted = $_POST['encrypted_message'] ?? '[]';
$encryptedArray = json_decode($rawEncrypted, true);
$error = null;
$decryptedText = '';
$decryptedBytes = [];
if (!is_array($encryptedArray)) {
$error = 'Payload inválido.';
$encryptedArray = [];
} else {
foreach ($encryptedArray as $item) {
if (!is_numeric($item)) {
$error = 'Payload contém valores inválidos.';
break;
}
$c = (int) $item;
// m = c^d mod n
$m = modPow($c, $privateKey['d'], $privateKey['n']);
if ($m < 0 || $m > 255) {
$error = 'Byte descriptografado fora do intervalo válido.';
break;
}
$decryptedBytes[] = $m;
}
}
if (!$error) {
if (!empty($decryptedBytes)) {
$decryptedText = pack('C*', ...$decryptedBytes);
} else {
$decryptedText = '';
}
}
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Criptografia RSA - Versão de brinquedo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="./">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container my-5">
<h1>Resultado no servidor PHP</h1>
<div class="alert alert-warning">
<strong>Chave privada no PHP:</strong>
<div><code>d = 29, n = 323</code></div>
</div>
<div class="alert alert-primary">
<strong>Mensagem criptografada recebida:</strong>
<div><?= htmlspecialchars($rawEncrypted, ENT_QUOTES, 'UTF-8') ?></div>
</div>
<?php if ($error): ?>
<div class="alert alert-danger">
<strong>Erro:</strong>
<div><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
</div>
<?php else: ?>
<div class="alert alert-success">
<strong>Bytes descriptografados:</strong>
<div><?= htmlspecialchars(json_encode($decryptedBytes), ENT_QUOTES, 'UTF-8') ?></div>
</div>
<div class="alert alert-info">
<strong>Mensagem descriptografada:</strong>
<div><?= htmlspecialchars($decryptedText, ENT_QUOTES, 'UTF-8') ?></div>
</div>
<?php endif; ?>
<p style="margin-top: 20px;">
<a class="btn btn-success" href="index.php">Voltar</a>
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
</body>
</html>
Basicamente é isso pessoal. Estude esse código, melhore, faça do seu jeito. Existe muitas aplicações para isso, mas tenha bastante cuidado porque isso é só a pontinha do iceberg da criptografia.
