Métodos de encriptação do PHP para senhas e outros dados sensíveis

recentemente assisti a Laracon EU 2018, onde Marcus Bointon deu uma grande palestra sobre cripto em PHP 7.2. Eu deixei a palestra tendo uma apreciação muito maior para o quão vastamente complicada criptografia é, mas também para como PHP está tornando a criptografia mais acessível graças à introdução de sódio. Criptografia de dados em PHP é algo em que eu tenho trabalhado como parte do meu trabalho em SpinupWP então eu pensei que era hora de eu compartilhar alguns insights. Aperta o cinto, porque isto pode ser um passeio acidentado!

tipos de encriptação

há uma gama de diferentes métodos de encriptação em uso hoje, sendo o mais comum a hashing, criptografia de chave secreta e criptografia de chave pública. Além disso, cada método de criptografia tem vários algoritmos ou cifras para escolher (cada um com suas próprias forças e fraquezas). Neste artigo vamos nos concentrar na implementação de hashing e criptografia de chave secreta.

Hashing

a hashing algorithm takes an input value and transforms it to a message digest. Em poucas palavras, os valores de texto simples são transformados em hash de comprimento fixo, e só podem ser validados passando o valor original para o algoritmo de hashing. Isto torna o hashing perfeito para armazenar senhas de utilizador.

vale a pena notar que o hashing não é uma solução à prova de bala e nem todos os algoritmos de hashing são iguais. Considere MD5 e SHA1 que são rápidos e eficientes, tornando-os ideais para checksumming e verificação de arquivos. No entanto, a sua velocidade torna-os inadequados para usar a senha de um utilizador. Com o poder computacional atual da GPUs moderna, uma senha pode ser quebrada pela Força bruta em questão de minutos, revelando a senha original de texto simples. Em vez disso, devem ser utilizados algoritmos de hashing intencionalmente mais lentos, como bcrypt ou Argon2.

enquanto uma senha hashed gerada por qualquer algoritmo irá certamente obscurecer os dados originais e atrasar qualquer atacante, nós, como desenvolvedores, devemos nos esforçar para usar o algoritmo mais forte disponível. Felizmente, PHP torna isso fácil graças a password_hash().

$hash = password_hash($password, PASSWORD_DEFAULT);

a função password_hash() não só usa um algoritmo de hashing seguro, mas também lida automaticamente com o sal e evita ataques de canal lateral com base no tempo. A partir de PHP 5.5, bcrypt será usado para gerar o hash, mas isso vai mudar no futuro como novos e mais seguros algoritmos de hashing são adicionados ao PHP. Argon2 é provável que se torne o próximo algoritmo de hashing padrão e pode ser usado hoje (em PHP 7.2), passando a bandeira PASSWORD_ARGON2I em vez de PASSWORD_DEFAULT.

verificar a senha de um usuário também é um processo trivial graças à função password_verify(). Basta passar a senha de texto simples fornecida pelo Usuário e compará-la com o hash armazenado, assim:

if (password_verify($password, $hash)) { echo "Let me in, I'm genuine!";}

observe como a verificação de senha é realizada em PHP. Se você está armazenando as credenciais de um usuário em uma base de dados, você pode estar inclinado a alterar a senha introduzida no login e, em seguida, realizar uma consulta de banco de dados, assim:

SELECT * FROM usersWHERE username = 'Ashley'AND password = 'password_hash'LIMIT 1;

esta abordagem é suscetível a ataques de canal lateral e deve ser evitada. Em vez disso, devolva o Usuário e, em seguida, verifique a senha hash em PHP.

SELECT username, password FROM usersWHERE username = 'Ashley'LIMIT 1;

enquanto o hashing é ótimo para armazenar a senha de um usuário, ele não funciona para dados arbitrários que a nossa aplicação precisa acessar sem a intervenção do Usuário. Vamos considerar uma aplicação de faturamento, que criptografa as informações do cartão de crédito de um usuário. A cada mês, a nossa aplicação precisa cobrar ao usuário pelo uso do mês anterior. Hashing os dados do cartão de crédito não vai funcionar porque requer que a nossa aplicação sabe os dados originais para ele recuperá-lo no texto simples.

encriptação de chave secreta para o resgate!

criptografia de Chave Secreta

criptografia de chave secreta (ou criptografia simétrica como também é conhecido) usa uma única chave para tanto criptografar e decifrar dados. Vamos ver como poderíamos implementar tal mecanismo usando sódio, que foi introduzido no PHP 7.2. Se você está executando uma versão mais antiga do PHP você pode instalar sódio via PECL.

