Crittografia bidirezionale più semplice tramite PHP


230

Qual è il modo più semplice di eseguire la crittografia bidirezionale nelle installazioni PHP comuni?

Devo essere in grado di crittografare i dati con una chiave stringa e utilizzare la stessa chiave per decrittografare dall'altra parte.

La sicurezza non è così importante come la portabilità del codice, quindi mi piacerebbe essere in grado di mantenere le cose il più semplice possibile. Attualmente sto usando un'implementazione di RC4, ma se trovo qualcosa supportato nativamente immagino di poter salvare un sacco di codice non necessario.



3
Per la crittografia per scopi generici, usa defuse / php-crittografia / invece di lanciare il tuo.
Scott Arciszewski,

2
Mani lontane da github.com/defuse/php-encryption - è più lento di ordini di grandezza rispetto a mcrypt.
Eugen Rieck,

1
@Scott Pensare sulla falsariga di "questo probabilmente non sarà il collo di bottiglia" è ciò che ci ha portato un sacco di software dannoso.
Eugen Rieck,

3
Se stai davvero crittografando / decrittografando molti dati al punto che i millisecondi che costano impantanano la tua applicazione, mordi il proiettile e passa a libsodium. Sodium::crypto_secretbox()e Sodium::crypto_secretbox_open()sono sicuri e performanti.
Scott Arciszewski,

Risposte:


196

Modificato:

Dovresti davvero usare openssl_encrypt () e openssl_decrypt ()

Come dice Scott , Mcrypt non è una buona idea in quanto non è stato aggiornato dal 2007.

Esiste persino un RFC per rimuovere Mcrypt da PHP - https://wiki.php.net/rfc/mcrypt-viking-funeral


6
@EugenRieck Sì, questo è il punto. Mcrypt non riceve patch. OpenSSL riceve patch non appena viene rilevata una vulnerabilità, grande o piccola.
Greg,

5
sarebbe meglio per una risposta così votata, essere presenti anche esempi più semplici in risposta. grazie comunque.
T.Todua,

ragazzi, solo FYI => MCRYPT È DEPRECATO. il limite quindi tutti dovrebbero sapere di non usarlo perché ci ha dato una miriade di problemi. Si è deprecato dal PHP 7.1 se non sbaglio.
clusterBuddy

Da PHP 7 la funzione mcrypt viene rimossa dalla base di codice php. Quindi quando si utilizza l'ultima versione di php (che dovrebbe essere standard) non è più possibile utilizzare questa funzione obsoleta.
Alexander Behling,

234

Importante : a meno che non si abbia un caso d'uso molto particolare, non crittografare le password , utilizzare invece un algoritmo di hashing delle password. Quando qualcuno dice di crittografare le password in un'applicazione lato server, non sono informati o descrivono un progetto di sistema pericoloso. La memorizzazione sicura delle password è un problema completamente separato dalla crittografia.

Essere informato. Progetta sistemi sicuri.

Crittografia dei dati portatile in PHP

Se stai utilizzando PHP 5.4 o versioni successive e non vuoi scrivere tu stesso un modulo di crittografia, ti consiglio di utilizzare una libreria esistente che fornisce la crittografia autenticata . La biblioteca che ho collegato si basa solo su ciò che fornisce PHP ed è sottoposta a revisione periodica da parte di una manciata di ricercatori sulla sicurezza. (Me stesso incluso.)

Se i tuoi obiettivi di portabilità non impediscono la richiesta di estensioni PECL, libsodium è altamente raccomandato su qualsiasi cosa tu o io possiamo scrivere in PHP.

Aggiornamento (12/06/2016): ora puoi utilizzare sodium_compat e utilizzare le stesse offerte di cripto-libsodio senza installare estensioni PECL.

Se vuoi cimentarti nell'ingegneria della crittografia, continua a leggere.


Innanzitutto, dovresti prenderti il ​​tempo per imparare i pericoli della crittografia non autenticata e del Principio crittografico del destino .

  • I dati crittografati possono comunque essere manomessi da un utente malintenzionato.
  • L'autenticazione dei dati crittografati impedisce la manomissione.
  • L'autenticazione dei dati non crittografati non impedisce la manomissione.

Crittografia e decrittografia

La crittografia in PHP è in realtà semplice (useremo openssl_encrypt()e openssl_decrypt()una volta che avrai preso alcune decisioni su come crittografare le tue informazioni. Consulta openssl_get_cipher_methods()un elenco dei metodi supportati sul tuo sistema. La scelta migliore è AES in modalità CTR :

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

Al momento non vi è motivo di ritenere che la dimensione della chiave AES sia un problema significativo di cui preoccuparsi (probabilmente la dimensione maggiore non è migliore a causa di una pianificazione errata della chiave nella modalità a 256 bit).

Nota: non lo stiamo utilizzando mcryptperché èandonware e presenta bug senza patch che potrebbero compromettere la sicurezza. Per questi motivi, incoraggio anche altri sviluppatori PHP a evitarlo.

Wrapper di crittografia / decrittografia semplice con 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;
    }
}

Esempio di utilizzo

$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


La semplice libreria di crittografia di cui sopra non è ancora sicura da usare. Dobbiamo autenticare i cifrati e verificarli prima di decifrare .

