Secara pribadi, saya akan menggunakan mcrypt
seperti orang lain yang diposting. Tetapi ada banyak lagi yang perlu diperhatikan ...
Bagaimana cara mengenkripsi dan mendekripsi kata sandi dalam PHP?
Lihat di bawah ini untuk kelas yang kuat yang mengurus semuanya untuk Anda:
Algoritma apa yang paling aman untuk mengenkripsi kata sandi?
paling aman ? siapapun dari mereka. Metode teraman jika Anda akan mengenkripsi adalah untuk melindungi terhadap kerentanan pengungkapan informasi (XSS, inklusi jarak jauh, dll). Jika keluar, penyerang akhirnya dapat memecahkan enkripsi (tidak ada enkripsi yang 100% tidak dapat dibalik tanpa kunci - seperti yang ditunjukkan oleh @NullUserException, hal ini tidak sepenuhnya benar. Ada beberapa skema enkripsi yang tidak mungkin diretas seperti OneTimePad ) .
Di mana saya menyimpan kunci pribadi?
Apa yang akan saya lakukan adalah menggunakan 3 kunci. Satu adalah pengguna yang disediakan, satu adalah aplikasi spesifik dan yang lainnya adalah pengguna spesifik (seperti garam). Kunci spesifik aplikasi dapat disimpan di mana saja (dalam file konfigurasi di luar web-root, dalam variabel lingkungan, dll). Yang khusus pengguna akan disimpan dalam kolom di db di sebelah kata sandi terenkripsi. Pengguna yang disediakan tidak akan disimpan. Kemudian, Anda akan melakukan sesuatu seperti ini:
$key = $userKey . $serverKey . $userSuppliedKey;
Manfaat di sana, adalah bahwa 2 kunci dapat dikompromikan tanpa data dikompromikan. Jika ada serangan SQL Injection, mereka bisa mendapatkan $userKey
, tetapi bukan yang lain 2. Jika ada exploit server lokal, mereka bisa mendapatkan $userKey
dan $serverKey
, tetapi bukan yang ketiga $userSuppliedKey
. Jika mereka mengalahkan pengguna dengan kunci pas, mereka bisa mendapatkan $userSuppliedKey
, tetapi tidak 2 yang lain (tapi sekali lagi, jika pengguna dipukul dengan kunci pas, Anda toh sudah terlambat).
Alih-alih menyimpan kunci privat, apakah lebih baik meminta pengguna memasukkan kunci privat kapan saja mereka membutuhkan kata sandi yang didekripsi? (Pengguna aplikasi ini dapat dipercaya)
Benar. Sebenarnya, itulah satu-satunya cara saya melakukannya. Kalau tidak, Anda perlu menyimpan versi yang tidak dienkripsi dalam format penyimpanan yang tahan lama (memori bersama seperti APC atau memcached, atau dalam file sesi). Itu membuat Anda terkena kompromi tambahan. Jangan pernah menyimpan versi kata sandi yang tidak terenkripsi dalam apa pun kecuali variabel lokal.
Dengan cara apa kata sandi dapat dicuri dan didekripsi? Apa yang perlu saya waspadai?
Segala bentuk kompromi dari sistem Anda akan membuat mereka melihat data terenkripsi. Jika mereka dapat menyuntikkan kode atau membuka sistem file Anda, mereka dapat melihat data yang didekripsi (karena mereka dapat mengedit file yang mendekripsi data). Segala bentuk Replay atau serangan MITM juga akan memberi mereka akses penuh ke kunci yang terlibat. Mengendus lalu lintas HTTP mentah juga akan memberi mereka kunci.
Gunakan SSL untuk semua lalu lintas. Dan pastikan tidak ada di server yang memiliki kerentanan (CSRF, XSS, SQL Injection, Privilege Escalation, Eksekusi Kode Jarak Jauh, dll).
Sunting: Ini adalah implementasi kelas PHP dari metode enkripsi yang kuat:
/**
* 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);
}
}
Perhatikan bahwa saya menggunakan fungsi ditambahkan dalam PHP 5.6: hash_equals
. Jika Anda berada di bawah 5.6, Anda dapat menggunakan fungsi pengganti ini yang mengimplementasikan fungsi perbandingan aman-waktu menggunakan verifikasi HMAC ganda :
function hash_equals($a, $b) {
$key = mcrypt_create_iv(128, MCRYPT_DEV_URANDOM);
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
}
Pemakaian:
$e = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$encryptedData = $e->encrypt($data, $key);
Lalu, untuk mendekripsi:
$e2 = new Encryption(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC);
$data = $e2->decrypt($encryptedData, $key);
Perhatikan bahwa saya menggunakan $e2
kedua kalinya untuk menunjukkan kepada Anda berbagai contoh masih akan mendekripsi data dengan benar.
Sekarang, bagaimana cara kerjanya / mengapa menggunakannya di atas solusi lain:
Kunci
Kunci tidak digunakan secara langsung. Sebagai gantinya, kunci diregangkan oleh derivasi PBKDF2 standar.
Kunci yang digunakan untuk enkripsi adalah unik untuk setiap blok teks terenkripsi. Karenanya, kunci yang disediakan menjadi "kunci utama". Karenanya kelas ini menyediakan rotasi kunci untuk kunci sandi dan kunci autentik.
CATATAN PENTING , $rounds
parameter ini dikonfigurasikan untuk kunci acak sejati dengan kekuatan yang cukup (minimum 128 bit Cryptographically Secure random). Jika Anda akan menggunakan kata sandi, atau kunci non-acak (atau kurang acak dari 128 bit CS acak), Anda harus meningkatkan parameter ini. Saya menyarankan minimal 10.000 untuk kata sandi (semakin banyak yang Anda mampu, semakin baik, tetapi akan menambah runtime) ...
Integritas data
- Versi yang diperbarui menggunakan ENCRYPT-THEN-MAC, yang merupakan metode yang jauh lebih baik untuk memastikan keaslian data terenkripsi.
Enkripsi:
- Ia menggunakan mcrypt untuk benar-benar melakukan enkripsi. Saya sarankan menggunakan salah satu
MCRYPT_BLOWFISH
atau MCRYPT_RIJNDAEL_128
sandi dan MCRYPT_MODE_CBC
untuk mode. Itu cukup kuat, dan masih cukup cepat (siklus enkripsi dan dekripsi memakan waktu sekitar 1/2 detik pada mesin saya).
Sekarang, ke poin 3 dari daftar pertama, apa yang akan memberi Anda adalah fungsi seperti ini:
function makeKey($userKey, $serverKey, $userSuppliedKey) {
$key = hash_hmac('sha512', $userKey, $serverKey);
$key = hash_hmac('sha512', $key, $userSuppliedKey);
return $key;
}
Anda dapat merentangkannya dalam makeKey()
fungsi, tetapi karena itu akan diregangkan nanti, tidak ada poin besar untuk melakukannya.
Sejauh ukuran penyimpanan, itu tergantung pada teks biasa. Blowfish menggunakan ukuran blok 8 byte, sehingga Anda akan memiliki:
- 16 byte untuk garam
- 64 byte untuk hmac
- panjang data
- Padding sehingga panjang data% 8 == 0
Jadi untuk sumber data 16 karakter, akan ada 16 karakter data yang akan dienkripsi. Jadi itu berarti ukuran data terenkripsi yang sebenarnya adalah 16 byte karena padding. Kemudian tambahkan 16 byte untuk garam dan 64 byte untuk hmac dan ukuran total yang disimpan adalah 96 byte. Jadi paling tidak ada 80 karakter overhead, dan paling buruk 87 karakter overhead ...
Saya harap itu membantu...
Catatan: 12/11/12: Saya baru saja memperbarui kelas ini dengan metode enkripsi JAUH lebih baik, menggunakan kunci turunan yang lebih baik, dan memperbaiki generasi MAC ...