Come crittografare / decrittografare i dati in php?


110

Sono attualmente uno studente e sto studiando PHP, sto cercando di fare una semplice crittografia / decrittografia dei dati in PHP. Ho fatto delle ricerche online e alcune di esse erano piuttosto confuse (almeno per me).

Ecco cosa sto cercando di fare:

Ho una tabella composta da questi campi (UserID, Fname, Lname, Email, Password)

Quello che voglio è che tutti i campi siano crittografati e quindi decrittografati (è possibile utilizzare sha256per crittografia / decrittografia, se non qualsiasi algoritmo di crittografia)

Un'altra cosa che voglio imparare è come creare un senso unico hash(sha256)abbinato ad un buon "sale". (Fondamentalmente voglio solo avere una semplice implementazione della crittografia / decrittografia, hash(sha256)+salt) signore / signora, le vostre risposte sarebbero di grande aiuto e sarebbero molto apprezzate. Grazie ++




9
SHA è un hash, non una crittografia. Il punto chiave è che un hash non può essere ripristinato ai dati originali (non facilmente, comunque). Probabilmente vuoi mcrypt o se non è disponibile consiglierei phpseclib , anche se è importante notare che qualsiasi implementazione PHP puro di qualsiasi cosa che coinvolga molta matematica di basso livello sarà sloooooowww ... Ecco perché mi piace phpseclib, perché utilizza prima mcrypt se è disponibile e ricorre alle implementazioni PHP solo come ultima risorsa.
DaveRandom

7
Normalmente non vuoi essere in grado di decrittografare una password!
Ja͢ck

1
Fondamentalmente non dovresti pensare alla crittografia a questo livello, dovresti pensare al controllo degli accessi, alla riservatezza, all'integrità e all'autenticazione. Dopo di che controlla come puoi ottenerlo, possibilmente usando la crittografia o l'hashing sicuro. Potresti voler leggere in PBKDF2 e bcrypt / scrypt per comprendere l'hashing sicuro delle password e simili.
Maarten Bodewes

Risposte:


289

Prefazione

A partire dalla definizione della tabella:

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

Ecco le modifiche:

  1. I campi Fname, Lnamee Emailverranno crittografati utilizzando una crittografia simmetrica, fornita da OpenSSL ,
  2. Il IVcampo memorizzerà il vettore di inizializzazione utilizzato per la crittografia. I requisiti di archiviazione dipendono dalla crittografia e dalla modalità utilizzata; ne parleremo più avanti.
  3. Il Passwordcampo verrà sottoposto ad hashing utilizzando un hash della password unidirezionale ,

crittografia

Cipher e modalità

La scelta della migliore crittografia e modalità di crittografia va oltre lo scopo di questa risposta, ma la scelta finale influisce sulla dimensione sia della chiave di crittografia che del vettore di inizializzazione; per questo post utilizzeremo AES-256-CBC che ha una dimensione del blocco fissa di 16 byte e una dimensione della chiave di 16, 24 o 32 byte.

Chiave crittografica

Una buona chiave di crittografia è un BLOB binario generato da un generatore di numeri casuali affidabile. Si consiglia il seguente esempio (> = 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

Questa operazione può essere eseguita una o più volte (se desideri creare una catena di chiavi di crittografia). Tienili il più privati ​​possibile.

IV

Il vettore di inizializzazione aggiunge casualità alla crittografia ed è richiesto per la modalità CBC. Questi valori dovrebbero essere idealmente usati solo una volta (tecnicamente una volta per chiave di crittografia), quindi un aggiornamento a qualsiasi parte di una riga dovrebbe rigenerarla.

Viene fornita una funzione per aiutarti a generare l'IV:

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

Esempio

Crittografiamo il campo del nome, utilizzando i precedenti $encryption_keye $iv; per fare questo, dobbiamo riempire i nostri dati alla dimensione del blocco:

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
);

Requisiti di archiviazione

L'output crittografato, come l'IV, è binario; la memorizzazione di questi valori in un database può essere eseguita utilizzando tipi di colonna designati come BINARYo VARBINARY.

Il valore di output, come IV, è binario; per memorizzare questi valori in MySQL, considera l'utilizzo di colonne BINARYoVARBINARY . Se questa non è un'opzione, è anche possibile convertire i dati binari in una rappresentazione testuale utilizzando base64_encode()o bin2hex(), ciò richiede dal 33% al 100% in più di spazio di archiviazione.

decrittazione

