Penting : Kecuali Anda memiliki case-use yang sangat khusus, jangan mengenkripsi kata sandi , gunakan algoritma hashing kata sandi. Ketika seseorang mengatakan mereka mengenkripsi kata sandi mereka di aplikasi sisi server, mereka entah tidak memberi tahu atau mereka menggambarkan desain sistem yang berbahaya. Menyimpan kata sandi dengan aman adalah masalah yang sama sekali terpisah dari enkripsi.
Telah diinformasikan. Desain sistem yang aman.
Enkripsi Data Portabel dalam PHP
Jika Anda menggunakan PHP 5.4 atau yang lebih baru dan tidak ingin menulis sendiri modul kriptografi, saya sarankan menggunakan perpustakaan yang ada yang menyediakan enkripsi terautentikasi . Perpustakaan yang saya tautkan hanya bergantung pada apa yang disediakan oleh PHP dan sedang ditinjau secara berkala oleh beberapa peneliti keamanan. (Termasuk saya.)
Jika tujuan portabilitas Anda tidak mencegah membutuhkan PECL ekstensi, libsodium adalah sangat dianjurkan atas apa-apa yang Anda atau saya bisa menulis di PHP.
Pembaruan (2016-06-12): Sekarang Anda dapat menggunakan sodium_compat dan menggunakan tawaran crypto libsodium yang sama tanpa menginstal ekstensi PECL.
Jika Anda ingin mencoba teknik kriptografi, baca terus.
Pertama, Anda harus meluangkan waktu untuk mempelajari bahaya enkripsi yang tidak diautentikasi dan Prinsip Kiamat Kriptografis .
- Data terenkripsi masih dapat dirusak oleh pengguna jahat.
- Otentikasi data terenkripsi mencegah gangguan.
- Otentikasi data yang tidak dienkripsi tidak mencegah gangguan.
Enkripsi dan Dekripsi
Enkripsi dalam PHP sebenarnya sederhana (kami akan menggunakan openssl_encrypt()
dan openssl_decrypt()
setelah Anda membuat beberapa keputusan tentang cara mengenkripsi informasi Anda. Konsultasikan openssl_get_cipher_methods()
untuk daftar metode yang didukung pada sistem Anda. Pilihan terbaik adalah AES dalam mode CTR :
aes-128-ctr
aes-192-ctr
aes-256-ctr
Saat ini tidak ada alasan untuk percaya bahwa ukuran kunci AES adalah masalah signifikan yang perlu dikhawatirkan (lebih besar mungkin tidak lebih baik, karena penjadwalan kunci yang buruk dalam mode 256-bit).
Catatan: Kami tidak menggunakan mcrypt
karena itu ditinggalkan dan memiliki bug yang belum ditonton yang mungkin mempengaruhi keamanan. Karena alasan ini, saya mendorong pengembang PHP lain untuk menghindarinya juga.
Enkripsi Sederhana / Pembungkus Dekripsi menggunakan OpenSSL
class UnsafeCrypto
{
const METHOD = 'aes-256-ctr';
/**
* Encrypts (but does not authenticate) a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = openssl_random_pseudo_bytes($nonceSize);
$ciphertext = openssl_encrypt(
$message,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
// Now let's pack the IV and the ciphertext together
// Naively, we can just concatenate
if ($encode) {
return base64_encode($nonce.$ciphertext);
}
return $nonce.$ciphertext;
}
/**
* Decrypts (but does not verify) a message
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string
*/
public static function decrypt($message, $key, $encoded = false)
{
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
$nonceSize = openssl_cipher_iv_length(self::METHOD);
$nonce = mb_substr($message, 0, $nonceSize, '8bit');
$ciphertext = mb_substr($message, $nonceSize, null, '8bit');
$plaintext = openssl_decrypt(
$ciphertext,
self::METHOD,
$key,
OPENSSL_RAW_DATA,
$nonce
);
return $plaintext;
}
}
Contoh Penggunaan
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Demo : https://3v4l.org/jl7qR
Pustaka crypto sederhana di atas masih tidak aman untuk digunakan. Kita perlu mengautentikasi ciphertext dan memverifikasi sebelum kita mendekripsi .
Catatan : Secara default, UnsafeCrypto::encrypt()
akan mengembalikan string biner mentah. Sebut seperti ini jika Anda perlu menyimpannya dalam format binary-safe (base64-encoded):
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = UnsafeCrypto::encrypt($message, $key, true);
$decrypted = UnsafeCrypto::decrypt($encrypted, $key, true);
var_dump($encrypted, $decrypted);
Demo : http://3v4l.org/f5K93
Pembungkus Autentikasi Sederhana
class SaferCrypto extends UnsafeCrypto
{
const HASH_ALGO = 'sha256';
/**
* Encrypts then MACs a message
*
* @param string $message - plaintext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encode - set to TRUE to return a base64-encoded string
* @return string (raw binary)
*/
public static function encrypt($message, $key, $encode = false)
{
list($encKey, $authKey) = self::splitKeys($key);
// Pass to UnsafeCrypto::encrypt
$ciphertext = parent::encrypt($message, $encKey);
// Calculate a MAC of the IV and ciphertext
$mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true);
if ($encode) {
return base64_encode($mac.$ciphertext);
}
// Prepend MAC to the ciphertext and return to caller
return $mac.$ciphertext;
}
/**
* Decrypts a message (after verifying integrity)
*
* @param string $message - ciphertext message
* @param string $key - encryption key (raw binary expected)
* @param boolean $encoded - are we expecting an encoded string?
* @return string (raw binary)
*/
public static function decrypt($message, $key, $encoded = false)
{
list($encKey, $authKey) = self::splitKeys($key);
if ($encoded) {
$message = base64_decode($message, true);
if ($message === false) {
throw new Exception('Encryption failure');
}
}
// Hash Size -- in case HASH_ALGO is changed
$hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit');
$mac = mb_substr($message, 0, $hs, '8bit');
$ciphertext = mb_substr($message, $hs, null, '8bit');
$calculated = hash_hmac(
self::HASH_ALGO,
$ciphertext,
$authKey,
true
);
if (!self::hashEquals($mac, $calculated)) {
throw new Exception('Encryption failure');
}
// Pass to UnsafeCrypto::decrypt
$plaintext = parent::decrypt($ciphertext, $encKey);
return $plaintext;
}
/**
* Splits a key into two separate keys; one for encryption
* and the other for authenticaiton
*
* @param string $masterKey (raw binary)
* @return array (two raw binary strings)
*/
protected static function splitKeys($masterKey)
{
// You really want to implement HKDF here instead!
return [
hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true),
hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true)
];
}
/**
* Compare two strings without leaking timing information
*
* @param string $a
* @param string $b
* @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW
* @return boolean
*/
protected static function hashEquals($a, $b)
{
if (function_exists('hash_equals')) {
return hash_equals($a, $b);
}
$nonce = openssl_random_pseudo_bytes(32);
return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce);
}
}
Contoh Penggunaan
$message = 'Ready your ammunition; we attack at dawn.';
$key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
$encrypted = SaferCrypto::encrypt($message, $key);
$decrypted = SaferCrypto::decrypt($encrypted, $key);
var_dump($encrypted, $decrypted);
Demo : biner mentah , base64-encoded
Jika ada yang ingin menggunakan SaferCrypto
perpustakaan ini di lingkungan produksi, atau implementasi Anda sendiri dari konsep yang sama, saya sangat menyarankan untuk menghubungi kriptografer penduduk Anda untuk pendapat kedua sebelum Anda melakukannya. Mereka akan dapat memberi tahu Anda tentang kesalahan yang bahkan mungkin tidak saya sadari.
Anda akan jauh lebih baik menggunakan perpustakaan kriptografi terkemuka .