Melhores práticas de criptografia

@vcampitelli

Sobre mim

Vinícius Campitelli

Vinícius Campitelli

  • Membro do PHPSP
  • Desenvolvedor na OTTera
  • Entusiasta em cibersegurança
  • Desenvolvedor PHP há mais de 10 anos

Sobre mim

Criptografia é...

a prática e o estudo de técnicas para comunicação segura na presença de terceiros
a construção e análise de protocolos que previnam terceiros de ler mensagens privadas
Wikipedia

Criptografia é...

Texto plano

Encriptação Decriptação
Texto cifrado 42a14c854faed

Criptografia Objetivos principais

Confidencialidade
só o destinatário autorizado deve ser capaz de extrair o conteúdo da mensagem da sua forma cifrada

Integridade
o destinatário deverá ser capaz de verificar se a mensagem foi alterada durante a transmissão
Autenticidade
o destinatário deverá ser capaz de verificar que se o remetente é realmente quem diz ser

Não-repúdio
não deverá ser possível ao remetente negar a autoria de sua mensagem
Wikipedia

Criptografia Simétrica

Usa a mesma chave para os dois processos

Portanto, essa chave deve permanecer secreta

Criptografia Simétrica

Cifras de Bloco

Operam com tamanho fixos de blocos de informação

Cifras de Fluxo

Trabalham com qualquer stream de bits

Criptografia Simétrica › Cifras de Bloco Algoritmos mais comuns
Blowfish
deve-se usar os algoritmos relacionados Twofish and Threefish, por serem mais seguros
DES
Data Encryption Standard
que não deve ser usado mais (e nem o seu successor Triple DES)
AES
Advanced Encryption Standard
também conhecido pelo seu nome original Rijndael, foi aprovado pelo NSA para encriptar informações confidenciais e é o mais recomendado
Criptografia Simétrica › Cifras de Bloco Modos de Operação

Processo que será responsável por executar a cifra em cada um dos blocos

Criptografia Simétrica › Cifras de Bloco Modos de Operação

O Vetor de Inicialização (IV) é um valor aleatório responsável por oferecer o estado inicial ao algoritmo e não pode ser reutilizado ou previsível

Criptografia Simétrica › Cifras de Bloco Modos de Operação

Principais algoritmos

  • Electronic codebook (ECB)
  • Cipher block chaining (CBC)
  • Cipher feedback (CFB)
  • Counter (CTR)
  • Galois/counter mode (GCM)
  • Counter with cipher block chaining MAC (CCM)
Criptografia Simétrica › Cifras de Bloco Por que não: Electronic codebook (ECB)
pngpix.com
Criptografia Simétrica › Cifras de Bloco Por que não: CBC, CFB e CTR

Não oferecem integridade e autenticidade

Ou seja, não é possível saber se alguém alterou ou forjou a mensagem

Iremos saber mais ao falar de hashing

Criptografia Simétrica › Cifras de Bloco Modos de Operação

Algoritmos

  • Electronic codebook (ECB)
  • Cipher block chaining (CBC)
  • Cipher feedback (CFB)
  • Counter (CTR)
  • Galois/counter (GCM)
  • Counter with cipher block chaining MAC (CCM)
Criptografia Simétrica › Cifras de Bloco Exemplo: GCM

                            $iv = random_bytes(
                                openssl_cipher_iv_length('aes-256-gcm')
                            );

                            $ciphertext = openssl_encrypt(
                                $data,             // plaintext
                                'aes-256-gcm',     // cipher
                                $key,              // key
                                OPENSSL_RAW_DATA,  // flags
                                $iv,               // iv (nonce)
                                $tag,              // tag
                                $aad,              // AAD
                                16                 // tag length
                            );

                            $message = base64_encode($iv . $tag . $ciphertext);
                        

PHP

Criptografia Simétrica › Cifras de Bloco Exemplo: GCM

                            const { readFileSync } = require('fs');
                            const { createCipheriv, randomBytes } = require('crypto');

                            const key = readFileSync('app.key').toString().trim();
                            const nonce = randomBytes(12);

                            const cipher = createCipheriv('aes-256-gcm', key, nonce, {
                                authTagLength: 16
                            });
                            const ciphertext = cipher.update(plaintext, 'utf8');
                            cipher.final();
                            const tag = cipher.getAuthTag();

                            const message = Buffer
                                .concat([nonce, tag, ciphertext])
                                .toString('hex');
                        
scripts/symmetric/node-gcm-encrypt.js

Criptografia Simétrica Cifras de Fluxo

Indicadas quando não se sabe o tamanho do dado

