Criptografia e Autenticação biométrica nativa no navegador

William Grasel

Por que precisamos de mais uma API para criptografia na Web?

O protocolo HTTPS já não é o suficiente?

O protocolo SSL/TLS já deveria ser padrão para TODA a Web

A Web está ficando cada vez mais complexa e com mais casos de uso!

Privacidade e segurança são requisitos cada vez mais importantes no nosso tempo

A Web pode fazer cada vez mais coisas que antes só era possível em apps nativos

A qualquer momento o usuário (ou um hacker) pode abrir o dev tools

Criptografia e Autenticação Biométrica são imprescindíveis para a evolução da

Web como Plataforma

Exemplos:

  • Guardar dados locais no navegador de maneira segura
  • Trocar dados cliente e servidor com mais camadas de proteção
  • Modelos de login e autenticação de dois fatores sem senha

Libs de criptografia não são o suficiente!

Tem seus próprios bugs e pontos de falha que podem ser explorados

API Nativas podem oferecer aceleração via hardware que alguns algoritmos precisam

Mas primeiro, qual o suporte dos navegadores a essas APIs?

Trago boas notícias!

Já podemos usar essas APIs em quase todos os principais navegadores sem nenhum polyfill

Web Crypto API

let cryptoObj = window.crypto || window.msCrypto; // for IE 11

Uma API de baixo nível para trabalhar com vários algoritmos diferentes

Aleatoriedade Forte

const typedArray = new Uint32Array(10);

let filledArray = window.crypto.getRandomValues(typedArray);

filledArray === typedArray // true, same instance

SubtleCrypto

let subtleCrypto = window.crypto.subtle;

subtleCrypto.decrypt(...);
subtleCrypto.deriveBits(...);
subtleCrypto.deriveKey(...);
subtleCrypto.digest(...);
subtleCrypto.encrypt(...);
subtleCrypto.exportKey(...);
subtleCrypto.generateKey(...);
subtleCrypto.importKey(...);
subtleCrypto.sign(...);
subtleCrypto.unwrapKey(...);
subtleCrypto.verify(...);
subtleCrypto.wrapKey(...);

A SubtleCrypto só está disponível sobre HTTPS

Todos os métodos do SubtleCrypto retornam promises!

Criptografia Sutil?

O que eles querem dizer:

window.crypto
    .IKnowWhatIamDoing
    .IacceptTheTermsAndConditions
    .encrypty(...)

É bem fácil fazer algum erro básico com o uso de cryptografia

E se for um requisito essencial, deve ser validado por especialistas no tema

Mas não devemos ter medo dessa API!

Temos de começar a ganhar familiaridade com criptografia

E então começar a usar essas APIs por padrão!

Mesmo se for através de libs q abstraiam parte da complexidade!

Vamos começar!

Algoritmos de Criptografia

Tipos de algoritmos:

  • Integridade
  • Autenticidade
  • Confidencialidade

Normalmente acabamos por usar uma mistura deles

Case:

Upload de Arquivo

Atenção:

Exemplos usando TypeScript

Integridade

aka hashing

> sha256sum ubuntu-mate-16.10-desktop-amd64.iso

c01b39c7a35ccc3b081a3e83d2c71fa9a767ebfeb45c69f08e17dfe3ef375a7b

SHA-256

"Lorem ipsum"
(data)
0x8dsf8dwl2c0
(digest)
crypto.subtle.digest(...);

Exemplo

function digestArrayBuffer(data: ArrayBuffer): Promise<ArrayBuffer> {
  return crypto.subtle.digest('SHA-256', data);
}

async function digestString(message: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(message); // Uint8Array UTF-8
  
  const digestBuffer = await crypto.subtle.digest('SHA-256', data);
  
  const decoder = new TextDecoder(); // default 'utf-8'
  return decoder.decode(digestBuffer);
}

Autenticidade

aka hashing com senha

HMAC

"Lorem ipsum"
(data)
0x8dsf8dwl2c0
(sign)
0x9s6mh7d8bew2
(key)
let signData = await window.crypto.subtle.sign(...);
let isValid = await window.crypto.subtle.verify(...);

Exemplo

function signData(
  data: ArrayBuffer, key: CryptoKey
): Promise<ArrayBuffer> {
  return window.crypto.subtle.sign(
    "HMAC", key, data
  );
}

function verifyData(
  data: ArrayBuffer, key: CryptoKey, sign: ArrayBuffer
): Promise<boolean> {
  return window.crypto.subtle.verify(
    "HMAC", key, sign, data
  );
}

Confidencialidade

aka cifra de dados

AES-GCM

"Lorem ipsum"
(data)
0x8dsf8dwl2c0
(cipher)
0x9s6mh7d8bew2
(key)
0xd9v7fd773m2
(nonce)
window.crypto.subtle.encrypt(...);
window.crypto.subtle.decrypt(...);

Exemplo

await function encryptData(
  data: ArrayBuffer, key: CryptoKey
) {
  const nonce = window.crypto.getRandomValues(new Uint8Array(12));
  const cipher = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: nonce
    },
    key,
    data
  );
  return { cipher, nonce }
}

async function decryptData(
  data: ArrayBuffer, key: CryptoKey, nonce: ArrayBuffer
) {
  return window.crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: nonce
    },
    key,
    data
  );
}

Crypto Keys

