Como criptografar / descriptografar dados em php?

110

Atualmente sou estudante e estou estudando PHP, estou tentando fazer uma criptografia / descriptografia simples de dados em PHP. Fiz algumas pesquisas online e algumas delas foram bastante confusas (pelo menos para mim).

Aqui está o que estou tentando fazer:

Eu tenho uma tabela que consiste nestes campos (UserID, Fname, Lname, Email, Password)

O que eu quero é ter todos os campos criptografados e, em seguida, ser descriptografados (é possível usar sha256para criptografia / descriptografia, se não algum algoritmo de criptografia)

Outra coisa que quero aprender é como criar um caminho único hash(sha256)combinado com um bom "sal". (Basicamente, eu só quero ter uma implementação simples de criptografia / descriptografia, hash(sha256)+salt) Senhor / Senhora, suas respostas seriam de grande ajuda e serão muito apreciadas. Obrigado ++

Randel Ramirez
fonte
3
Os horrores da criptografia Sha-1!
Naftali também conhecido como Neal
9
SHA é um hash, não criptografia. O ponto principal é que um hash não pode ser revertido para os dados originais (pelo menos não facilmente). Você provavelmente quer o mcrypt ou, se ele não estiver disponível, eu recomendaria o phpseclib - embora seja importante notar que qualquer implementação de PHP puro de qualquer coisa que envolva muita matemática de baixo nível será sloooooowww ... É por isso que gosto do phpseclib, porque ele usa mcrypt primeiro se estiver disponível e só recorre às implementações de PHP como último recurso.
DaveRandom
7
Normalmente, você não deseja descriptografar uma senha!
Ja͢ck
1
Basicamente, você não deve pensar em criptografia neste nível, deve pensar em controle de acesso, confidencialidade, integridade e autenticação. Depois disso, verifique como você pode fazer isso, possivelmente usando criptografia ou hash seguro. Você pode querer ler em PBKDF2 e bcrypt / scrypt para entender o hashing seguro de senhas e similares.
Maarten Bodewes

Respostas:

289

Prefácio

Começando com a definição da sua mesa:

- UserID
- Fname
- Lname
- Email
- Password
- IV

Aqui estão as mudanças:

  1. Os campos Fname, Lnamee Emailserão criptografados usando uma cifra simétrica, fornecida pelo OpenSSL ,
  2. O IVcampo armazenará o vetor de inicialização usado para criptografia. Os requisitos de armazenamento dependem da cifra e do modo usado; mais sobre isso mais tarde.
  3. O Passwordcampo será hash usando um one-way hash de senha,

Encriptação

Cifra e modo

A escolha da melhor cifra e modo de criptografia está além do escopo desta resposta, mas a escolha final afeta o tamanho da chave de criptografia e do vetor de inicialização; para esta postagem, usaremos AES-256-CBC, que tem um tamanho de bloco fixo de 16 bytes e um tamanho de chave de 16, 24 ou 32 bytes.

Chave de encriptação

Uma boa chave de criptografia é um blob binário gerado a partir de um gerador de números aleatórios confiável. O seguinte exemplo seria recomendado (> = 5,3):

$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe

Isso pode ser feito uma ou várias vezes (se desejar criar uma cadeia de chaves de criptografia). Mantenha-os o mais privados possível.

IV

O vetor de inicialização adiciona aleatoriedade à criptografia e é necessário para o modo CBC. Idealmente, esses valores devem ser usados ​​apenas uma vez (tecnicamente, uma vez por chave de criptografia), portanto, uma atualização de qualquer parte de uma linha deve gerá-la novamente.

Uma função é fornecida para ajudá-lo a gerar o IV:

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

Exemplo

Vamos criptografar o campo de nome, usando o anterior $encryption_keye $iv; para fazer isso, temos que preencher nossos dados com o tamanho do bloco:

function pkcs7_pad($data, $size)
{
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}

$name = 'Jack';
$enc_name = openssl_encrypt(
    pkcs7_pad($name, 16), // padded data
    'AES-256-CBC',        // cipher and mode
    $encryption_key,      // secret key
    0,                    // options (not used)
    $iv                   // initialisation vector
);

Requisitos de armazenamento

A saída criptografada, como o IV, é binária; o armazenamento desses valores em um banco de dados pode ser realizado usando tipos de coluna designados como BINARYou VARBINARY.

O valor de saída, como o IV, é binário; para armazenar esses valores no MySQL, considere usar as colunas BINARYouVARBINARY . Se isso não for uma opção, você também pode converter os dados binários em uma representação textual usando base64_encode()ou bin2hex(), para fazer isso requer entre 33% a 100% mais espaço de armazenamento.

Decifrar

A descriptografia dos valores armazenados é semelhante:

function pkcs7_unpad($data)
{
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}

$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];

$name = pkcs7_unpad(openssl_decrypt(
    $enc_name,
    'AES-256-CBC',
    $encryption_key,
    0,
    $iv
));

