Neste tutorial, daremos uma olhada na API de criptografia da Web : uma interface de criptografia de dados do lado do cliente. Este tutorial é baseado neste artigo . Presume-se que você esteja familiarizado com a criptografia.
O que exatamente vamos fazer? Vamos escrever um servidor simples que aceitará dados criptografados do cliente e os devolveremos mediante solicitação. Os próprios dados serão processados no lado do cliente.
O servidor será implementado em Node.js usando Express, o cliente em JavaScript. O bootstrap será usado para estilização.
O código do projeto está aqui .
Se você estiver interessado, por favor me siga.
Treinamento
Crie um diretório
crypto-tut
:
mkdir crypto-tut
Vamos lá e inicializamos o projeto:
cd crypto-tut
npm init -y
Instale
express
:
npm i express
Instale
nodemon
:
npm i -D nodemon
Editando
package.json
:
"main": "server.js",
"scripts": {
"start": "nodemon"
},
Estrutura do projeto:
crypto-tut
--node_modules
--src
--client.js
--index.html
--style.css
--package-lock.json
--package.json
--server.js
Conteúdo
index.html
:
<head>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<script src="client.js" defer></source>
</head>
<body>
<div class="container">
<h3>Web Cryptography API Tutorial</h3>
<input type="text" value="Hello, World!" class="form-control">
<div class="btn-box">
<button class="btn btn-primary btn-send">Send message</button>
<button class="btn btn-success btn-get" disabled>Get message</button>
</div>
<output></output>
</div>
</body>
Conteúdo
style.css
:
h3,
.btn-box {
margin: .5em;
text-align: center;
}
input,
output {
display: block;
margin: 1em auto;
text-align: center;
}
output span {
color: green;
}
Servidor
Vamos começar a criar um servidor.
Nós abrimos
server.js
.
Conectamos express e criamos instâncias do aplicativo e do roteador:
const express = require('express')
const app = express()
const router = express.Router()
Conectamos o middleware (camada intermediária entre solicitação e resposta):
//
app.use(express.json({
type: ['application/json', 'text/plain']
}))
//
app.use(router)
//
app.use(express.static('src'))
Criamos uma variável para armazenar dados:
let data
Processamos o recebimento de dados do cliente:
router.post('/secure-api', (req, res) => {
//
data = req.body
//
console.log(data)
//
res.end()
})
Processamos o envio de dados ao cliente:
router.get('/secure-api', (req, res) => {
// JSON,
//
res.json(data)
})
Iniciamos o servidor:
app.listen(3000, () => console.log('Server ready'))
Nós executamos o comando
npm start
. O terminal exibe a mensagem "Servidor pronto". Abertura http://localhost:3000
:

