Làm thế nào để mã hóa / giải mã dữ liệu trong php?


110

Tôi hiện là sinh viên và tôi đang học PHP, tôi đang cố gắng tạo một mã hóa / giải mã dữ liệu đơn giản bằng PHP. Tôi đã thực hiện một số nghiên cứu trực tuyến và một số trong số chúng khá khó hiểu (ít nhất là đối với tôi).

Đây là những gì tôi đang cố gắng làm:

Tôi có một bảng bao gồm các trường này (UserID, Fname, Lname, Email, Password)

Những gì tôi muốn có là có tất cả các trường được mã hóa và sau đó được giải mã (Có thể sử dụng sha256để mã hóa / giải mã không, nếu không phải là bất kỳ thuật toán mã hóa nào)

Một điều tôi muốn học là làm thế nào để tạo ra một cách hash(sha256)kết hợp với một "muối" tốt. (Về cơ bản, tôi chỉ muốn triển khai mã hóa / giải mã đơn giản, hash(sha256)+salt) thưa bà / thưa bà, câu trả lời của ông sẽ giúp ích rất nhiều và được đánh giá rất cao. Cảm ơn ông ++




9
SHA là một hàm băm, không phải mã hóa. Điểm mấu chốt là một hàm băm không thể được đảo ngược về dữ liệu ban đầu (dù sao cũng không dễ dàng). Bạn có thể muốn mcrypt hoặc nếu nó không có sẵn, tôi khuyên bạn nên dùng phpseclib - mặc dù điều quan trọng cần lưu ý là bất kỳ triển khai PHP thuần nào của bất kỳ thứ gì liên quan đến nhiều toán học cấp thấp sẽ là sloooooowww ... Đó là lý do tại sao tôi thích phpseclib, bởi vì nó sử dụng mcrypt đầu tiên nếu nó có sẵn và chỉ quay trở lại triển khai PHP như một phương sách cuối cùng.
DaveRandom

7
Bạn thường không muốn giải mã mật khẩu!
Ja͢ck

1
Về cơ bản bạn không nên nghĩ đến mã hóa ở cấp độ này, bạn nên nghĩ đến kiểm soát truy cập, bảo mật, toàn vẹn và xác thực. Sau đó, hãy kiểm tra cách bạn có thể đạt được điều này, có thể sử dụng mã hóa hoặc băm an toàn. Bạn có thể muốn đọc PBKDF2 và bcrypt / scrypt để hiểu về băm mật khẩu an toàn và những thứ tương tự.
Maarten Bodewes

Câu trả lời:


289

Lời tựa

Bắt đầu với định nghĩa bảng của bạn:

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

Dưới đây là những thay đổi:

  1. Các lĩnh vực Fname, LnameEmailsẽ được mã hóa bằng mật mã đối xứng, được cung cấp bởi OpenSSL ,
  2. Các IVtrường sẽ lưu trữ các vector khởi tạo sử dụng để mã hóa. Các yêu cầu lưu trữ phụ thuộc vào mật mã và chế độ được sử dụng; thêm về điều này sau.
  3. Các Passwordlĩnh vực sẽ được băm bằng cách sử dụng một chiều mật khẩu băm,

Mã hóa

Mật mã và chế độ

Việc chọn mật mã và chế độ mã hóa tốt nhất nằm ngoài phạm vi của câu trả lời này, nhưng lựa chọn cuối cùng ảnh hưởng đến kích thước của cả khóa mã hóa và vectơ khởi tạo; cho bài đăng này, chúng tôi sẽ sử dụng AES-256-CBC có kích thước khối cố định là 16 byte và kích thước khóa là 16, 24 hoặc 32 byte.

Khóa mã hóa