A5/1
usada no padrão GSM para comunicação móvel
RC4
usada no protocolo WEP para redes wireless
ChaCha
a mais recomendada para uso hoje em dia

Criptografia Simétrica Onde armazenar a chave?

Melhor opção é usar solução de hardware, mas pode ser caro e não estar disponível na infraestrutura

Exemplos: HSM (Hardware Security Module) e TPM (Trusted Platform Module)

Criptografia Simétrica Onde armazenar a chave?

Serviços do seu provedor de Cloud

Exemplos: AWS Key Management Service e Google Cloud Key Management

Criptografia Simétrica Onde armazenar a chave?

Para Kubernetes ou Swarm, checar as features de Secrets

PS: o Kubernetes não criptografa os dados por padrão, então siga o artigo Encrypting Secret Data at Rest

Criptografia Simétrica Onde armazenar a chave?

git-secret também é uma alternativa e acompanha o versionamento do seu código

Criptografia Simétrica Onde armazenar a chave?

Dependendo da criticidade da aplicação, você pode dividir a chave e colocar cada parte em uma localização diferente

Criptografia Assimétrica

Utiliza duas chaves, sendo cada uma responsável por um dos processos

Chave Pública

Pode ser distribuída para outros

Chave Privada

Deve permanecer com o portador

Também conhecida como Criptografia de chave pública

Criptografia Assimétrica

Se você quer que terceiros se comuniquem com você...

Chave Pública

Usada por eles para escrevem para você


Encriptação

Chave Privada

Usada por você para ler o texto recebido


Decriptação

Criptografia Assimétrica

Se você quiser se comunicar com outros...

Chave Pública

Usada por eles para verificar se o texto realmente veio de você


Decriptação

Chave Privada

Usada para você para escrever textos


Encriptação

Criptografia Assimétrica Mas como pode uma chave ser usada para ler e outra para escrever?

Criptografia Assimétrica Mas como pode uma chave ser usada para ler e outra para escrever?

O RSA, algoritmo bastante conhecido, depende de números semi-primos (que são aqueles com exatamente dois números primos como fatores) muito grandes

A segurança desse algoritmo consiste na dificuldade computacional em fatorar esses números

Criptografia Assimétrica Mas como pode uma chave ser usada para ler e outra para escrever?

Outros algoritmos (como ED25519) são baseados em curvas elípticas, que são pontos gerados por equações cúbicas como y² = x³ + ax + b

Nesses casos, o cálculo do logaritmo discreto ainda é muito custoso computacionalmente

Criptografia Assimétrica Mas como pode uma chave ser usada para ler e outra para escrever?

Em ambos os casos, a segurança se baseia no fato das operações reversas serem muito custosas com o poder computacional existente

Já existem estudos de criptografia pós-quântica para resistirem a ataques de computadores quânticos

Criptografia Assimétrica Abordagem híbrida

Na prática, por causa de limitações do tamanho do texto a ser cifrado em alguns algoritmos (como no RSA, que com chaves de 2048 bits só permite cifrar blocos de até 214 bytes) e por serem mais lentos na maioria dos casos, o que geralmente acontece é:

Criptografia Assimétrica Abordagem híbrida

  1. É gerada uma chave secreta (aleatória e única para a sessão);
  2. Usa-se Criptografia Simétrica para criptografar o texto plano;
  3. Usa-se Criptografia Assimétrica para criptografar a chave gerada.

Criptografia Assimétrica Exemplo - RSA


                            const crypto = require('crypto');
                            const publicKey = crypto.createPublicKey(
                                fs.readFileSync('/path/to/public.key').toString()
                            );

                            const encryptedData = crypto.publicEncrypt(
                                {
                                    key: publicKey,
                                    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                                    oaepHash: 'sha512',
                                },
                                Buffer.from(message)
                            );

                            console.log(encryptedData.toString('hex'));
                        
scripts/asymmetric/node-rsa-encrypt.js

Criptografia Assimétrica Exemplo - RSA


                            const crypto = require('crypto');
                            const privateKey = crypto.createPrivateKey(
                                fs.readFileSync('/path/to/private.key').toString()
                            );

                            const decryptedData = crypto.privateDecrypt(
                                {
                                    key: privateKey,
                                    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                                    oaepHash: 'sha512',
                                },
                                Buffer.from(message, 'hex')
                            );

                            console.log(decryptedData.toString);
                        
scripts/asymmetric/node-rsa-decrypt.js

Hashing

é uma função que mapeia dados de tamanho arbitrário para dados de tamanho fixo
Wikipedia

Hashing