Nota : per impostazione predefinita, UnsafeCrypto::encrypt()restituirà una stringa binaria non elaborata. Chiamalo così se hai bisogno di memorizzarlo in un formato binario sicuro (codificato in base64):

$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

Wrapper di autenticazione semplice

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

Esempio di utilizzo

$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 : binari crudo , base64 codificato


Se qualcuno desidera utilizzare questa SaferCryptolibreria in un ambiente di produzione o la propria implementazione degli stessi concetti, consiglio vivamente di contattare i crittografi residenti per un secondo parere prima di voi. Saranno in grado di parlarti di errori di cui forse non sono nemmeno a conoscenza.

Sarai molto meglio usando una libreria di crittografia stimabile .


3
Quindi, sto solo cercando di far funzionare prima UnsafeCrypto. La crittografia avviene bene, ma ogni volta che eseguo la decrittografia, ricevo "false" come risposta. Sto usando la stessa chiave per decrittografare e passare true sulla codifica, nonché sulla decodifica. C'è, quello che presumo sia un errore di battitura nell'esempio, mi chiedo se è da lì che proviene il mio problema. Puoi spiegare da dove proviene la variabile $ mac e dovrebbe essere semplicemente $ iv?
David C,

1
@EugenRieck Le implementazioni di cifratura OpenSSL sono probabilmente le uniche parti che non fanno schifo, ed è l'unico modo per sfruttare AES-NI in PHP vaniglia. Se installi su OpenBSD, PHP verrà compilato su LibreSSL senza che il codice PHP noti una differenza. Libsodium> OpenSSL in qualsiasi giorno. Inoltre, non usare libmcrypt . Cosa consiglieresti agli sviluppatori PHP di usare al posto di OpenSSL?
Scott Arciszewski,

2
Né 5.2 né 5.3 sono più supportati . Dovresti invece esaminare l'aggiornamento a una versione supportata di PHP , ad esempio 5.6.
Scott Arciszewski,


1
L'ho fatto solo come dimostrazione che vuoi stringhe binarie, non stringhe di lettura umana, per le tue chiavi .
Scott Arciszewski,

22

Utilizzare mcrypt_encrypt()e mcrypt_decrypt()con i parametri corrispondenti. Davvero semplice e diretto, e usi un pacchetto di crittografia testato in battaglia.

MODIFICARE

5 anni e 4 mesi dopo questa risposta, l' mcryptestensione è ora in fase di deprecazione ed eventuale rimozione da PHP.


34
Battle test e non aggiornato per più di 8 anni?
Maarten Bodewes,

2
Bene, mcrypt è in PHP7 e non è deprecato - è abbastanza buono per me. Non tutto il codice è orribile di OpenSSL e necessita di patch ogni pochi giorni.
Eugen Rieck,

3
mcrypt non è solo orribile per quanto riguarda il supporto. Inoltre, non implementa le migliori pratiche come l'imbottitura conforme a PKCS # 7, la crittografia autenticata. Non supporterà SHA-3 o altri nuovi algoritmi poiché nessuno lo sta mantenendo, derubandoti di un percorso di aggiornamento. Inoltre accettava cose come chiavi parziali, eseguire zero padding ecc. C'è una buona ragione per cui è in procinto di essere gradualmente rimosso da PHP.
Maarten Bodewes,

2
In PHP 7.1, tutte le funzioni mcrypt_ * genereranno un avviso E_DEPRECATED. In PHP 7.1 + 1 (sia 7.2 o 8.0), l'estensione mcrypt verrà spostata fuori dal core e in PECL, dove le persone che vogliono davvero installarla possono ancora farlo se possono installare estensioni PHP da PECL.
Mladen Janjetovic,

4

PHP 7.2 si è completamente allontanato Mcrypte la crittografia ora si basa sulla Libsodiumlibreria gestibile.

Tutte le esigenze di crittografia possono essere sostanzialmente risolte tramite la Libsodiumlibreria.

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

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


2
se incolli del codice, assicurati che tutte le variabili siano coperte. Nel tuo esempio $ secret_sign_key e $ alice_sign_publickey sono NULL
undefinedman

1
L' crypto_signAPI non crittografa i messaggi, ciò richiederà una delle crypto_aead_*_encryptfunzioni.
Roger Dueck,

1

IMPORTANTE questa risposta è valida solo per PHP 5, in PHP 7 usa le funzioni crittografiche integrate.

Ecco un'implementazione semplice ma abbastanza sicura:

  • Crittografia AES-256 in modalità CBC
  • PBKDF2 per creare la chiave di crittografia dalla password di testo normale
  • HMAC per autenticare il messaggio crittografato.

Codice ed esempi sono qui: https://stackoverflow.com/a/19445173/1387163


1
Non sono un esperto di crittografia, ma avere una chiave derivata direttamente da una password sembra un'idea terribile. Tabelle arcobaleno + password debole e via è la tua sicurezza. Anche il tuo link punta a funzioni mcrypt, che sono deprecate da PHP 7.1
Alph.Dev

@ Alph.Dev hai ragione la risposta sopra è valida solo per PHP 5
Eugene Fidelin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.