Khóa mã hóa tốt là một khối nhị phân được tạo ra từ một trình tạo số ngẫu nhiên đáng tin cậy. Ví dụ sau sẽ được đề xuất (> = 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

Điều này có thể được thực hiện một lần hoặc nhiều lần (nếu bạn muốn tạo một chuỗi khóa mã hóa). Giữ những điều này càng riêng tư càng tốt.

IV

Vectơ khởi tạo thêm tính ngẫu nhiên vào mã hóa và cần thiết cho chế độ CBC. Các giá trị này lý tưởng chỉ nên được sử dụng một lần (về mặt kỹ thuật là một lần cho mỗi khóa mã hóa), do đó, bản cập nhật cho bất kỳ phần nào của hàng sẽ tạo lại nó.

Một chức năng được cung cấp để giúp bạn tạo IV:

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

Thí dụ

Hãy mã hóa trường tên bằng cách sử dụng trường tên cũ hơn $encryption_key$iv; để làm điều này, chúng tôi phải đưa dữ liệu của mình vào kích thước khối:

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

Yêu cầu lưu trữ

Đầu ra được mã hóa, giống như IV, là nhị phân; Việc lưu trữ các giá trị này trong cơ sở dữ liệu có thể được thực hiện bằng cách sử dụng các kiểu cột được chỉ định như BINARYhoặc VARBINARY.

Giá trị đầu ra, giống như IV, là nhị phân; để lưu trữ các giá trị đó trong MySQL, hãy xem xét sử dụng BINARYhoặcVARBINARY các cột. Nếu đây không phải là một tùy chọn, bạn cũng có thể chuyển đổi dữ liệu nhị phân thành biểu diễn văn bản bằng cách sử dụng base64_encode()hoặc bin2hex(), làm như vậy yêu cầu thêm từ 33% đến 100% không gian lưu trữ.

Giải mã

Việc giải mã các giá trị được lưu trữ cũng tương tự:

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

Mã hóa xác thực

Bạn có thể cải thiện hơn nữa tính toàn vẹn của văn bản mật mã đã tạo bằng cách thêm chữ ký được tạo từ khóa bí mật (khác với khóa mã hóa) và văn bản mật mã. Trước khi văn bản mật mã được giải mã, chữ ký sẽ được xác minh đầu tiên (tốt nhất là bằng phương pháp so sánh thời gian không đổi).

Thí dụ

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

Xem thêm: hash_equals()

Băm

Lưu trữ mật khẩu có thể đảo ngược trong cơ sở dữ liệu của bạn phải được tránh càng nhiều càng tốt; bạn chỉ muốn xác minh mật khẩu hơn là biết nội dung của nó. Nếu người dùng mất mật khẩu, tốt hơn nên cho phép họ đặt lại mật khẩu thay vì gửi cho họ mật khẩu ban đầu (đảm bảo rằng việc đặt lại mật khẩu chỉ có thể được thực hiện trong một thời gian giới hạn).

Áp dụng một hàm băm là một hoạt động một chiều; sau đó nó có thể được sử dụng một cách an toàn để xác minh mà không tiết lộ dữ liệu gốc; đối với mật khẩu, phương pháp brute force là một cách tiếp cận khả thi để khám phá nó do độ dài tương đối ngắn và sự lựa chọn mật khẩu kém của nhiều người.

Các thuật toán băm như MD5 hoặc SHA1 được thực hiện để xác minh nội dung tệp dựa trên giá trị băm đã biết. Chúng được tối ưu hóa rất nhiều để giúp xác minh này nhanh nhất có thể mà vẫn chính xác. Với không gian đầu ra tương đối hạn chế, thật dễ dàng để xây dựng một cơ sở dữ liệu với các mật khẩu đã biết và đầu ra băm tương ứng của chúng, các bảng cầu vồng.

Thêm một muối vào mật khẩu trước khi băm nó sẽ làm cho một bảng cầu vồng trở nên vô dụng, nhưng những tiến bộ phần cứng gần đây đã khiến tra cứu brute force trở thành một cách tiếp cận khả thi. Đó là lý do tại sao bạn cần một thuật toán băm cố tình làm chậm và đơn giản là không thể tối ưu hóa. Nó cũng có thể tăng tải cho phần cứng nhanh hơn mà không ảnh hưởng đến khả năng xác minh các băm mật khẩu hiện có để làm bằng chứng trong tương lai.

Hiện tại, có hai sự lựa chọn phổ biến:

  1. PBKDF2 (Chức năng lấy lại khóa dựa trên mật khẩu v2)
  2. bcrypt (hay còn gọi là Blowfish)

Câu trả lời này sẽ sử dụng một ví dụ với bcrypt.

Thế hệ

Một hàm băm mật khẩu có thể được tạo như sau:

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

Muối được tạo ra openssl_random_pseudo_bytes()để tạo thành một khối dữ liệu ngẫu nhiên sau đó được chạy qua base64_encode()strtr()khớp với bảng chữ cái được yêu cầu của [A-Za-z0-9/.].

Các crypt()thực hiện chức năng các băm dựa trên các thuật toán ( $2y$cho Blowfish), yếu tố chi phí (một yếu tố của 13 mất khoảng 0.40s trên một máy 3GHz) và muối của 22 ký tự.

Thẩm định

Khi bạn đã tìm nạp hàng chứa thông tin người dùng, bạn xác thực mật khẩu theo cách sau:

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

Để xác minh mật khẩu, bạn gọi crypt()lại nhưng bạn chuyển hàm băm đã tính trước đó làm giá trị muối. Giá trị trả về mang lại cùng một hàm băm nếu mật khẩu đã cho khớp với hàm băm. Để xác minh hàm băm, bạn thường nên sử dụng hàm so sánh thời gian không đổi để tránh các cuộc tấn công về thời gian.

Băm mật khẩu với PHP 5.5

PHP 5.5 đã giới thiệu các hàm băm mật khẩu mà bạn có thể sử dụng để đơn giản hóa phương pháp băm ở trên:

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

Và xác minh:

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

Xem thêm: password_hash(),password_verify()


Tôi nên sử dụng độ dài nào để lưu trữ tên, họ, email, v.v. để đặt cược an toàn nhất? varbinary (???)
BentCoder

2
Chắc chắn, nhưng nó phụ thuộc vào cách nó được sử dụng. Nếu bạn xuất bản một thư viện mã hóa, bạn không biết các nhà phát triển sẽ triển khai nó như thế nào. Đó là lý do tại sao github.com/defuse/php-encryption cung cấp mã hóa khóa đối xứng đã xác thực và không cho phép các nhà phát triển làm suy yếu nó mà không cần chỉnh sửa mã của nó.
Scott Arciszewski

2
@Scott Rất tốt, tôi đã thêm một ví dụ về mã hóa được xác thực; cảm ơn vì sự thúc đẩy :)
Ja͢ck