Os algoritmos mais comuns são:

  • CRC
  • MD5
  • SHA-1
  • SHA-256
  • SHA-512
  • RIPEMD-160
  • Whirlpool
  • BLAKE3

Hashing

Para fins de segurança, devemos optar por:

  • CRC
  • MD5
  • SHA-1
  • SHA-256
  • SHA-512
  • RIPEMD-160
  • Whirlpool
  • BLAKE3

Hashing Integridade + Autenticidade

Apenas criptografar sem fazer o hash não é o suficiente, pois algum agente malicioso pode alterar a mensagem

Hashing Integridade + Autenticidade

Demonstração simples de bit flipping

O que um sistema que rode o código a seguir pode dar de informação a um atacante?

                            function login(username, password) {
                                const user = this.findByUsername(username);
                                if (!user) {
                                    return false;
                                }

                                if (!this.verifyPassword(password, user.password)) {
                                    return false;
                                }

                                return true;
                            }
                        

Enumeração de usuários

É possível saber que usuários existem em sua aplicação através de Timing attacks

Timing Attacks

ataque no qual um atacante tenta comprometer um sistema criptográfico analisando o tempo gasto para executar determinados algoritmos
Wikipedia

Timing Attacks

Será que não estamos exagerando?
É realmente possível ver essa diferença?


Timing Attacks

Timing Attacks


                            function login(username, password) {
                                const user = this.findByUsername(username);

                                // this.generateFakePassword = () => 'abcdefg123456'
                                let storedPassword = this.generateFakePassword();
                                if (user !== null) {
                                    storedPassword = user.password;
                                }

                                return (this.verifyPassword(password, storedPassword))
                                    && (user !== null);
                            }
                        

Código resistente a timing attacks

Timing Attacks Hashing

Exercício: criar um algoritmo para comparar duas strings

  1. A primeira coisa é comparar o tamanho delas
  2. Se os tamanhos forem diferentes, retorna false
  3. Se forem iguais, você irá comparar letra por letra
  4. Assim que houver uma letra diferente, retorna false
  5. Se chegou até o final da comparação, retorna true

Timing Attacks Hashing

Exercício: criar um algoritmo para comparar duas strings

Cada uma das comparações do item 4. irá levar um certo tempo, que chamaremos de 1u
(que provavelmente serão alguns milésimos de segundo)

Então, para comparar as strings abcdef e abcdef...

Comparação Tempo decorrido
Tamanhos são iguais -
a é igual a a 1u
b é igual a b 2u
c é igual a c 3u
d é igual a d 4u
e é igual a e 5u
f é igual a f 6u

Agora, para comparar as strings abcdef e abcdxf...

Comparação Tempo decorrido
Tamanhos são iguais -
a é igual a a 1u
b é igual a b 2u
c é igual a c 3u
d é igual a d 4u
e é igual a x 5u

Timing Attacks Hashing

Use comparações de tempo constante entre hashes


                            // Ao invés de if ($hash1 === $hash2) {
                            if (hash_equals($hash1, $hash2)) {
                                // ...
                            }
                        
PHP

Timing Attacks Hashing

Use comparações de tempo constante entre hashes


                            const { timingSafeEqual } = require('crypto');
                            const buffer1 = Buffer.from('hash1');
                            const buffer2 = Buffer.from('hash2');

                            // Ao invés de if (buffer1.equals(buffer2)) {
                            let check = false;
                            try {
                                check = timingSafeEqual(buffer1, buffer2);
                            } catch (err) {
                                // false
                            }
                        
Node.js

Armazenamento de Senhas

Você deve usar algoritmos de hashing ao invés de encriptação

Em ordem de recomendação:

  1. Argon2ID
  2. bcrypt
  3. PBKDF2

Armazenamento de Senhas Exemplo


                            // Para salvar
                            public function setPassword(string $password): string {
                                return \password_hash($password, PASSWORD_ARGON2ID);
                            }

                            // Para verificar
                            public function checkPassword(User $user, string $password): bool {
                                return \password_verify($password, $user->getPassword());
                            }
                        
PHP

Armazenamento de Senhas Exemplo


                            const argon2 = require('argon2');

                            try {
                                // Para salvar
                                const hash = await argon2.hash(
                                    password,
                                    { type: argon2.argon2id }
                                );

                                // Para verificar
                                if (await argon2.verify(hash, password)) {
                                    // ...
                                }
                            } catch (err) {
                                // algum erro
                            }
                        
Node.js (pacote argon2)

Armazenamento de Senhas Bibliotecas e leitura

Referências Bibliotecas

Referências Ferramentas

Referências Leitura

Referências Modelos e Procedimentos

Referências Vulnerabilidades

Obrigado!