primeiro precisamos de uma chave de criptografia, que pode ser gerada usando a função random_bytes(). Normalmente, você vai fazer isso apenas uma vez e armazená-lo como uma variável de ambiente. Lembre-se que esta chave deve ser mantida em segredo a todo custo. Assim que a chave estiver comprometida, os dados encriptados também.

$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);

para cifrar o valor que passamos para sodium_crypto_secretbox() com o nosso $key e um $nonce. O nonce é gerado usando random_bytes(), porque o mesmo nonce nunca deve ser reutilizado.

$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);$ciphertext = sodium_crypto_secretbox('This is a secret!', $nonce, $key);

isto apresenta um problema porque nós precisamos do nonce para descriptografar o valor mais tarde. Felizmente, os nonces não precisam ser mantidos em segredo para que possamos prepará-lo para o nosso $ciphertext e depois base64_encode() o valor antes de salvá-lo para a base de dados.

$encoded = base64_encode($nonce . $ciphertext);var_dump($encoded);// string 'v6KhzRACVfUCyJKCGQF4VNoPXYfeFY+/pyRZcixz4x/0jLJOo+RbeGBTiZudMLEO7aRvg44HRecC' (length=76)

quando se trata de descriptografar o valor, fazemos o oposto.

$decoded = base64_decode($encoded);

porque sabemos o comprimento do nonce podemos extraí-lo usando mb_substr() antes de descriptografar o valor.

$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);var_dump($plaintext);// string 'This is a secret!' (length=17)

isso é tudo o que há para criptografia de chave secreta em PHP, graças ao sódio!

encriptação de envelopes

embora a abordagem descrita acima seja certamente um passo na direcção certa, ainda deixa os nossos dados vulneráveis se a chave secreta estiver comprometida. Vamos considerar um usuário malicioso que obtém acesso ao servidor que hospeda nossa aplicação. Neste cenário, é provável que o atacante seja capaz de descobrir nossa chave secreta que usamos para criptografar os dados. Isto deixa os nossos dados completamente expostos.

a solução simples é não armazenar a nossa chave secreta no mesmo local que os dados encriptados, mas isto apresenta um problema. Como encriptamos e descodificamos a pedido? Indique o serviço de gestão de chaves do Google Cloud (Cloud KMS).

Cloud KMS é um serviço fornecido pela Google para hospedar chaves criptográficas de forma segura. Ele fornece uma variedade de recursos úteis em torno do armazenamento de chaves, incluindo rotação automática de chaves e destruição de chave atrasada. No entanto, neste exemplo estamos principalmente preocupados em armazenar nossa chave secreta longe de nossos dados.Para tornar as coisas mais seguras, vamos usar uma técnica conhecida como encriptação de envelopes. Essencialmente, criptografia de envelopes envolve criptografar chaves com outra chave. Fazemos isto por duas razões.:

  1. Cloud KMS tem um limite de tamanho de 64 KiB nos dados que podem ser criptografados e decifrados. Portanto, pode não ser possível enviar todos os dados de uma só vez.
  2. mais importante ainda, não queremos enviar os nossos dados de texto simples sensíveis a terceiros, independentemente de quão confiáveis possam parecer.

em vez de enviarmos os nossos dados de texto simples para o KMS da nuvem, vamos gerar uma chave de encriptação única sempre que escrevermos dados sensíveis para a base de dados. Esta chave é conhecida como uma chave de criptografia de dados (dek), que será usada para criptografar nossos dados. O DEK é então enviado para Cloud KMS para ser criptografado, que retorna uma chave de criptografia (conhecida como KEK). Finalmente, o KEK é armazenado lado a lado na base de dados ao lado dos dados criptografados e o DEK é destruído. O processo fica assim:

  1. Gerar uma única chave de criptografia (DEK)
  2. Criptografar os dados usando a chave secreta de criptografia
  3. Enviar a única DEK (chave de criptografia) para Nuvem KMS para criptografia, que retorna o KEK
  4. Armazenamento de dados criptografados e chave criptografada (KEK) lado-a-lado
  5. Destruir a chave gerada (DEK)

Quando a descriptografia de dados o processo é invertido:

  1. Recuperar os dados criptografados e chave criptografada (KEK) a partir do banco de dados
  2. Enviar o KEK a Nuvem KMS para a descodificação, que retorna o DEK
  3. Usar o DEK para descriptografar os nossos dados criptografados
  4. Destruir o DEK

Com isso em mente, eu criei um simples auxiliar de classe para a realização de envelope de criptografia. Eu não vou rever os passos necessários no Google Cloud console, como o Quickstart e guias de autenticação esboçam tudo o que você precisa para começar. Por uma questão de brevidade, não há manipulação de erros, etc. neste exemplo.

