PHP kullanarak en basit iki yönlü şifreleme


230

Ortak PHP kurulumlarında iki yönlü şifreleme yapmanın en basit yolu nedir?

Bir dize anahtar ile veri şifrelemek ve diğer ucunda şifresini çözmek için aynı anahtarı kullanmak gerekir.

Güvenlik, kodun taşınabilirliği kadar büyük bir endişe kaynağı değildir, bu yüzden işleri olabildiğince basit tutabilmek istiyorum. Şu anda, bir RC4 uygulaması kullanıyorum, ancak yerel olarak desteklenen bir şey bulabilirsem çok fazla gereksiz kod kaydedebilirim.



3
Genel amaçlı şifreleme için, kendi şifrenizi almak yerine defuse / php-şifreleme / kullanın.
Scott Arciszewski

2
Eller github.com/defuse/php-encryption'dan uzakta - büyüklük sıralarıyla mcrypt'ten daha yavaş.
Eugen Rieck

1
@Scott "Bu muhtemelen darboğaz olmayacak" çizgileri boyunca düşünmek bize çok kötü yazılım getirdi.
Eugen Rieck

3
Çok fazla veriyi, milisaniyenin uygulamanızı düşürdüğü noktaya kadar gerçekten şifreliyor / şifresini çözüyorsanız, mermiyi ısırın ve libsodium'a geçin. Sodium::crypto_secretbox()ve Sodium::crypto_secretbox_open()güvenli ve performanslıdır.
Scott Arciszewski

Yanıtlar:


196

Düzenlendi:

Gerçekten openssl_encrypt () ve openssl_decrypt () kullanıyor olmalısınız

As Scott diyor, Mcrypt 2007 yılından bu yana güncellenmiş olmamıştır olarak iyi bir fikir değildir.

Mcrypt'i PHP'den kaldırmak için bir RFC bile var - https://wiki.php.net/rfc/mcrypt-viking-funeral


6
@EugenRieck Evet, mesele bu. Mcrypt yama almaz. OpenSSL, büyük veya küçük herhangi bir güvenlik açığı keşfedildiğinde yamaları alır.
Greg

5
bu kadar yüksek oy alan bir cevap için, cevapta da en basit örnekleri sunmak daha iyi olurdu. yine de teşekkürler.
T.Todua

millet, sadece FYI => MCRYPT DEPRECATED. Herkesin bize sayısız sorun verdiği için onu kullanmamasını bilmeleri gerekir. Yanılmıyorsam PHP 7.1'den beri kullanımdan kaldırıldı.
clusterBuddy

PHP 7'den beri mcrypt işlevi php kod tabanından kaldırılır. Bu yüzden php (standart olması gerekir) en son sürümünü kullanırken artık bu kullanımdan kaldırılmış işlevi kullanamazsınız.
Alexander Behling

234

Önemli : Çok özel bir kullanım durumunuz yoksa , şifreleri şifrelemeyin , bunun yerine bir şifre karma algoritması kullanın. Birisi şifrelerini sunucu tarafındaki bir uygulamada şifrelediğini söylediğinde , ya bilgisizdir ya da tehlikeli bir sistem tasarımını tanımlar. Şifreleri güvenli bir şekilde saklamak , şifrelemeden tamamen ayrı bir sorundur.

Haberdar olmak. Güvenli sistemler tasarlayın.

PHP'de Taşınabilir Veri Şifreleme

PHP 5.4 veya daha yenisini kullanıyorsanız ve kendiniz bir şifreleme modülü yazmak istemiyorsanız , kimliği doğrulanmış şifreleme sağlayan mevcut bir kitaplığı kullanmanızı öneririm . Bağladığım kütüphane sadece PHP'nin sağladığı bilgilere dayanıyor ve bir avuç güvenlik araştırmacısı tarafından periyodik olarak inceleniyor. (Ben de dahil.)

Taşınabilirlik hedefleriniz PECL uzantıları gerektirmezse, libsodium sizin veya PHP'de yazabileceğim herhangi bir şey için şiddetle tavsiye edilir.

