Jak zaszyfrować / odszyfrować dane w PHP?

110

Obecnie jestem studentem i studiuję PHP, próbuję wykonać proste szyfrowanie / deszyfrowanie danych w PHP. Zrobiłem kilka poszukiwań online i niektóre z nich były dość mylące (przynajmniej dla mnie).

Oto, co próbuję zrobić:

Mam tabelę składającą się z tych pól (ID użytkownika, Fname, Lname, Email, Password)

Chcę mieć wszystkie pola zaszyfrowane, a następnie odszyfrowane (czy można użyć sha256do szyfrowania / deszyfrowania, jeśli nie algorytmu szyfrowania)

Kolejną rzeczą, której chcę się nauczyć, jest to, jak tworzyć jednokierunkowe hash(sha256)połączenie z dobrą „solą”. (Zasadniczo chcę mieć prostą implementację szyfrowania / deszyfrowania, proszę hash(sha256)+salt) pana, pańskie odpowiedzi byłyby bardzo pomocne i bardzo docenione. Dziękuję ++

Randel Ramirez
źródło
9
SHA to skrót, a nie szyfrowanie. Kluczową kwestią jest to, że hasha nie można przywrócić do oryginalnych danych (w każdym razie nie jest to łatwe). Prawdopodobnie chcesz mcrypt lub jeśli nie jest dostępny, poleciłbym phpseclib - chociaż ważne jest, aby pamiętać, że każda implementacja w czystym PHP czegokolwiek, co wiąże się z dużą ilością matematyki niskiego poziomu, będzie sloooooowww ... Dlatego lubię phpseclib, ponieważ najpierw używa mcrypt, jeśli jest dostępny i wraca do implementacji PHP tylko w ostateczności.
DaveRandom
7
Zwykle nie chcesz mieć możliwości odszyfrowania hasła!
Ja͢ck
1
Zasadniczo nie powinieneś myśleć o szyfrowaniu na tym poziomie, powinieneś pomyśleć o kontroli dostępu, poufności, integralności i uwierzytelnianiu. Następnie sprawdź, jak możesz to osiągnąć, prawdopodobnie używając szyfrowania lub bezpiecznego haszowania. Możesz przeczytać PBKDF2 i bcrypt / scrypt, aby zrozumieć bezpieczne mieszanie haseł i tym podobne.
Maarten Bodewes

Odpowiedzi:

289

Przedmowa

Zaczynając od definicji tabeli:

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

Oto zmiany:

  1. Pola Fname, Lnamei Emailzostaną zaszyfrowane przy użyciu symetrycznego szyfru, dostarczone przez OpenSSL ,
  2. IVPole będzie przechowywać wektor inicjujący używany do szyfrowania. Wymagania dotyczące przechowywania zależą od używanego szyfrowania i trybu; więcej o tym później.
  3. PasswordPole zostanie zakodowane przy użyciu jednokierunkowej hash hasła,

Szyfrowanie

Szyfr i tryb

Wybór najlepszego szyfru i trybu szyfrowania wykracza poza zakres tej odpowiedzi, ale ostateczny wybór wpływa na rozmiar zarówno klucza szyfrowania, jak i wektora inicjalizacji; w tym poście będziemy używać AES-256-CBC, który ma stały rozmiar bloku 16 bajtów i rozmiar klucza 16, 24 lub 32 bajty.

Klucz szyfrowania

