Enkripsi dua arah yang paling sederhana menggunakan PHP


230

Apa cara paling sederhana untuk melakukan enkripsi dua arah dalam pemasangan PHP yang umum?

Saya harus dapat mengenkripsi data dengan kunci string, dan menggunakan kunci yang sama untuk mendekripsi di ujung lainnya.

Keamanannya tidak sebesar perhatian dari portabilitas kode, jadi saya ingin dapat menjaga hal-hal sesederhana mungkin. Saat ini, saya menggunakan implementasi RC4, tetapi jika saya dapat menemukan sesuatu yang didukung secara native, saya pikir saya dapat menyimpan banyak kode yang tidak perlu.



3
Untuk enkripsi tujuan umum, gunakan defuse / php-enkripsi / alih-alih menggulir sendiri.
Scott Arciszewski

2
Tangan jauh dari github.com/defuse/php-encryption - ini lebih lambat oleh urutan besarnya daripada mcrypt.
Eugen Rieck

1
@Scott Berpikir sepanjang garis "ini mungkin tidak akan menjadi hambatan" adalah apa yang membawa kita banyak perangkat lunak yang buruk.
Eugen Rieck

3
Jika Anda benar-benar mengenkripsi / mendekripsi banyak data hingga milidetik, biaya aplikasi Anda turun, gigit peluru dan beralih ke libsodium. Sodium::crypto_secretbox()dan Sodium::crypto_secretbox_open()aman dan berkinerja.
Scott Arciszewski

Jawaban:


196

Diedit:

Anda harus benar-benar menggunakan openssl_encrypt () & openssl_decrypt ()

Seperti yang dikatakan Scott , Mcrypt bukan ide yang baik karena belum diperbarui sejak 2007.

Bahkan ada RFC untuk menghapus Mcrypt dari PHP - https://wiki.php.net/rfc/mcrypt-viking-funeral


6
@EugenRieck Ya, itu intinya. Mcrypt tidak menerima tambalan. OpenSSL menerima tambalan segera setelah kerentanan ditemukan, besar atau kecil.
Greg

5
akan lebih baik untuk jawaban dengan suara tinggi seperti itu, untuk berada di sana memberikan contoh paling sederhana juga dalam jawaban. terima kasih.
T.Todua

teman-teman, hanya FYI => MCRYPT dilecehkan. capiting jadi semua orang harus tahu untuk tidak menggunakannya karena memberi kami banyak masalah. Itu sudah ditinggalkan sejak PHP 7.1 jika saya tidak salah.
clusterBuddy

Sejak PHP 7 fungsi mcrypt dihapus dari basis kode php. Jadi ketika menggunakan versi terbaru dari php (yang seharusnya menjadi standar) Anda tidak dapat menggunakan fungsi yang sudah usang ini lagi.
Alexander Behling

234

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 mcryptkarena 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 SaferCryptoperpustakaan 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 .


3
Jadi, saya hanya mencoba agar UnsafeCrypto berfungsi lebih dulu. Enkripsi terjadi dengan baik, tetapi setiap kali saya menjalankan dekripsi, saya mendapatkan jawaban 'salah'. Saya menggunakan kunci yang sama untuk mendekripsi, dan meneruskan true pada encode, serta decode. Ada, apa yang saya anggap salah ketik dalam contoh ini, saya bertanya-tanya apakah dari situlah masalah saya berasal. Bisakah Anda menjelaskan dari mana variabel $ mac berasal, dan haruskah itu hanya $ iv?
David C

1
@EugenRieck Implementasi cipher OpenSSL mungkin satu-satunya bagian yang tidak payah, dan itu satu-satunya cara untuk memanfaatkan AES-NI di vanilla PHP. Jika Anda menginstal di OpenBSD, PHP akan dikompilasi melawan LibreSSL tanpa kode PHP memperhatikan perbedaan. Libsodium> OpenSSL kapan saja. Juga, jangan gunakan libmcrypt . Apa yang akan Anda rekomendasikan oleh para pengembang PHP alih-alih OpenSSL?
Scott Arciszewski

2
Baik 5.2 maupun 5.3 tidak didukung lagi . Anda seharusnya melihat ke dalam pembaruan ke versi PHP yang didukung , seperti 5.6.
Scott Arciszewski