Güncelleme (2016-06-12): Artık sodyum_compat kullanabilir ve PECL uzantılarını yüklemeden aynı kripto libsodium tekliflerini kullanabilirsiniz.

Kriptografi mühendisliğinde elinizi denemek istiyorsanız, okumaya devam edin.


İlk olarak, kimliği doğrulanmamış şifrelemenin ve Kriptografik Kıyamet İlkesi'nin tehlikelerini öğrenmek için zaman ayırmalısınız .

  • Şifrelenmiş veriler yine de kötü niyetli bir kullanıcı tarafından oynanabilir.
  • Şifrelenmiş verilerin kimliğinin doğrulanması kurcalamayı önler.
  • Şifrelenmemiş verilerin kimliğinin doğrulanması kurcalamayı önlemez.

Şifreleme ve Şifre Çözme

PHP'de Şifreleme biz kullanacağız (aslında basittir openssl_encrypt()ve openssl_decrypt()size bilgileri şifrelemek için nasıl ilgili bazı kararlar yaptıktan sonra başvurun. openssl_get_cipher_methods()Sisteminizde desteklenen yöntemlerinin listesi için en iyi seçimdir. TO modunda AES :

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

Şu anda AES anahtar boyutunun endişelenmesi gereken önemli bir konu olduğuna inanmak için bir neden yoktur ( 256 bit modundaki kötü anahtar zamanlaması nedeniyle büyük olasılıkla daha iyi değildir ).

Not: Kullanılmadığı için terkedilebilir ve güvenliği etkileyebilecek , hata ayıklanmış hatalar kullanmıyoruzmcrypt . Bu nedenlerden dolayı, diğer PHP geliştiricilerini de bundan kaçınmaları için teşvik ediyorum.

OpenSSL kullanarak Basit Şifreleme / Şifre Çözme Sarıcı

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

Kullanım Örneği

$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


Yukarıdaki basit kripto kütüphanesinin kullanımı hala güvenli değildir. Biz gereken metni elde doğrulamak ve biz şifresini önce onları doğrulamak .

Not : Varsayılan olarak UnsafeCrypto::encrypt()ham bir ikili dize döndürür. İkili güvenlikli bir biçimde (base64 kodlu) saklamanız gerekiyorsa bu şekilde adlandırın:

$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

Basit Kimlik Doğrulama Sarıcı

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

Kullanım Örneği

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

Demolar : ham ikili , base64 kodlu


Bu SaferCryptokütüphaneyi bir üretim ortamında veya aynı kavramları kendi uygulamanızda kullanmak isteyen varsa , sizden önce ikinci bir görüş için yerleşik kriptograflarınıza ulaşmanızı şiddetle tavsiye ederim . Farkında bile olamayacağım hatalardan bahsedebilirler.

Saygın bir şifreleme kütüphanesi kullanmaktan çok daha iyi olacaksınız .


3
Bu yüzden, sadece UnsafeCrypto'nun önce çalışmasını sağlıyorum. Şifreleme iyi olur, ancak şifresini her çalıştırdığımda, yanıt olarak 'yanlış' alıyorum. Şifresini çözmek ve kod çözme yanı sıra kod çözme doğru geçen aynı anahtarı kullanıyorum. Örnekte bir typeo olduğunu varsaydığım şey, sorunumun nereden geldiğini merak ediyorum. $ Mac değişkeninin nereden geldiğini açıklayabilir misiniz ve bu sadece iv $ mı olmalıdır?
David C

1
@EugenRieck OpenSSL şifreleme uygulamaları muhtemelen emilmeyen parçalardır ve vanilya PHP'de AES-NI'den yararlanmanın tek yolu budur. OpenBSD'ye yüklerseniz PHP, PHP kodu bir fark fark etmeden LibreSSL'ye karşı derlenecektir. Libsodium> OpenSSL her gün. Ayrıca libmcrypt kullanmayın . PHP geliştiricilerinin OpenSSL yerine ne kullanmasını önerirsiniz?
Scott Arciszewski

