Importante : A menos que você tenha um caso de uso muito específico, não criptografe senhas , use um algoritmo de hash de senha. Quando alguém diz que criptografa suas senhas em um aplicativo do servidor, elas não são informadas ou estão descrevendo um design de sistema perigoso. Armazenar senhas com segurança é um problema totalmente separado da criptografia.
Ser informado. Projete sistemas seguros.
Criptografia de dados portátil em PHP
Se você estiver usando o PHP 5.4 ou mais recente e não quiser escrever um módulo de criptografia, recomendo usar uma biblioteca existente que forneça criptografia autenticada . A biblioteca que vinculei depende apenas do que o PHP fornece e está sendo revisada periodicamente por um punhado de pesquisadores de segurança. (Eu mesmo incluído.)
Se seus objetivos de portabilidade não impedirem a necessidade de extensões PECL, libsodium é altamente recomendado sobre qualquer coisa que você ou eu possamos escrever em PHP.
Atualização (12/06/2016): agora você pode usar sodium_compat e usar as mesmas ofertas de cripto libsodium sem instalar extensões PECL.
Se você quiser experimentar a engenharia de criptografia, continue lendo.
Primeiro, você deve reservar um tempo para aprender os perigos da criptografia não autenticada e o Princípio da Perdição Criptográfica .
- Os dados criptografados ainda podem ser adulterados por um usuário mal-intencionado.
- A autenticação dos dados criptografados impede a violação.
- A autenticação dos dados não criptografados não impede a violação.
Criptografia e descriptografia
A criptografia no PHP é realmente simples (vamos usar openssl_encrypt()
e openssl_decrypt()
depois de tomar algumas decisões sobre como criptografar suas informações. Consulte openssl_get_cipher_methods()
uma lista dos métodos suportados no seu sistema. A melhor opção é AES no modo CTR :
aes-128-ctr
aes-192-ctr
aes-256-ctr
Atualmente, não há razão para acreditar que o tamanho da chave AES seja um problema significativo com o qual se preocupar (maior provavelmente não é melhor, devido ao mau agendamento de teclas no modo de 256 bits).
Nota: Não estamos usando mcrypt
porque é um abandonware e possui bugs não corrigidos que podem afetar a segurança. Por esses motivos, incentivo outros desenvolvedores de PHP a evitá-lo também.
Wrapper de criptografia / descriptografia simples usando OpenSSL
class UnsafeCrypto
{
const METHOD = 'aes-256-ctr';
/**
* Encrypts (but does not authenticate) a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = openssl_random_pseudo_bytes($nonceSize);
$ciphertext = openssl_encrypt(
$message,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
// Now let's pack the IV and the ciphertext together
// Naively, we can just concatenate
if ($encode) {
return base64_encode($nonce.$ciphertext);
}
return $nonce.$ciphertext;
}
/**
* Decrypts (but does not verify) a message
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string
*/
public static function decrypt($message, $key, $encoded = false)
{
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = mb_substr($message, 0, $nonceSize, '8bit');
$ciphertext = mb_substr($message, $nonceSize, null, '8bit');
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
return $plaintext;
}
}
Exemplo de uso
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Demonstração : https://3v4l.org/jl7qR
A biblioteca de criptografia simples acima ainda não é segura de usar. Precisamos autenticar textos cifrados e verificá-los antes de decifrar .
Nota : Por padrão, UnsafeCrypto::encrypt()
retornará uma sequência binária bruta. Chame assim se precisar armazená-lo em um formato binário seguro (codificado em base64):
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);
var_dump($encrypted, $decrypted);
Demo : http://3v4l.org/f5K93
Wrapper de autenticação simples
class SaferCrypto extends UnsafeCrypto
{
const HASH_ALGO = 'sha256';
/**
* Encrypts then MACs a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded string
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
list($encKey, $authKey) = self::splitKeys($key);
// Pass to UnsafeCrypto::encrypt
$ciphertext = parent::encrypt($message, $encKey);
// Calculate a MAC of the IV and ciphertext
$mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);
if ($encode) {
return base64_encode($mac.$ciphertext);
}
// Prepend MAC to the ciphertext and return to caller
return $mac.$ciphertext;
}
/**
* Decrypts a message (after verifying integrity)
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string (raw binary)
*/
public static function decrypt($message, $key, $encoded = false)
{
list($encKey, $authKey) = self::splitKeys($key);
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
// Hash Size -- in case HASH_ALGO is changed
$hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
$mac = mb_substr($message, 0, $hs, '8bit');
$ciphertext = mb_substr($message, $hs, null, '8bit');
$calculated = hash_hmac(
self::HASH_ALGO,
$ciphertext,
$authKey,
true
);
if (!self::hashEquals($mac, $calculated)) {
throw new Exception('Encryption failure');
}
// Pass to UnsafeCrypto::decrypt
$plaintext = parent::decrypt($ciphertext, $encKey);
return $plaintext;
}
/**
* Splits a key into two separate keys; one for encryption
* and the other for authenticaiton
*
* @param string $masterKey (raw binary)
* @return array (two raw binary strings)
*/
protected static function splitKeys($masterKey)
{
// You really want to implement HKDF here instead!
return [
hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
];
}
/**
* Compare two strings without leaking timing information
*
* @param string $a
* @param string $b
* @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
* @return boolean
*/
protected static function hashEquals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$nonce = openssl_random_pseudo_bytes(32);
return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
}
}
Exemplo de uso
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Demonstrações : binário bruto , codificado em base64
Se alguém desejar usar esta SaferCrypto
biblioteca em um ambiente de produção ou sua própria implementação dos mesmos conceitos, recomendo que você entre em contato com os criptografadores residentes para obter uma segunda opinião antes de você. Eles poderão falar sobre erros dos quais talvez eu nem saiba.
Você ficará muito melhor usando uma biblioteca de criptografia respeitável .