La decrittografia dei valori memorizzati è simile:

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
));

Crittografia autenticata

È possibile migliorare ulteriormente l'integrità del testo cifrato generato aggiungendo una firma generata da una chiave segreta (diversa dalla chiave di crittografia) e il testo cifrato. Prima che il testo cifrato venga decrittografato, la firma viene prima verificata (preferibilmente con un metodo di confronto a tempo costante).

Esempio

// 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
}

Guarda anche: hash_equals()

hashing

La memorizzazione di una password reversibile nel database deve essere evitata il più possibile; desideri solo verificare la password invece di conoscerne il contenuto. Se un utente perde la password, è meglio consentirgli di reimpostarla piuttosto che inviargli quella originale (assicurati che la reimpostazione della password possa essere eseguita solo per un tempo limitato).

L'applicazione di una funzione hash è un'operazione unidirezionale; successivamente può essere tranquillamente utilizzato per la verifica senza rivelare i dati originali; per le password, un metodo di forza bruta è un approccio fattibile per scoprirlo a causa della sua lunghezza relativamente breve e delle scarse scelte di password di molte persone.

Algoritmi di hash come MD5 o SHA1 sono stati creati per verificare il contenuto del file rispetto a un valore hash noto. Sono notevolmente ottimizzati per rendere questa verifica il più veloce possibile pur rimanendo accurati. Dato il loro spazio di output relativamente limitato, è stato facile creare un database con password note e i rispettivi output hash, le tabelle arcobaleno.

Aggiungere un salt alla password prima dell'hashing renderebbe inutile una tabella arcobaleno, ma i recenti progressi dell'hardware hanno reso le ricerche di forza bruta un approccio praticabile. Ecco perché hai bisogno di un algoritmo di hashing che sia deliberatamente lento e semplicemente impossibile da ottimizzare. Dovrebbe anche essere in grado di aumentare il carico per hardware più veloce senza influire sulla capacità di verificare gli hash delle password esistenti per renderlo a prova di futuro.

Attualmente sono disponibili due scelte popolari:

  1. PBKDF2 (funzione di derivazione chiave basata su password v2)
  2. bcrypt (aka Blowfish)

Questa risposta utilizzerà un esempio con bcrypt.

Generazione

Un hash della password può essere generato in questo modo:

$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);

Il sale viene generato con openssl_random_pseudo_bytes()per formare un blob casuale di dati che viene quindi eseguito base64_encode()e strtr()per abbinare l'alfabeto richiesto [A-Za-z0-9/.].

La crypt()funzione esegue l'hashing in base all'algoritmo ( $2y$per Blowfish), al fattore di costo (un fattore di 13 impiega circa 0.40s su una macchina a 3GHz) e il sale di 22 caratteri.

Validazione

Dopo aver recuperato la riga contenente le informazioni sull'utente, convalidi la password in questo modo:

$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;
}

Per verificare una password, si chiama di crypt()nuovo ma si passa l'hash calcolato in precedenza come valore salt. Il valore restituito restituisce lo stesso hash se la password fornita corrisponde all'hash. Per verificare l'hash, è spesso consigliabile utilizzare una funzione di confronto a tempo costante per evitare attacchi temporali.

Hashing delle password con PHP 5.5

PHP 5.5 ha introdotto le funzioni di hashing della password che puoi usare per semplificare il metodo di hashing sopra:

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

E verificando:

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

Vedi anche: password_hash(),password_verify()


Quale lunghezza devo utilizzare per memorizzare nome, cognome, e-mail ecc. Per una scommessa più sicura? varbinary (???)
BentCoder

2
Certo, ma dipende da come viene utilizzato. Se pubblichi una libreria di crittografia, non sai come la implementeranno gli sviluppatori. Ecco perché github.com/defuse/php-encryption fornisce la crittografia a chiave simmetrica autenticata e non consente agli sviluppatori di indebolirla senza modificarne il codice.
Scott Arciszewski

2
@ Scott Molto bene, ho aggiunto un esempio di crittografia autenticata; grazie per la spinta :)
Ja͢ck

1
+1 per la crittografia autenticata. Non ci sono abbastanza informazioni nella domanda per dire che AE non è necessario qui. Certamente il traffico SQL passa spesso su una rete con proprietà di sicurezza sconosciute, così come il traffico dal database allo storage. Anche backup e replica. Qual è il modello di minaccia? La domanda non dice e potrebbe essere pericoloso fare supposizioni.
Jason Orendorff