Dobry klucz szyfrowania to binarny obiekt blob wygenerowany z niezawodnego generatora liczb losowych. Zalecany byłby następujący przykład (> = 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

Można to zrobić raz lub wiele razy (jeśli chcesz utworzyć łańcuch kluczy szyfrujących). Zachowaj je jak najbardziej poufne.

IV

Wektor inicjalizacyjny dodaje losowość do szyfrowania i jest wymagany w trybie CBC. Idealnie byłoby, gdyby te wartości były używane tylko raz (technicznie raz na klucz szyfrowania), więc aktualizacja dowolnej części wiersza powinna ją ponownie wygenerować.

Dostępna jest funkcja pomagająca w generowaniu IV:

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

Przykład

Zaszyfrujmy pole nazwy, używając wcześniejszego $encryption_keyi $iv; aby to zrobić, musimy dopełnić nasze dane do rozmiaru bloku:

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

Wymagania dotyczące przechowywania

Zaszyfrowane dane wyjściowe, podobnie jak IV, są binarne; przechowywanie tych wartości w bazie danych można osiągnąć za pomocą wyznaczonych typów kolumn, takich jak BINARYlub VARBINARY.

Wartość wyjściowa, podobnie jak IV, jest binarna; aby przechowywać te wartości w MySQL, rozważ użycie BINARYlubVARBINARY kolumn. Jeśli nie ma takiej opcji, możesz również przekonwertować dane binarne na reprezentację tekstową za pomocą base64_encode()lub bin2hex(), wymaga to od 33% do 100% więcej miejsca na dysku.

Deszyfrowanie

Odszyfrowanie przechowywanych wartości jest podobne:

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

Uwierzytelnione szyfrowanie

Możesz dodatkowo poprawić integralność wygenerowanego tekstu zaszyfrowanego, dołączając podpis, który jest generowany z tajnego klucza (innego niż klucz szyfrowania) i zaszyfrowanego tekstu. Przed odszyfrowaniem tekstu zaszyfrowanego podpis jest najpierw weryfikowany (najlepiej metodą porównywania w czasie stałym).

Przykład

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

Zobacz też: hash_equals()

Haszowanie

W miarę możliwości należy unikać przechowywania odwracalnego hasła w bazie danych; chcesz tylko zweryfikować hasło, a nie znać jego zawartość. Jeśli użytkownik zgubi swoje hasło, lepiej pozwolić mu je zresetować, zamiast wysyłać mu swoje oryginalne (upewnij się, że resetowanie hasła można wykonać tylko przez ograniczony czas).

Stosowanie funkcji skrótu jest operacją jednokierunkową; następnie można go bezpiecznie używać do weryfikacji bez ujawniania oryginalnych danych; w przypadku haseł metoda brutalnej siły jest wykonalnym podejściem do ich ujawnienia ze względu na jej stosunkowo krótką długość i zły dobór haseł przez wiele osób.

Algorytmy haszujące, takie jak MD5 lub SHA1, zostały stworzone w celu sprawdzenia zawartości plików względem znanej wartości skrótu. Są one znacznie zoptymalizowane, aby weryfikacja była jak najszybsza, a jednocześnie była dokładna. Biorąc pod uwagę ich stosunkowo ograniczoną przestrzeń wyjściową, łatwo było zbudować bazę danych ze znanymi hasłami i odpowiadającymi im wyjściami mieszania, tęczowymi tablicami.

Dodanie soli do hasła przed haszowaniem sprawiłoby, że tęczowa tablica byłaby bezużyteczna, ale ostatnie postępy w sprzęcie sprawiły, że wyszukiwanie brutalne stało się opłacalnym podejściem. Dlatego potrzebujesz algorytmu haszującego, który jest celowo wolny i po prostu niemożliwy do optymalizacji. Powinien również być w stanie zwiększyć obciążenie szybszego sprzętu bez wpływu na możliwość weryfikacji istniejących skrótów haseł, aby zapewnić przyszłą ochronę.

Obecnie dostępne są dwie popularne opcje:

  1. PBKDF2 (funkcja wyprowadzania klucza w oparciu o hasło v2)
  2. bcrypt (aka Blowfish)

W tej odpowiedzi wykorzystany zostanie przykład z bcrypt.

Pokolenie

Skrót hasła można wygenerować w następujący sposób:

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

Sól jest generowana w openssl_random_pseudo_bytes()celu utworzenia losowej porcji danych, która jest następnie przepuszczana base64_encode()i strtr()dopasowywana do wymaganego alfabetu [A-Za-z0-9/.].

W crypt()pełni funkcję wycieniowanie podstawie algorytmu ( $2y$na Blowfish), czynnik kosztowy (współczynnik 13 zajmuje z grubsza 0.40s na maszynie do 3 GHz) i sól 22 znaków.

Uprawomocnienie

Po pobraniu wiersza zawierającego informacje o użytkowniku weryfikujesz hasło w następujący sposób:

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

Aby zweryfikować hasło, dzwonisz crypt()ponownie, ale przekazujesz poprzednio obliczony skrót jako wartość soli. Wartość zwracana daje ten sam skrót, jeśli podane hasło jest zgodne z hashem. Aby zweryfikować hash, często zaleca się użycie funkcji porównania w stałym czasie, aby uniknąć ataków czasowych.

Haszowanie haseł w PHP 5.5

PHP 5.5 wprowadziło funkcje haszowania haseł , których możesz użyć do uproszczenia powyższej metody haszowania:

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

I weryfikacja:

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

Zobacz również: password_hash(),password_verify()

Jacek
źródło
Jakiej długości należy użyć do przechowywania imienia, nazwiska, adresu e-mail itp., Aby uzyskać najbezpieczniejszy zakład? varbinary (???)
BentCoder
2
Jasne, ale to zależy od tego, jak jest używane. Jeśli publikujesz bibliotekę szyfrowania, nie wiesz, w jaki sposób programiści ją wdrożą. Właśnie dlatego github.com/defuse/php-encryption zapewnia uwierzytelnione szyfrowanie z kluczem symetrycznym i nie pozwala programistom osłabić go bez edycji kodu.
Scott Arciszewski
2
@Scott Bardzo dobrze, dodałem przykład uwierzytelnionego szyfrowania; dzięki za pchnięcie :)
Ja͢ck
1
+1 za uwierzytelnione szyfrowanie. W pytaniu nie ma wystarczających informacji, aby stwierdzić, że AE nie jest tutaj konieczne. Z pewnością ruch SQL często przechodzi przez sieć o nieznanych właściwościach bezpieczeństwa, podobnie jak ruch z bazy danych do magazynu. Kopie zapasowe i replikacja również. Jaki jest model zagrożenia? Pytanie nie mówi, a przypuszczenia mogą być niebezpieczne.
Jason Orendorff
1
Zamiast kodowania na stałe użyłbym $iv_size = 16;: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))w celu wskazania związku między rozmiarem iv do użycia z użytym szyfrem. Możesz również rozszerzyć nieco potrzebę (lub nie) pkcs7_pad()/ pkcs7_unpad()lub po prostu uprościć post, pozbywając się ich i używając "aes-256-ctr". Świetny post @ Ja͢ck
Patrick Allaert
24