1
+1 cho mã hóa được xác thực. Không có đủ thông tin trong câu hỏi để nói rằng AE không cần thiết ở đây. Chắc chắn lưu lượng truy cập SQL thường đi qua một mạng có các thuộc tính bảo mật không xác định, cũng như lưu lượng truy cập từ cơ sở dữ liệu đến bộ nhớ. Sao lưu và nhân rộng quá. Mô hình mối đe dọa là gì? Câu hỏi không nói, và có thể nguy hiểm nếu đưa ra các giả định.
Jason Orendorff

1
Thay vì mã hóa cứng $iv_size = 16;, tôi sẽ sử dụng: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))để chỉ ra mối liên hệ giữa kích thước của iv sẽ sử dụng với mật mã được sử dụng. Bạn cũng có thể mở rộng một chút nếu cần (hoặc không) pkcs7_pad()/ pkcs7_unpad(), hoặc đơn giản hóa bài đăng bằng cách loại bỏ chúng và sử dụng "aes-256-ctr". Bài đăng tuyệt vời @ Ja͢ck
Patrick Allaert 14/12/16

24

Tôi nghĩ điều này đã được trả lời trước đây ... nhưng dù sao, nếu bạn muốn mã hóa / giải mã dữ liệu, bạn không thể sử dụng 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
Bạn cũng không nên sử dụng ECB vì vấn đề đó.
Maarten Bodewes

7
Các khóa phải là các byte ngẫu nhiên hoặc bạn nên sử dụng một hàm dẫn xuất khóa an toàn.
Maarten Bodewes

4
MCRYPT_RIJNDAEL_256 không phải là một chức năng tiêu chuẩn hóa, bạn nên sử dụng AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes

14

Bối cảnh và giải thích câu trả lời