O gerenciamento da chave criptográfica é essencial!

Existem muitos meios de se obter ou criar uma chave

Dependendo muito da sua aplicação e modelo de negocio

A chave não deve nunca ser armazenada no navegador

E deveria transitar o mínimo possível entre back e front

Gerando chaves

const cryptoKey = await window.crypto.subtle.generateKey(
  "AES-GCM", // algorítimo q será usada
  true, // essa chave por ser extraída?
  ["encrypt", "decrypt"] // usos dessa chave
);

const rawKey = await window.crypto.subtle.exportKey(
  "raw", cryptoKey
);

const sameKey = await window.crypto.subtle.importKey(
  "raw", rawKey,
  "AES-GCM",
  false,
  ["encrypt", "decrypt"]
);

Que tal usar a senha do usuário como chave?

Você não precisa enviar a senha aberta para o servidor

Você pode enviar apenas o hash dela

Senhas de usuários são péssimas chaves para criptografia

Mas podemos usar a senha como base para gerar uma

chave forte!

Esse processo é chamado de derivação de chave

PBKDF2

0x8dsf8dwl2c0
(week key)
0x8dsf8dwl2c0
(stronger key)
0x9s6mh7d8bew2
(salt)
50000
(interactions)

Derivando chaves

const encoder = new TextEncoder();
const encodedPass = encoder.encode(userPass); // Uint8Array UTF-8

const baseKey = await window.crypto.subtle.importKey(
  'raw',
  encodedPass,
  'PBKDF2', // algoritimo de derivação q usaremos
  false, // não precisamos extrair a chave de volta
  ['deriveKey'],
),

const salt = crypto.getRandomValues(new Uint8Array(8));

const cryptoKey = await window.crypto.subtle.deriveKey(
  {
    "name": "PBKDF2",
    salt: salt,
    "iterations": 100000,
    "hash": "SHA-256"
  },
  baseKey,
  { "name": "AES-GCM", "length": 256 },
  false,
  [ "encrypt", "decrypt" ]
);

Autenticação Biométrica

Mais de 80% dos ataques a sites são causados por senhas fracas

A maioria dos celulares e notebooks modernos usam biometria

Que podem aumentar tanto a segurança quanto a praticidade

WebAuthn API faz a interface com o SO para usar da biometria

O cadastro da biometria num site não pode ser usado em outros

Protegendo também a ataques de phishing

Além de ser compatível com Yubikeys:

Registrando a Biometria

Gerando a credencial

const credential = await window.navigator.credentials.create({
    publicKey: generatePublicKeyOptions(
      randomChallengeFromServer,
      userData
    )
});

function generatePublicKeyOptions(challenge, user) {
  const encoder = new TextEncoder();
  return {
    challenge: encoder.encode(challenge),
    rp: {
      name: "My Website Name",
      id: "my-site-domain.com",
    },
    user: {
      id: encoder.encode(user.id),
      name: user.email,
      displayName: user.name,
    },
    pubKeyCredParams: [{alg: -7, type: "public-key"}]
  }
}

Inspecionando a credencial

const credential = await navigator.credentials.create(...);
                                                       
console.log(credential);

PublicKeyCredential {
  id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
  rawId: ArrayBuffer(59),
  response: AuthenticatorAttestationResponse {
    clientDataJSON: ArrayBuffer(121), // utf-8 string decoded
    attestationObject: ArrayBuffer(306), // CBOR decoded
  },
  type: 'public-key'
}

Logando

Gerando a assinatura

const credential = await navigator.credentials.get({
    publicKey: generatePublicKeyOptions(
      randomChallengeFromServer,
      credentialId
    )
});

function generatePublicKeyOptions(challenge, credentialId) {
  const encoder = new TextEncoder();
  return {
    challenge: encoder.encode(challenge),
    allowCredentials: [{
      id: encoder.encode(credentialId),
      type: 'public-key'
    }]
  }
}

Inspecionando a assinatura

const credential = await navigator.credentials.get(...);
                                                       
console.log(credential);

PublicKeyCredential {
  id: 'ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...',
  rawId: ArrayBuffer(59),
  response: AuthenticatorAttestationResponse {
    clientDataJSON: ArrayBuffer(121), // dados do cliente registrado
    authenticatorData: ArrayBuffer(191), // dados do autenticador
    signature: ArrayBuffer(70), // assinatura com a chave privada
  },
  type: 'public-key'
}

E se a gente pudesse matar de vez as senhas?

Agora com passkeys nos podemos!

Passkeys nada mais é do que um novo fluxo da WebAuth API

Permitindo compartilhar

Chaves Criptográficas

entre devices!

Demo Time

Web Crypto Storage

Referências

Perguntas?

Obrigado! =)

Criptografia e Autenticação biométrica nativa no navegador

By William Grasel

Criptografia e Autenticação biométrica nativa no navegador

Segurança e privacidade são requisitos essenciais de nosso tempo, e claro que muita gente usa esses fatores como desculpa para fazer aplicativos nativos, pois na Web não temos nenhuma outra proteção além do HTTPS, correto? ERRADO! A Web como plataforma tem se tornado cada vez mais madura, com novas API's que possibilitam novos casos de usos! Nessa palestra vamos ver como usar algoritmos avançados de criptografia e autenticação biométrica de forma nativa e segura na sua PWA!

  • 2,088