1
Invece di hard-coding $iv_size = 16;, userei : $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))per indicare il collegamento tra la dimensione di iv da utilizzare con il cifrario utilizzato. Potresti anche espandere un po 'la necessità (o meno) di pkcs7_pad()/ pkcs7_unpad(), o semplicemente semplificare il post sbarazzandoti di loro e usando "aes-256-ctr". Ottimo post @ Ja͢ck
Patrick Allaert

24

Penso che questo abbia già avuto una risposta ... ma comunque, se vuoi crittografare / decrittografare i dati, non puoi usare 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);

7
Non dovresti nemmeno usare ECB, se è per questo.
Maarten Bodewes

7
Le chiavi dovrebbero essere byte casuali, oppure dovresti usare una funzione di derivazione della chiave sicura.
Maarten Bodewes

4
MCRYPT_RIJNDAEL_256 non è una funzione standardizzata, dovresti usare AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes

14

Risposta Sfondo e spiegazione

Per capire questa domanda, devi prima capire cos'è SHA256. SHA256 è una funzione hash crittografica . Una funzione hash crittografica è una funzione unidirezionale, il cui output è protetto crittograficamente. Ciò significa che è facile calcolare un hash (equivalente alla crittografia dei dati), ma difficile ottenere l'input originale utilizzando l'hash (equivalente alla decrittografia dei dati). Poiché l'utilizzo di una funzione hash crittografica significa che la decrittografia non è computazionalmente fattibile, quindi non è possibile eseguire la decrittografia con SHA256.

Quello che vuoi usare è una funzione a due vie, ma più specificamente, un Block Cipher . Una funzione che consente sia la crittografia che la decrittografia dei dati. Le funzioni mcrypt_encrypte mcrypt_decryptper impostazione predefinita utilizzano l'algoritmo Blowfish. L'uso di mcrypt da parte di PHP può essere trovato in questo manuale . Esiste anche un elenco di definizioni di crittografia per selezionare la crittografia utilizzata da mcrypt. Un wiki su Blowfish può essere trovato su Wikipedia . Un cifrario a blocchi crittografa l'input in blocchi di dimensioni e posizione note con una chiave nota, in modo che i dati possano essere successivamente decrittografati utilizzando la chiave. Questo è ciò che SHA256 non può fornirti.

Codice

$key = 'ThisIsTheCipherKey';

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

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);

Non dovresti nemmeno usare ECB, se è per questo.
Maarten Bodewes

Le chiavi dovrebbero essere byte casuali, oppure dovresti usare una funzione di derivazione della chiave sicura.
Maarten Bodewes

4
Non usare mai la modalità ECB. Non è sicuro e la maggior parte delle volte non aiuta davvero a crittografare i dati (piuttosto che semplicemente codificarli). Vedi l' eccellente articolo di Wikipedia sull'argomento per maggiori informazioni.
Holger solo l'

1
È meglio non usare mcrypt, è un software abbandonato, non è stato aggiornato da anni e non supporta il riempimento standard PKCS # 7 (née PKCS # 5), solo il riempimento nullo non standard che non può essere utilizzato nemmeno con dati binari . mcrypt aveva molti bug in sospeso risalenti al 2003 .. Considerare invece di utilizzare il disinnesco , viene mantenuto ed è corretto.
Zaf

9

Ecco un esempio che utilizza 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;

2
Invece di mcrypt_create_iv(), openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))userei:, in questo modo la metodologia funziona per qualsiasi valore di $ encryptionMethod e userei solo l'estensione openssl.
Patrick Allaert

Il codice precedente restituisce falseper openssl_decrypt(). Vedere stackoverflow.com/q/41952509/1066234 Poiché le crittografie a blocchi come AES richiedono che i dati di input siano un multiplo esatto della dimensione del blocco (16 byte per AES) è necessario il riempimento.
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;
    }

molto semplice ! Lo uso per la decrittografia della crittografia del segmento URL. Grazie
Mahbub Tito

0

Mi ci è voluto un po 'per capire come non ottenere un falsequando si utilizza openssl_decrypt()e come crittografare e decrittografare il lavoro.

    // 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 vuoi passare la stringa crittografata tramite un URL, devi codificare la stringa:

    $encrypted = urlencode($encrypted);

Per capire meglio cosa sta succedendo, leggi:

Per generare chiavi lunghe 16 byte puoi usare:

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

Per vedere i messaggi di errore di openssl puoi usare: echo openssl_error_string();

Spero che aiuti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.