Pessoalmente, eu usaria mcrypt
como outros postados. Mas há muito mais a ser observado ...
Como criptografar e descriptografar uma senha no PHP?
Veja abaixo uma classe forte que cuida de tudo para você:
Qual é o algoritmo mais seguro para criptografar as senhas?
mais seguro ? qualquer um deles. O método mais seguro para criptografar é proteger contra vulnerabilidades de divulgação de informações (XSS, inclusão remota etc.). Se sair, o invasor pode eventualmente quebrar a criptografia (nenhuma criptografia é 100% reversível sem a chave - como @NullUserException indica que isso não é totalmente verdade. Existem alguns esquemas de criptografia impossíveis de decifrar, como o OneTimePad ) .
Onde guardo a chave privada?
O que eu faria é usar 3 chaves. Um é fornecido pelo usuário, um é específico ao aplicativo e o outro é específico ao usuário (como um sal). A chave específica do aplicativo pode ser armazenada em qualquer lugar (em um arquivo de configuração fora da raiz da web, em uma variável ambiental, etc.). O específico do usuário seria armazenado em uma coluna no banco de dados próximo à senha criptografada. O usuário fornecido não seria armazenado. Então, você faria algo assim:
$key = $userKey . $serverKey . $userSuppliedKey;
O benefício é que qualquer uma das duas chaves pode ser comprometida sem que os dados sejam comprometidos. Se houver um ataque de injeção SQL, eles podem obter o $userKey
, mas não o outro 2. Se houver uma exploração do servidor local, eles podem obter $userKey
e $serverKey
, mas não o terceiro $userSuppliedKey
. Se eles baterem no usuário com uma chave inglesa, poderão obter o $userSuppliedKey
, mas não os outros 2 (mas, novamente, se o usuário for espancado com uma chave inglesa, você será tarde demais).
Em vez de armazenar a chave privada, é uma boa ideia exigir que os usuários insiram a chave privada sempre que precisarem de uma senha descriptografada? (Os usuários deste aplicativo podem ser confiáveis)
Absolutamente. De fato, essa é a única maneira de fazê-lo. Caso contrário, você precisará armazenar uma versão não criptografada em um formato de armazenamento durável (memória compartilhada, como APC ou memcached, ou em um arquivo de sessão). Isso está se expondo a compromissos adicionais. Nunca armazene a versão não criptografada da senha em nada, exceto em uma variável local.
De que maneiras a senha pode ser roubada e descriptografada? Do que eu preciso estar ciente?
Qualquer forma de comprometimento de seus sistemas permitirá que eles visualizem dados criptografados. Se eles puderem injetar código ou acessar o sistema de arquivos, poderão visualizar os dados descriptografados (já que podem editar os arquivos que descriptografam os dados). Qualquer forma de ataque Replay ou MITM também lhes dará acesso total às chaves envolvidas. Farejar o tráfego HTTP bruto também fornecerá as chaves.
Use SSL para todo o tráfego. E verifique se nada no servidor possui algum tipo de vulnerabilidade (CSRF, XSS, Injeção de SQL, Escalação de Privilégios, Execução Remota de Código, etc.).
Edit: Aqui está uma implementação de classe PHP de um método de criptografia forte:
/**
* A class to handle secure encryption and decryption of arbitrary data
*
* Note that this is not just straight encryption. It also has a few other
* features in it to make the encrypted data far more secure. Note that any
* other implementations used to decrypt data will have to do the same exact
* operations.
*
* Security Benefits:
*
* - Uses Key stretching
* - Hides the Initialization Vector
* - Does HMAC verification of source data
*
*/
class Encryption {
/**
* @var string $cipher The mcrypt cipher to use for this instance
*/
protected $cipher = '';
/**
* @var int $mode The mcrypt cipher mode to use
*/
protected $mode = '';
/**
* @var int $rounds The number of rounds to feed into PBKDF2 for key generation
*/
protected $rounds = 100;
/**
* Constructor!
*
* @param string $cipher The MCRYPT_* cypher to use for this instance
* @param int $mode The MCRYPT_MODE_* mode to use for this instance
* @param int $rounds The number of PBKDF2 rounds to do on the key
*/
public function __construct($cipher, $mode, $rounds = 100) {
$this->cipher = $cipher;
$this->mode = $mode;
$this->rounds = (int) $rounds;
}
/**
* Decrypt the data with the provided key
*
* @param string $data The encrypted datat to decrypt
* @param string $key The key to use for decryption
*
* @returns string|false The returned string if decryption is successful
* false if it is not
*/
public function decrypt($data, $key) {
$salt = substr($data, 0, 128);
$enc = substr($data, 128, -64);
$mac = substr($data, -64);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
if (!hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
return false;
}
$dec = mcrypt_decrypt($this->cipher, $cipherKey, $enc, $this->mode, $iv);
$data = $this->unpad($dec);
return $data;
}
/**
* Encrypt the supplied data using the supplied key
*
* @param string $data The data to encrypt
* @param string $key The key to encrypt with
*
* @returns string The encrypted data
*/
public function encrypt($data, $key) {
$salt = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
$data = $this->pad($data);
$enc = mcrypt_encrypt($this->cipher, $cipherKey, $data, $this->mode, $iv);
$mac = hash_hmac('sha512', $enc, $macKey, true);
return $salt . $enc . $mac;
}
/**
* Generates a set of keys given a random salt and a master key
*
* @param string $salt A random string to change the keys each encryption
* @param string $key The supplied key to encrypt with
*
* @returns array An array of keys (a cipher key, a mac key, and a IV)
*/
protected function getKeys($salt, $key) {
$ivSize = mcrypt_get_iv_size($this->cipher, $this->mode);
$keySize = mcrypt_get_key_size($this->cipher, $this->mode);
$length = 2 * $keySize + $ivSize;
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
$cipherKey = substr($key, 0, $keySize);
$macKey = substr($key, $keySize, $keySize);
$iv = substr($key, 2 * $keySize);
return array($cipherKey, $macKey, $iv);
}
/**
* Stretch the key using the PBKDF2 algorithm
*
* @see http://en.wikipedia.org/wiki/PBKDF2
*
* @param string $algo The algorithm to use
* @param string $key The key to stretch
* @param string $salt A random salt
* @param int $rounds The number of rounds to derive
* @param int $length The length of the output key
*
* @returns string The derived key.
*/
protected function pbkdf2($algo, $key, $salt, $rounds, $length) {
$size = strlen(hash($algo, '', true));
$len = ceil($length / $size);
$result = '';
for ($i = 1; $i <= $len; $i++) {
$tmp = hash_hmac($algo, $salt . pack('N', $i), $key, true);
$res = $tmp;
for ($j = 1; $j < $rounds; $j++) {
$tmp = hash_hmac($algo, $tmp, $key, true);
$res ^= $tmp;
}
$result .= $res;
}
return substr($result, 0, $length);
}
protected function pad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$padAmount = $length - strlen($data) % $length;
if ($padAmount == 0) {
$padAmount = $length;
}
return $data . str_repeat(chr($padAmount), $padAmount);
}
protected function unpad($data) {
$length = mcrypt_get_block_size($this->cipher, $this->mode);
$last = ord($data[strlen($data) - 1]);
if ($last > $length) return false;
if (substr($data, -1 * $last) !== str_repeat(chr($last), $last)) {
return false;
}
return substr($data, 0, -1 * $last);
}
}
Note que eu estou usando uma função adicionado no PHP 5.6: hash_equals
. Se você estiver com menos de 5,6, poderá usar esta função substituta que implementa uma função de comparação com tempo seguro usando a verificação dupla do HMAC :
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Uso:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Então, para descriptografar:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Observe que eu usei $e2
a segunda vez para mostrar que diferentes instâncias ainda decifrariam os dados corretamente.
Agora, como funciona / por que usá-lo em outra solução:
Chaves
As chaves não são usadas diretamente. Em vez disso, a chave é esticada por uma derivação PBKDF2 padrão.
A chave usada para criptografia é exclusiva para cada bloco de texto criptografado. A chave fornecida torna-se, portanto, uma "chave mestra". Essa classe, portanto, fornece rotação de chaves para chaves de cifra e autenticação.
NOTA IMPORTANTE , o $rounds
parâmetro está configurado para chaves aleatórias verdadeiras com força suficiente (128 bits de aleatório Criptograficamente seguro no mínimo). Se você for usar uma senha ou chave não aleatória (ou menos aleatória que 128 bits de CS aleatória), deverá aumentar este parâmetro. Eu sugeriria um mínimo de 10000 para senhas (quanto mais você puder pagar, melhor, mas isso aumentará o tempo de execução) ...
Integridade de dados
- A versão atualizada usa ENCRYPT-THEN-MAC, que é um método muito melhor para garantir a autenticidade dos dados criptografados.
Criptografia:
- Ele usa mcrypt para realmente executar a criptografia. Eu sugeriria o uso de um
MCRYPT_BLOWFISH
ou de MCRYPT_RIJNDAEL_128
códigos e MCRYPT_MODE_CBC
para o modo. É forte o suficiente e ainda bastante rápido (um ciclo de criptografia e descriptografia leva cerca de 1/2 segundo na minha máquina).
Agora, como no ponto 3 da primeira lista, o que isso daria a você é uma função como esta:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Você pode esticá-lo na makeKey()
função, mas como ele será expandido mais tarde, não há realmente um grande ponto em fazê-lo.
Quanto ao tamanho do armazenamento, isso depende do texto sem formatação. O Blowfish usa um tamanho de bloco de 8 bytes, então você terá:
- 16 bytes para o sal
- 64 bytes para o hmac
- comprimento dos dados
- Preenchimento para que o comprimento dos dados% 8 == 0
Portanto, para uma fonte de dados de 16 caracteres, haverá 16 caracteres a serem criptografados. Isso significa que o tamanho real dos dados criptografados é de 16 bytes devido ao preenchimento. Em seguida, adicione 16 bytes para o salt e 64 bytes para o hmac e o tamanho total armazenado é 96 bytes. Portanto, há no máximo uma sobrecarga de 80 caracteres e, na pior, uma sobrecarga de 87 caracteres ...
Espero que ajude ...
Nota: 12/11/12: Acabei de atualizar esta classe com um método de criptografia MUITO melhor, usando chaves derivadas melhores e corrigindo a geração MAC ...