Để hiểu câu hỏi này, trước tiên bạn phải hiểu SHA256 là gì. SHA256 là một Hàm băm mật mã . Hàm băm mật mã là một hàm một chiều, có đầu ra được bảo mật bằng mật mã. Điều này có nghĩa là dễ dàng tính toán một hàm băm (tương đương với việc mã hóa dữ liệu), nhưng khó có được đầu vào ban đầu bằng cách sử dụng hàm băm (tương đương với việc giải mã dữ liệu). Vì sử dụng hàm băm mật mã có nghĩa là việc giải mã là không khả thi về mặt tính toán, vì vậy bạn không thể thực hiện giải mã với SHA256.

Những gì bạn muốn sử dụng là một hàm hai chiều, nhưng cụ thể hơn là Mật mã khối . Một chức năng cho phép cả mã hóa và giải mã dữ liệu. Các chức năng mcrypt_encryptmcrypt_decrypttheo mặc định sử dụng thuật toán Blowfish. Việc sử dụng mcrypt của PHP có thể được tìm thấy trong sách hướng dẫn này . Một danh sách các định nghĩa mật mã để chọn mật mã mà mcrypt sử dụng cũng tồn tại. Có thể tìm thấy wiki về Blowfish tại Wikipedia . Mật mã khối mã hóa đầu vào trong các khối có kích thước và vị trí đã biết bằng một khóa đã biết, để dữ liệu sau này có thể được giải mã bằng khóa. Đây là những gì SHA256 không thể cung cấp cho bạn.

$key = 'ThisIsTheCipherKey';

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

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

Bạn cũng không nên sử dụng ECB vì vấn đề đó.
Maarten Bodewes

Các khóa phải là các byte ngẫu nhiên hoặc bạn nên sử dụng một hàm dẫn xuất khóa an toàn.
Maarten Bodewes

4
Không bao giờ sử dụng chế độ ECB. Nó không an toàn và hầu hết thời gian không thực sự giúp ích trong việc thực sự mã hóa dữ liệu (thay vì chỉ mã hóa nó). Xem bài viết tuyệt vời trên Wikipedia về chủ đề này để biết thêm thông tin.
Holger Just

1
Tốt nhất là không nên sử dụng mcrypt, nó là phần mềm bỏ rơi, không được cập nhật trong nhiều năm và không hỗ trợ phần đệm PKCS # 7 (née PKCS # 5) tiêu chuẩn, chỉ có phần đệm rỗng không tiêu chuẩn thậm chí không thể được sử dụng với dữ liệu nhị phân . mcrypt có nhiều lỗi nổi bật từ năm 2003 .. Thay vào đó, hãy xem xét sử dụng defuse , nó đang được duy trì và chính xác.
zaph

9

Đây là một ví dụ sử dụng 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
Thay vì mcrypt_create_iv(), tôi sẽ sử dụng : openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod)), theo cách này, phương pháp này hoạt động cho bất kỳ giá trị nào của $ encodehod và sẽ chỉ sử dụng phần mở rộng openssl.
Patrick Allaert

Đoạn mã trên trả về falsecho openssl_decrypt(). Xem stackoverflow.com/q/41952509/1066234 Vì mật mã khối như AES yêu cầu dữ liệu đầu vào là bội số chính xác của kích thước khối (16-byte cho AES) là cần thiết.
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;
    }

rất đơn giản ! Tôi sử dụng nó để mã hóa-giải mã phân đoạn url. Cảm ơn
Mahbub Tito

0

Tôi đã mất khá nhiều thời gian để tìm ra, làm thế nào để không bị lỗi falsekhi sử dụng openssl_decrypt()và mã hóa và giải mã hoạt động.

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

Nếu bạn muốn chuyển chuỗi được mã hóa qua một URL, bạn cần phải mã hóa chuỗi:

    $encrypted = urlencode($encrypted);

Để hiểu rõ hơn những gì đang xảy ra, hãy đọc:

Để tạo các khóa dài 16 byte, bạn có thể sử dụng:

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

Để xem thông báo lỗi của openssl, bạn có thể sử dụng: echo openssl_error_string();

Hy vọng rằng sẽ giúp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.