2
Ne 5.2 ne de 5.3 artık desteklenmiyor . Bunun yerine , 5.6 gibi PHP'nin desteklenen bir sürümüne güncelleme yapmalısınız .
Scott Arciszewski


1
Ben sadece sizin anahtarlarınız için insan-readabale dizeleri değil, ikili dizeleri istediğiniz bir gösteri olarak yaptım .
Scott Arciszewski

22

Kullanım mcrypt_encrypt()ve mcrypt_decrypt()karşılık gelen parametrelerle. Gerçekten kolay ve anlaşılır ve savaşta test edilmiş bir şifreleme paketi kullanıyorsunuz.

DÜZENLE

Bu yanıttan 5 yıl 4 ay sonra, mcryptuzantı artık PHP'den kullanımdan kaldırılma ve nihai olarak kaldırılma sürecindedir.


34
Savaş test edildi ve 8 yıldan fazla güncellenmedi mi?
Maarten Bodewes

2
Mcrypt PHP7'de ve kullanımdan kaldırılmadı - bu benim için yeterince iyi. Tüm kodlar OpenSSL'nin korkunç kalitesinde değildir ve birkaç günde bir yama gerektirir.
Eugen Rieck

3
mcrypt sadece destek açısından korkunç değil. Ayrıca PKCS # 7 uyumlu dolgu, kimlik doğrulamalı şifreleme gibi en iyi uygulamaları uygulamaz. SHA-3'ü veya başka bir yeni algoritmayı desteklemez, çünkü kimse bunu sürdürmez ve sizi bir yükseltme yolundan soyar. Ayrıca kısmi anahtarlar, sıfır dolgu vb. Gibi şeyleri kabul etmek için kullanılırdı. PHP'nin aşamalı olarak kaldırılmasının iyi bir nedeni vardır.
Maarten Bodewes

2
PHP 7.1'de tüm mcrypt_ * işlevleri bir E_DEPRECATED uyarısı oluşturur. PHP 7.1 + 1'de (7.2 veya 8.0 olsun), mcrypt uzantısı çekirdeğin dışına ve PECL'ye taşınacaktır, burada gerçekten yüklemek isteyen insanlar PECL'den PHP uzantıları yükleyebilirlerse yine de yapabilirler.
Mladen Janjetoviç

4

PHP 7.2 tamamen uzaklaştı Mcryptve şimdi şifreleme sürdürülebilir Libsodiumkütüphaneye dayanıyor .

Tüm şifreleme ihtiyaçlarınız temel olarak Libsodiumkütüphane ile çözülebilir .

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

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


2
bir kod yapıştırırsanız, tüm değişkenlerin kapsandığından emin olun. Örneğinizde $ secret_sign_key ve $ alice_sign_publickey NULL
undefinedman

1
crypto_signAPI yapar değil şifrelemek mesajları - biri gerektireceğini crypto_aead_*_encryptfonksiyonları.
Roger Dueck

1

ÖNEMLİ Bu cevap sadece PHP 5 için geçerlidir, PHP 7'de dahili şifreleme fonksiyonları kullanılır.

İşte basit ama yeterince güvenli uygulama:

  • CBC modunda AES-256 şifreleme
  • PBKDF2 düz metin paroladan şifreleme anahtarı oluşturmak için
  • HMAC şifreli mesajı doğrulamak için.

Kod ve örnekler burada: https://stackoverflow.com/a/19445173/1387163


1
Bir şifreleme uzmanı değilim, ancak doğrudan bir paroladan türetilen bir anahtarın olması korkunç bir fikir gibi görünüyor. Gökkuşağı tabloları + zayıf şifre ve gitti sizin güvenliğinizdir. Ayrıca PHP 7.1'den beri kullanımdan kaldırılan mcrypt işlevlerine bağlantı noktanız
Alph.Dev

@ Alph.Dev doğru cevap yukarıdaki cevap sadece PHP 5 için geçerlidir
Eugene Fidelin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.