1
Saya hanya melakukannya sebagai demonstrasi Anda menginginkan string biner, bukan string yang bisa dibaca manusia, untuk kunci Anda .
Scott Arciszewski

22

Gunakan mcrypt_encrypt()dan mcrypt_decrypt()dengan parameter yang sesuai. Sangat mudah dan lurus ke depan, dan Anda menggunakan paket enkripsi yang teruji pertempuran.

EDIT

5 tahun dan 4 bulan setelah jawaban ini, mcryptekstensi sekarang dalam proses penghentian dan penghapusan akhirnya dari PHP.


34
Pertempuran diuji dan tidak diperbarui selama lebih dari 8 tahun?
Maarten Bodewes

2
Yah, mcrypt ada di PHP7 dan tidak usang - itu cukup bagus untuk saya. Tidak semua kode berkualitas mengerikan OpenSSL dan perlu ditambal setiap beberapa hari.
Eugen Rieck

3
mcrypt tidak hanya mengerikan dalam hal dukungan. Ini juga tidak menerapkan praktik terbaik seperti padding yang sesuai dengan PKCS # 7, enkripsi yang diautentikasi. Itu tidak akan mendukung SHA-3 atau algoritma baru lainnya karena tidak ada yang memeliharanya, merampas Anda dari jalur peningkatan. Selain itu digunakan untuk menerima hal-hal seperti kunci parsial, melakukan nol padding dll. Ada alasan bagus mengapa itu sedang dalam proses dihapus secara bertahap dari PHP.
Maarten Bodewes

2
Di PHP 7.1, semua fungsi mcrypt_ * akan memunculkan pemberitahuan E_DEPRECATED. Di PHP 7.1 + 1 (baik 7.2 atau 8.0), ekstensi mcrypt akan dipindahkan dari inti dan ke PECL, di mana orang yang benar - benar ingin menginstalnya mungkin masih melakukannya jika mereka dapat menginstal ekstensi PHP dari PECL.
Mladen Janjetovic

4

PHP 7.2 pindah sepenuhnya dari Mcryptdan enkripsi sekarang didasarkan pada Libsodiumperpustakaan yang dapat dikelola .

Semua kebutuhan enkripsi Anda pada dasarnya dapat diselesaikan melalui Libsodiumpustaka.

// On Alice's computer:
$msg = 'This comes from Alice.';
$signed_msg = sodium_crypto_sign($msg, $secret_sign_key);


// On Bob's computer:
$original_msg = sodium_crypto_sign_open($signed_msg, $alice_sign_publickey);
if ($original_msg === false) {
    throw new Exception('Invalid signature');
} else {
    echo $original_msg; // Displays "This comes from Alice."
}

Dokumentasi Libsodium: https://github.com/paragonie/pecl-libsodium-doc


2
jika Anda menempelkan beberapa kode, pastikan semua variabel tercakup. Dalam contoh Anda $ secret_sign_key dan $ alice_sign_publickey adalah NULL
undefinedman

1
The crypto_signAPI tidak tidak pesan mengenkripsi - yang akan membutuhkan salah satu crypto_aead_*_encryptfungsi.
Roger Dueck

1

PENTING jawaban ini hanya berlaku untuk PHP 5, di PHP 7 menggunakan fungsi kriptografi bawaan.

Berikut ini adalah implementasi yang sederhana namun cukup aman:

  • Enkripsi AES-256 dalam mode CBC
  • PBKDF2 untuk membuat kunci enkripsi dari kata sandi teks biasa
  • HMAC untuk mengotentikasi pesan terenkripsi.

Kode dan contoh ada di sini: https://stackoverflow.com/a/19445173/1387163


1
Saya bukan ahli kriptografi, tetapi memiliki kunci yang diturunkan langsung dari kata sandi sepertinya ide yang buruk. Tabel pelangi + kata sandi yang lemah dan hilang adalah keamanan Anda. Juga titik tautan Anda ke fungsi mcrypt, yang sudah tidak digunakan lagi sejak PHP 7.1
Alph.Dev

@ Alph.Dev Anda benar jawaban di atas hanya berlaku untuk PHP 5
Eugene Fidelin
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.