Aqui é onde terminamos com o servidor, vá para o lado cliente da aplicação.
Cliente
Isto é onde a diversão começa.
Abra o arquivo
client.js
.
O algoritmo simétrico AES-GCM será usado para criptografia de dados. Esses algoritmos permitem o uso da mesma chave para criptografar e descriptografar.
Crie uma função de geração de chave simétrica:
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey
const generateKey = async () =>
window.crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt'])
Os dados devem ser codificados em um fluxo de bytes antes da criptografia. Isso é feito facilmente com a classe TextEncoder:
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
const encode = data => {
const encoder = new TextEncoder()
return encoder.encode(data)
}
Em seguida, precisamos de um vetor de execução (vetor de inicialização, IV), que é uma sequência aleatória ou pseudo-aleatória de caracteres que é adicionada à chave de criptografia para aumentar sua segurança:
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
const generateIv = () =>
// https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
window.crypto.getRandomValues(new Uint8Array(12))
Depois de criar as funções auxiliares, podemos implementar a função de criptografia. Esta função deve retornar uma cifra e um IV para que a cifra possa ser decodificada posteriormente:
const encrypt = async (data, key) => {
const encoded = encode(data)
const iv = generateIv()
const cipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv
}, key, encoded)
return {
cipher,
iv
}
}
Depois de criptografar os dados com SubtleCrypto , eles são buffers de dados binários brutos. Este não é o melhor formato para transmissão e armazenamento. Vamos consertar isso.
Os dados geralmente são enviados no formato JSON e armazenados em um banco de dados. Portanto, faz sentido compactar os dados em um formato portátil. Uma maneira de fazer isso é convertendo os dados em strings base64:
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const pack = buffer => window.btoa(
String.fromCharCode.apply(null, new Uint8Array(buffer))
)
Após o recebimento dos dados, é necessário realizar o processo reverso, ou seja, converter strings codificadas em base64 em buffers binários brutos:
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const unpack = packed => {
const string = window.atob(packed)
const buffer = new ArrayBuffer(string.length)
const bufferView = new Uint8Array(buffer)
for (let i = 0; i < string.length; i++) {
bufferView[i] = string.charCodeAt(i)
}
return buffer
}
Resta decifrar os dados recebidos. No entanto, após a descriptografia, precisamos decodificar o fluxo de bytes em seu formato original. Isso pode ser feito usando a classe TextDecoder:
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
const decode = byteStream => {
const decoder = new TextDecoder()
return decoder.decode(byteStream)
}
A função de descriptografia é o inverso da função de criptografia:
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt
const decrypt = async (cipher, key, iv) => {
const encoded = await window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv
}, key, cipher)
return decode(encoded)
}
Nesta fase, o conteúdo se
client.js
parece com este:
const generateKey = async () =>
window.crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt'])
const encode = data => {
const encoder = new TextEncoder()
return encoder.encode(data)
}
const generateIv = () =>
window.crypto.getRandomValues(new Uint8Array(12))
const encrypt = async (data, key) => {
const encoded = encode(data)
const iv = generateIv()
const cipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv
}, key, encoded)
return {
cipher,
iv
}
}
const pack = buffer => window.btoa(
String.fromCharCode.apply(null, new Uint8Array(buffer))
)
const unpack = packed => {
const string = window.atob(packed)
const buffer = new ArrayBuffer(string.length)
const bufferView = new Uint8Array(buffer)
for (let i = 0; i < string.length; i++) {
bufferView[i] = string.charCodeAt(i)
}
return buffer
}
const decode = byteStream => {
const decoder = new TextDecoder()
return decoder.decode(byteStream)
}
const decrypt = async (cipher, key, iv) => {
const encoded = await window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv
}, key, cipher)
return decode(encoded)
}
Agora vamos implementar o envio e recebimento de dados.
Criamos variáveis:
// ,
const input = document.querySelector('input')
//
const output = document.querySelector('output')
//
let key
Criptografia e envio de dados:
const encryptAndSendMsg = async () => {
const msg = input.value
//
key = await generateKey()
const {
cipher,
iv
} = await encrypt(msg, key)
//
await fetch('http://localhost:3000/secure-api', {
method: 'POST',
body: JSON.stringify({
cipher: pack(cipher),
iv: pack(iv)
})
})
output.innerHTML = ` <span>"${msg}"</span> .<br> .`
}
Recebendo e descriptografando dados:
const getAndDecryptMsg = async () => {
const res = await fetch('http://localhost:3000/secure-api')
const data = await res.json()
//
console.log(data)
//
const msg = await decrypt(unpack(data.cipher), key, unpack(data.iv))
output.innerHTML = ` .<br> <span>"${msg}"</span> .`
}
Tratamento de cliques de botão:
document.querySelector('.btn-box').addEventListener('click', e => {
if (e.target.classList.contains('btn-send')) {
encryptAndSendMsg()
e.target.nextElementSibling.removeAttribute('disabled')
} else if (e.target.classList.contains('btn-get')) {
getAndDecryptMsg()
}
})
Reinicie o servidor para o caso. Nós abrimos
http://localhost:3000
. Clique no botão "Enviar mensagem":

Vemos os dados recebidos pelo servidor no terminal:
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
Clique no botão "Obter mensagem":

Vemos os mesmos dados recebidos pelo cliente no console:
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
A API de criptografia da Web abre oportunidades interessantes para protegermos informações confidenciais do lado do cliente. Outro passo para o desenvolvimento web sem servidor.
O suporte para esta tecnologia atualmente é de 96%:

Espero que tenha gostado do artigo. Obrigado pela atenção.