Criptografia autenticada

Você pode melhorar ainda mais a integridade do texto cifrado gerado anexando uma assinatura gerada a partir de uma chave secreta (diferente da chave criptografada) e o texto cifrado. Antes de o texto cifrado ser descriptografado, a assinatura é primeiro verificada (de preferência com um método de comparação de tempo constante).

Exemplo

// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);

// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;

// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);

if (hash_equals($auth, $actual_auth)) {
    // perform decryption
}

Veja também: hash_equals()

Hashing

O armazenamento de uma senha reversível em seu banco de dados deve ser evitado tanto quanto possível; você deseja apenas verificar a senha em vez de saber seu conteúdo. Se um usuário perder a senha, é melhor permitir que ele a redefina em vez de enviar a senha original (certifique-se de que a redefinição da senha só possa ser feita por um período limitado).

Aplicar uma função hash é uma operação unilateral; depois, pode ser usado com segurança para verificação sem revelar os dados originais; para senhas, um método de força bruta é uma abordagem viável para descobri-lo devido ao seu comprimento relativamente curto e escolhas de senhas pobres de muitas pessoas.

Algoritmos de hash, como MD5 ou SHA1, foram feitos para verificar o conteúdo do arquivo em relação a um valor de hash conhecido. Eles são bastante otimizados para tornar essa verificação o mais rápido possível e, ao mesmo tempo, ser precisa. Dado seu espaço de saída relativamente limitado, foi fácil construir um banco de dados com senhas conhecidas e suas respectivas saídas hash, as rainbow tables.

Adicionar um salt à senha antes de fazer o hash tornaria uma tabela de arco-íris inútil, mas os recentes avanços de hardware tornaram as pesquisas de força bruta uma abordagem viável. É por isso que você precisa de um algoritmo de hash deliberadamente lento e simplesmente impossível de otimizar. Ele também deve ser capaz de aumentar a carga para hardware mais rápido sem afetar a capacidade de verificar os hashes de senha existentes para torná-lo à prova de futuro.

Atualmente, existem duas opções populares disponíveis:

  1. PBKDF2 (função de derivação de chave baseada em senha v2)
  2. bcrypt (também conhecido como Blowfish)

Esta resposta usará um exemplo com bcrypt.

Geração

Um hash de senha pode ser gerado assim:

$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
    13, // 2^n cost factor
    substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);

$hash = crypt($password, $salt);

O sal é gerado openssl_random_pseudo_bytes()para formar um blob aleatório de dados que é então percorrido base64_encode()e strtr()para corresponder ao alfabeto exigido de [A-Za-z0-9/.].

A crypt()função executa o hashing com base no algoritmo ( $2y$para Blowfish), o fator de custo (um fator de 13 leva aproximadamente 0,40s em uma máquina de 3GHz) e o sal de 22 caracteres.

Validação

Depois de buscar a linha que contém as informações do usuário, você valida a senha desta maneira:

$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash

$given_hash = crypt($given_password, $db_hash);

if (isEqual($given_hash, $db_hash)) {
    // user password verified
}

// constant time string compare
function isEqual($str1, $str2)
{
    $n1 = strlen($str1);
    if (strlen($str2) != $n1) {
        return false;
    }
    for ($i = 0, $diff = 0; $i != $n1; ++$i) {
        $diff |= ord($str1[$i]) ^ ord($str2[$i]);
    }
    return !$diff;
}

Para verificar uma senha, você chama crypt()novamente, mas passa o hash calculado anteriormente como o valor de sal. O valor de retorno produz o mesmo hash se a senha fornecida corresponder ao hash. Para verificar o hash, geralmente é recomendado usar uma função de comparação de tempo constante para evitar ataques de temporização.

Hash de senha com PHP 5.5

PHP 5.5 introduziu as funções de hash de senha que você pode usar para simplificar o método de hash acima:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

E verificando:

if (password_verify($given_password, $db_hash)) {
    // password valid
}

Veja também: password_hash(),password_verify()

Ja͢ck
fonte
Que comprimento devo usar para armazenar nome, sobrenome, e-mail, etc. para uma aposta mais segura? varbinary (???)
BentCoder
2
Claro, mas depende de como está sendo usado. Se você publicar uma biblioteca de criptografia, não sabe como os desenvolvedores a implementarão. É por isso que github.com/defuse/php-encryption fornece criptografia de chave simétrica autenticada e não permite que os desenvolvedores enfraqueçam sem editar seu código.
Scott Arciszewski
2
@Scott Muito bem, adicionei um exemplo de criptografia autenticada; obrigado pelo empurrão :)
Ja͢ck
1
1 para criptografia autenticada. Não há informações suficientes na pergunta para dizer que o AE não é necessário aqui. Certamente, o tráfego SQL geralmente passa por uma rede com propriedades de segurança desconhecidas, assim como o tráfego do banco de dados para o armazenamento. Backups e replicação também. Qual é o modelo de ameaça? A pergunta não diz, e pode ser perigoso fazer suposições.
Jason Orendorff
1
Em vez de codificar $iv_size = 16;, eu usaria: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))para indicar o link entre o tamanho do iv para usar com a cifra usada. Você também pode expandir um pouco a necessidade (ou não) de pkcs7_pad()/ pkcs7_unpad(), ou simplesmente simplificar a postagem livrando-se deles e usar "aes-256-ctr". Excelente postagem @ Ja͢ck
Patrick Allaert
24