<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;class KeyManager{ private $kms; private $encryptRequest; private $decryptRequest; private $projectId; private $locationId; private $keyRingId; private $cryptoKeyId; public function __construct(Kms $kms, EncryptRequest $encryptRequest, DecryptRequest $decryptRequest, $projectId, $locationId, $keyRingId, $cryptoKeyId) { $this->kms = $kms; $this->encryptRequest = $encryptRequest; $this->decryptRequest = $decryptRequest; $this->projectId = $projectId; $this->locationId = $locationId; $this->keyRingId = $keyRingId; $this->cryptoKeyId = $cryptoKeyId; } public function encrypt($data) { $key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $ciphertext = sodium_crypto_secretbox($data, $nonce, $key); return ; } public function decrypt($secret, $data) { $decoded = base64_decode($data); $key = $this->decryptSecret($secret); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); return sodium_crypto_secretbox_open($ciphertext, $nonce, $key); } private function encryptKey($key) { $this->encryptRequest->setPlaintext(base64_encode($key)); $response = $this->kms->projects_locations_keyRings_cryptoKeys->encrypt( $this->getResourceName(), $this->encryptRequest ); return $response; } private function decryptSecret($secret) { $this->decryptRequest->setCiphertext($secret); $response = $this->kms->projects_locations_keyRings_cryptoKeys->decrypt( $this->getResourceName(), $this->decryptRequest ); return base64_decode($response); } private function getResourceName() { return sprintf( 'projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s', $this->projectId, $this->locationId, $this->keyRingId, $this->cryptoKeyId ); }}

irá notar que os métodos de encriptação e descodificação actuais são quase idênticos à implementação da chave secreta introduzida acima. A diferença, no entanto, é que agora estamos usando várias chaves de criptografia. Vamos ver a classe auxiliar em acção. Você vai precisar de fornecer o seu $projectId, $locationId, $keyRingId e $cryptoKeyId que estão disponíveis no Google Cloud console.

<?phpuse Google_Service_CloudKMS as Kms;use Google_Service_CloudKMS_DecryptRequest as DecryptRequest;use Google_Service_CloudKMS_EncryptRequest as EncryptRequest;$client = new Google_Client();$client->setAuthConfig(getenv('GOOGLE_CREDENTIALS_FILE'));$client->addScope('https://www.googleapis.com/auth/cloud-platform');$keyManager = new KeyManager( new Kms($client), new EncryptRequest(), new DecryptRequest(), $projectId, $locationId, $keyRingId, $cryptoKeyId);$encrypted = $keyManager->encrypt('This is a secret!');var_dump($encrypted);// array (size=2)// 'data' => string 'uKjmEU7e1JEU+2vL3hBK2wBk6afCSgb+Y4GQtu/mmLuffgHlnqxnqOMPOI6WGkM18vAGGvFVDTvd' (length=76)// 'secret' => string 'CiQAdA0emUW2nhlU3RijX/5GnUsTnPPrQdLZNxdHWXWYugx49a4SSQBHyYr0T/PEbKwyFhIkaZl28oKkJRkXqNcqOL4Z+OTQFLpGvS6zCDt2mFn/nUQ/bi4znD4DORk9ZDTqiIBK3UNFUZcrXvoExds=' (length=152)$decrypted = $keyManager->decrypt($encrypted, $encrypted);var_dump($decrypted);// string 'This is a secret!' (length=17)

se um atacante comprometesse o nosso sistema, eles também seriam capazes de ganhar as nossas credenciais de API para Cloud KMS? Dependendo do método de autenticação usado, então sim pode ser uma possibilidade. Se for esse o caso, deve estar a perguntar-se como é que a encriptação do envelope é mais segura do que a encriptação da chave secreta normal? A principal diferença (trocadilho pretendido) é que o acesso API pode ser revogado, impedindo assim um atacante que é feito com seus dados sensíveis. É o equivalente a mudar as tuas fechaduras se alguém te roubar a chave da casa. Com encriptação de chave secreta regular onde uma única chave local está comprometida, você não tem esse luxo. O atacante tem todo o tempo do mundo para descriptografar seus dados sensíveis.

encerrar

a segurança e a encriptação dos dados são assuntos vastos e eu cobri apenas um punhado de maneiras de proteger dados sensíveis usando PHP. (Escrevemos anteriormente sobre a protecção deste tipo de dados no seu ambiente local)

Deixe uma resposta

O seu endereço de email não será publicado.

Previous post Atualização: Polícia prende suposto atirador em Shady Oaks – Albert Lea Tribuna | Albert Lea Tribune
Next post como remover manchas de óleo e graxa nas roupas