Antes de fazer algo mais, procure entender a diferença entre criptografia e autenticação e por que você provavelmente deseja criptografia autenticada em vez de apenas criptografia .
Para implementar a criptografia autenticada, você deseja criptografar e depois o MAC. A ordem de criptografia e autenticação é muito importante! Uma das respostas existentes para essa pergunta cometeu esse erro; assim como muitas bibliotecas de criptografia escritas em PHP.
Você deve evitar implementar sua própria criptografia e, em vez disso, usar uma biblioteca segura criada e revisada por especialistas em criptografia.
Atualização: o PHP 7.2 agora fornece libsodium ! Para melhor segurança, atualize seus sistemas para usar o PHP 7.2 ou superior e siga apenas os conselhos de libsodium nesta resposta.
Use libsodium se você tiver acesso ao PECL (ou sodium_compat se desejar libsodium sem PECL); caso contrário ...
Use defuse / php-encryption ; não role sua própria criptografia!
Ambas as bibliotecas vinculadas acima acima tornam fácil e simples implementar a criptografia autenticada em suas próprias bibliotecas.
Se você ainda deseja escrever e implantar sua própria biblioteca de criptografia, contra a sabedoria convencional de todo especialista em criptografia na Internet, estas são as etapas que você deve executar.
Criptografia:
- Criptografar usando AES no modo CTR. Você também pode usar o GCM (que remove a necessidade de um MAC separado). Além disso, ChaCha20 e Salsa20 (fornecidos pela libsodium ) são cifras de fluxo e não precisam de modos especiais.
- A menos que você tenha escolhido o GCM acima, você deve autenticar o texto cifrado com HMAC-SHA-256 (ou, para as cifras de fluxo, Poly1305 - a maioria das APIs de libsódio faz isso por você). O MAC deve cobrir tanto o IV quanto o texto cifrado!
Descriptografia:
- A menos que Poly1305 ou GCM seja usado, recalcule o MAC do texto cifrado e compare-o com o MAC que foi enviado usando
hash_equals()
. Se falhar, aborte.
- Descriptografe a mensagem.
Outras considerações de design:
- Nunca comprima nada. O texto cifrado não é compactável; compactar texto sem formatação antes da criptografia pode levar a vazamentos de informações (por exemplo, CRIME e BREACH on TLS).
- Certifique-se de usar
mb_strlen()
e mb_substr()
, usando o '8bit'
modo de conjunto de caracteres para evitar mbstring.func_overload
problemas.
- IVs devem ser gerados usando um CSPRNG ; Se você estiver usando
mcrypt_create_iv()
, NÃO USEMCRYPT_RAND
!
- A menos que você esteja usando uma construção AEAD, SEMPRE criptografa o MAC!
bin2hex()
, base64_encode()
etc. podem vazar informações sobre suas chaves de criptografia via tempo de cache. Evite-os, se possível.
Mesmo se você seguir o conselho dado aqui, muita coisa pode dar errado com a criptografia. Sempre tenha um especialista em criptografia revisando sua implementação. Se você não tiver a sorte de ser amigo pessoal de um estudante de criptografia da sua universidade local, sempre poderá tentar o fórum do Cryptography Stack Exchange para obter orientação.
Se você precisar de uma análise profissional de sua implementação, sempre poderá contratar uma equipe respeitável de consultores de segurança para revisar seu código de criptografia PHP (divulgação: meu empregador).
Importante: Quando não usar criptografia
Não criptografe senhas . Emvez disso,você deseja fazer o hash , usando um destes algoritmos de hash de senha:
Nunca use uma função de hash de uso geral (MD5, SHA256) para armazenamento de senha.
Não criptografe parâmetros de URL . É a ferramenta errada para o trabalho.
Exemplo de criptografia de seqüência de caracteres PHP com Libsodium
Se você estiver no PHP <7.2 ou não tiver o libsodium instalado, poderá usar o sódio_compat para obter o mesmo resultado (embora mais lento).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Em seguida, para testá-lo:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halita - Libsodium Tornado Mais Fácil
Um dos projetos em que estou trabalhando é uma biblioteca de criptografia chamada Halite , que visa tornar o libsodium mais fácil e mais intuitivo.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Toda a criptografia subjacente é manipulada pelo libsodium.
Exemplo com defuse / php-encryption
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Nota : Crypto::encrypt()
retorna a saída codificada em hexadecimal.
Gerenciamento de chave de criptografia
Se você estiver tentado a usar uma "senha", pare agora. Você precisa de uma chave de criptografia aleatória de 128 bits, não de uma senha memorável humana.
Você pode armazenar uma chave de criptografia para uso a longo prazo, como:
$storeMe = bin2hex($key);
E, sob demanda, você pode recuperá-lo da seguinte forma:
$key = hex2bin($storeMe);
Eu fortemente recomendo apenas armazenar uma chave gerada aleatoriamente para uso a longo prazo em vez de qualquer tipo de senha como a chave (ou para derivar a chave).
Se você estiver usando a biblioteca do Defuse:
"Mas eu realmente quero usar uma senha."
É uma má ideia, mas tudo bem, eis como fazê-lo com segurança.
Primeiro, gere uma chave aleatória e armazene-a em uma constante.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Observe que você está adicionando trabalho extra e pode usar essa constante como chave e poupar muita dor de cabeça!
Em seguida, use PBKDF2 (assim) para obter uma chave de criptografia adequada da sua senha, em vez de criptografá-la diretamente.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
Não use apenas uma senha de 16 caracteres. Sua chave de criptografia será comicamente quebrada.