Acho que isso já foi respondido antes ... mas de qualquer maneira, se você deseja criptografar / descriptografar dados, não pode usar SHA256

//Key
$key = 'SuperSecretKey';

//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);

//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);
Romo
fonte
7
Você também não deve usar o ECB, por falar nisso.
Maarten Bodewes
7
As chaves devem ser bytes aleatórios ou você deve usar uma função de derivação de chave segura.
Maarten Bodewes
4
MCRYPT_RIJNDAEL_256 não é uma função padronizada, você deve usar AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes
14

Histórico da resposta e explicação

Para entender esta questão, você deve primeiro entender o que é SHA256. SHA256 é uma função criptográfica de hash . Uma função criptográfica Hash é uma função unilateral, cuja saída é criptograficamente segura. Isso significa que é fácil calcular um hash (equivalente a criptografar dados), mas difícil obter a entrada original usando o hash (equivalente a descriptografar os dados). Visto que usar uma função de hash criptográfico significa que a descriptografia é computacionalmente inviável, portanto, você não pode realizar a descriptografia com SHA256.

O que você deseja usar é uma função bidirecional, mas mais especificamente, uma Block Cipher . Uma função que permite criptografar e descriptografar dados. As funções mcrypt_encrypte mcrypt_decryptpor padrão usam o algoritmo Blowfish. O uso de mcrypt pelo PHP pode ser encontrado neste manual . Também existe uma lista de definições de cifras para selecionar a cifra que o mcrypt usa. Um wiki sobre Blowfish pode ser encontrado na Wikipedia . Uma cifra de bloco criptografa a entrada em blocos de tamanho e posição conhecidos com uma chave conhecida, para que os dados possam ser posteriormente descriptografados usando a chave. Isso é o que o SHA256 não pode fornecer a você.

Código

$key = 'ThisIsTheCipherKey';

$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);
cytinus
fonte
Você também não deve usar o ECB, por falar nisso.
Maarten Bodewes
As chaves devem ser bytes aleatórios ou você deve usar uma função de derivação de chave segura.
Maarten Bodewes
4
Nunca use o modo ECB. É inseguro e, na maioria das vezes, não ajuda realmente na criptografia dos dados (em vez de apenas codificá-los). Consulte o excelente artigo da Wikipedia sobre o assunto para obter mais informações.
Holger apenas
1
É melhor não usar mcrypt, é abandonware, não é atualizado há anos e não suporta preenchimento padrão PKCS # 7 (née PKCS # 5), apenas preenchimento nulo não padrão que nem pode ser usado com dados binários . O mcrypt tinha muitos bugs pendentes desde 2003. Em vez disso, considere o uso de defuse , ele está sendo mantido e está correto.
zaph
9

Aqui está um exemplo usando openssl_encrypt

//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);

//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;
Vivek
fonte
2
Em vez de mcrypt_create_iv(), eu usaria:, openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))desta forma a metodologia funciona para qualquer valor de $ encryptionMethod e usaria apenas a extensão openssl.
Patrick Allaert
O código acima retorna falsepara openssl_decrypt(). Consulte stackoverflow.com/q/41952509/1066234 Como as cifras de bloco, como AES, exigem que os dados de entrada sejam um múltiplo exato do tamanho do bloco (16 bytes para AES), o preenchimento é necessário.
Kai Noack,
6
     function my_simple_crypt( $string, $action = 'e' ) {
        // you may change these values to your own
        $secret_key = 'my_simple_secret_key';
        $secret_iv = 'my_simple_secret_iv';

        $output = false;
        $encrypt_method = "AES-256-CBC";
        $key = hash( 'sha256', $secret_key );
        $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

        if( $action == 'e' ) {
            $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
        }
        else if( $action == 'd' ){
            $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
        }

        return $output;
    }
gauravbhai daxini
fonte
muito simples ! Eu o uso para criptografia-descriptografia de segmento de url. Obrigado
Mahbub Tito
0

Levei um bom tempo para descobrir como não obter um falseao usar openssl_decrypt()e criptografar e descriptografar funcionando.

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

Se você deseja passar a string criptografada por meio de um URL, é necessário codificar a string em url:

    $encrypted = urlencode($encrypted);

Para entender melhor o que está acontecendo, leia:

Para gerar chaves de 16 bytes, você pode usar:

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

Para ver as mensagens de erro do openssl, você pode usar: echo openssl_error_string();

Espero que ajude.

Kai Noack
fonte