Myślę, że odpowiedziano na to już wcześniej ... ale tak czy inaczej, jeśli chcesz zaszyfrować / odszyfrować dane, nie możesz użyć 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
źródło
7
Nie powinieneś też korzystać z EBC.
Maarten Bodewes
7
Klucze powinny być losowymi bajtami lub należy użyć funkcji bezpiecznego wyprowadzania klucza.
Maarten Bodewes
4
MCRYPT_RIJNDAEL_256 nie jest standardową funkcją, należy użyć AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes
14

Tło odpowiedzi i wyjaśnienie

Aby zrozumieć to pytanie, musisz najpierw zrozumieć, czym jest SHA256. SHA256 to kryptograficzna funkcja skrótu . Cryptographic Hash Function to funkcja jednokierunkowa, której dane wyjściowe są kryptograficznie bezpieczne. Oznacza to, że obliczenie skrótu (odpowiednik szyfrowania danych) jest łatwe, ale trudno jest uzyskać oryginalne dane wejściowe za pomocą skrótu (odpowiednik odszyfrowania danych). Ponieważ użycie kryptograficznej funkcji skrótu oznacza, że ​​odszyfrowanie jest niewykonalne obliczeniowo, dlatego nie można wykonać deszyfrowania za pomocą SHA256.

To, czego chcesz użyć, to funkcja dwukierunkowa, a dokładniej szyfr blokowy . Funkcja, która umożliwia zarówno szyfrowanie, jak i deszyfrowanie danych. Funkcje mcrypt_encrypti mcrypt_decryptdomyślnie używają algorytmu Blowfish. Sposób użycia mcrypt przez PHP można znaleźć w tym podręczniku . Istnieje również lista definicji szyfrów służących do wyboru szyfru używanego przez mcrypt. Wiki dotyczące Blowfish można znaleźć w Wikipedii . Szyfr blokowy szyfruje dane wejściowe w blokach o znanym rozmiarze i położeniu za pomocą znanego klucza, dzięki czemu dane można później odszyfrować za pomocą klucza. Tego właśnie SHA256 nie może Ci zapewnić.

Kod

$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
źródło
Nie powinieneś też korzystać z EBC.
Maarten Bodewes
Klucze powinny być losowymi bajtami lub należy użyć funkcji bezpiecznego wyprowadzania klucza.
Maarten Bodewes
4
Nigdy, przenigdy nie używaj trybu EBC. Jest niebezpieczny i przez większość czasu nie pomaga w szyfrowaniu danych (a nie tylko w kodowaniu). Więcej informacji można znaleźć w doskonałym artykule w Wikipedii na ten temat .
Holger Just
1
Najlepiej nie używać mcrypt, jest to porzucić oprogramowanie, nie było aktualizowane od lat i nie obsługuje standardowego wypełniania PKCS # 7 (z domu PKCS # 5), tylko niestandardowe wypełnienie zerowe, którego nie można nawet używać z danymi binarnymi . mcrypt miał wiele wybitnych błędów z 2003 roku. Zamiast tego rozważ użycie defuse , jest ono utrzymywane i jest poprawne.
zaph
9

Oto przykład wykorzystujący 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
źródło
2
Zamiast mcrypt_create_iv(), użyłbym: openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod)), w ten sposób metodologia działa dla dowolnej wartości $ encryptionMethod i będzie używać tylko openssl rozszerzenie.
Patrick Allaert,
Powyższy kod powraca falsedo openssl_decrypt(). Zobacz stackoverflow.com/q/41952509/1066234 Ponieważ szyfry blokowe, takie jak AES, wymagają, aby dane wejściowe były dokładną wielokrotnością rozmiaru bloku (16 bajtów dla AES), konieczne jest wypełnienie.
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
źródło
bardzo prosty ! Używam go do szyfrowania-deszyfrowania segmentów adresów URL. Dzięki
Mahbub Tito
0

Zajęło mi trochę czasu, aby dowiedzieć się, jak nie uzyskać falsepodczas używania openssl_decrypt()i uzyskać szyfrowanie i odszyfrowywanie.

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

Jeśli chcesz przekazać zaszyfrowany ciąg za pośrednictwem adresu URL, musisz zakodować ciąg jako urlen:

    $encrypted = urlencode($encrypted);

Aby lepiej zrozumieć, co się dzieje, przeczytaj:

Aby wygenerować 16-bajtowe klucze, możesz użyć:

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

Aby zobaczyć komunikaty o błędach openssl, możesz użyć: echo openssl_error_string();

Mam nadzieję, że to pomoże.

Kai Noack
źródło