Einfachste bidirektionale Verschlüsselung mit PHP

230

Was ist die einfachste Methode zur bidirektionalen Verschlüsselung in gängigen PHP-Installationen?

Ich muss in der Lage sein, Daten mit einem Zeichenfolgenschlüssel zu verschlüsseln und am anderen Ende denselben Schlüssel zum Entschlüsseln zu verwenden.

Die Sicherheit ist nicht so wichtig wie die Portabilität des Codes, daher möchte ich die Dinge so einfach wie möglich halten können. Derzeit verwende ich eine RC4-Implementierung, aber wenn ich etwas finde, das von Haus aus unterstützt wird, kann ich wahrscheinlich viel unnötigen Code speichern.

user1206970
quelle
3
Verwenden Sie für die allgemeine Verschlüsselung defuse / php-encryption /, anstatt Ihre eigene zu rollen.
Scott Arciszewski
2
Hände weg von github.com/defuse/php-encryption - es ist um Größenordnungen langsamer als mcrypt.
Eugen Rieck
1
@Scott Das Denken nach dem Motto "Dies wird wahrscheinlich nicht der Engpass sein" hat uns viel schlechte Software gebracht.
Eugen Rieck
3
Wenn Sie wirklich viele Daten so weit verschlüsseln / entschlüsseln, dass die Millisekunden, die sie kosten, Ihre Anwendung blockieren, beißen Sie in die Kugel und wechseln Sie zu libsodium. Sodium::crypto_secretbox()und Sodium::crypto_secretbox_open()sind sicher und performant.
Scott Arciszewski

Antworten:

196

Bearbeitet:

Sie sollten wirklich openssl_encrypt () & openssl_decrypt () verwenden

Wie Scott sagt, ist Mcrypt keine gute Idee, da es seit 2007 nicht aktualisiert wurde.

Es gibt sogar einen RFC, um Mcrypt aus PHP zu entfernen - https://wiki.php.net/rfc/mcrypt-viking-funeral

472084
quelle
6
@ EugenRieck Ja, das ist der Punkt. Mcrypt erhält keine Patches. OpenSSL erhält Patches, sobald eine große oder kleine Sicherheitslücke entdeckt wird.
Greg
5
Es wäre besser für eine solche hochstimmige Antwort, dort auch einfachste Beispiele als Antwort zu liefern. Danke trotzdem.
T.Todua
Leute, nur zu Ihrer Information => MCRYPT IST DEPRECATED. Capsing, damit jeder wissen sollte, es nicht zu benutzen, da es uns eine Vielzahl von Problemen gab. Es ist seit PHP 7.1 veraltet, wenn ich mich nicht irre.
ClusterBuddy
Seit PHP 7 wird die mcrypt-Funktion aus der PHP-Codebasis entfernt. Wenn Sie also die neueste Version von PHP verwenden (die Standard sein sollte), können Sie diese veraltete Funktion nicht mehr verwenden.
Alexander Behling
234

Wichtig : Wenn Sie einen haben sehr speziellen Anwendungsfall, nicht verschlüsseln Passwörter , verwenden Sie stattdessen ein Kennwort - Hashing - Algorithmus. Wenn jemand sagt, dass er seine Passwörter in einer serverseitigen Anwendung verschlüsselt , ist er entweder nicht informiert oder beschreibt ein gefährliches Systemdesign. Das sichere Speichern von Passwörtern ist ein völlig anderes Problem als die Verschlüsselung.

Informiert werden. Entwerfen Sie sichere Systeme.

Portable Datenverschlüsselung in PHP

Wenn Sie PHP 5.4 oder neuer verwenden und kein Kryptografiemodul selbst schreiben möchten, empfehle ich die Verwendung einer vorhandenen Bibliothek, die authentifizierte Verschlüsselung bietet . Die Bibliothek, die ich verlinkt habe, basiert nur auf dem, was PHP bietet, und wird regelmäßig von einer Handvoll Sicherheitsforschern überprüft. (Mich eingenommen.)

Wenn Ihre Portabilität Ziele nicht verhindern PECL - Erweiterungen erfordern, libsodium ist hoch über alles , was Sie empfohlen oder ich kann in PHP schreiben.

Update (12.06.2016): Sie können jetzt Natrium_Kompat verwenden und dieselben Crypto Libsodium-Angebote verwenden, ohne PECL-Erweiterungen zu installieren.

Wenn Sie sich in der Kryptografietechnik versuchen möchten, lesen Sie weiter.


Zunächst sollten Sie sich die Zeit nehmen, um die Gefahren einer nicht authentifizierten Verschlüsselung und das Cryptographic Doom-Prinzip kennenzulernen .

  • Verschlüsselte Daten können weiterhin von einem böswilligen Benutzer manipuliert werden.
  • Durch die Authentifizierung der verschlüsselten Daten werden Manipulationen verhindert.
  • Die Authentifizierung der unverschlüsselten Daten verhindert keine Manipulationen.

Verschlüsselung und Entschlüsselung

Die Verschlüsselung in PHP ist eigentlich einfach (wir werden sie verwenden openssl_encrypt()und openssl_decrypt()sobald Sie einige Entscheidungen zur Verschlüsselung Ihrer Informationen getroffen haben. openssl_get_cipher_methods()Eine Liste der auf Ihrem System unterstützten Methoden finden Sie hier. Die beste Wahl ist AES im CTR-Modus :

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

Derzeit gibt es keinen Grund zu der Annahme, dass die Größe des AES-Schlüssels ein wichtiges Problem darstellt (größer ist wahrscheinlich nicht besser, da die Schlüsselplanung im 256-Bit-Modus schlecht ist).

Hinweis: Wir verwenden es nicht, mcryptda es sich um Abbruchware handelt und nicht gepatchte Fehler aufweist , die möglicherweise die Sicherheit beeinträchtigen. Aus diesen Gründen ermutige ich andere PHP-Entwickler, dies ebenfalls zu vermeiden.

Einfacher Verschlüsselungs- / Entschlüsselungs-Wrapper mit 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;
    }
}

Anwendungsbeispiel

$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


Die obige einfache Kryptobibliothek ist immer noch nicht sicher zu verwenden. Wir müssen Chiffretexte authentifizieren und überprüfen, bevor wir entschlüsseln .

Hinweis : Standardmäßig UnsafeCrypto::encrypt()wird eine rohe Binärzeichenfolge zurückgegeben. Nennen Sie es so, wenn Sie es in einem binärsicheren Format (base64-codiert) speichern müssen:

$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

Einfacher Authentifizierungs-Wrapper

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

Anwendungsbeispiel

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

Demos : Raw Binary , Base64-codiert


Wenn jemand diese SaferCryptoBibliothek in einer Produktionsumgebung oder in Ihrer eigenen Implementierung derselben Konzepte verwenden möchte, empfehle ich dringend, sich vor Ihnen an Ihre ansässigen Kryptographen zu wenden, um eine zweite Meinung einzuholen. Sie können Ihnen von Fehlern erzählen, die mir vielleicht gar nicht bewusst sind.

Mit einer seriösen Kryptografie-Bibliothek sind Sie viel besser dran .

Scott Arciszewski
quelle
3
Ich versuche nur, das UnsafeCrypto zuerst zum Laufen zu bringen. Die Verschlüsselung funktioniert einwandfrei, aber jedes Mal, wenn ich die Entschlüsselung durchführe, wird als Antwort "falsch" angezeigt. Ich verwende denselben Schlüssel zum Entschlüsseln und übergebe sowohl die Codierung als auch die Decodierung true. Ich gehe davon aus, dass es sich bei dem Beispiel um einen Tippfehler handelt. Ich frage mich, ob dort mein Problem liegt. Können Sie erklären, woher die Variable $ mac kommt, und sollte es einfach $ iv sein?
David C
1
@EugenRieck Die OpenSSL-Verschlüsselungsimplementierungen sind wahrscheinlich die einzigen Teile, die nicht scheißen, und es ist die einzige Möglichkeit, AES-NI in Vanilla-PHP zu nutzen. Wenn Sie unter OpenBSD installieren, wird PHP gegen LibreSSL kompiliert, ohne dass der PHP-Code einen Unterschied bemerkt. Libsodium> OpenSSL jeden Tag. Auch nicht verwenden libmcrypt . Was würden Sie PHP-Entwicklern anstelle von OpenSSL empfehlen?
Scott Arciszewski
2
Weder 5.2 noch 5.3 werden mehr unterstützt . Sie sollten sich stattdessen mit der Aktualisierung auf eine unterstützte Version von PHP befassen , z. B. 5.6.
Scott Arciszewski
1
@BBeta paragonie.com/blog/2015/09/...
Scott Arciszewski
1
Ich habe es nur getan, um zu demonstrieren, dass Sie für Ihre Schlüssel binäre Zeichenfolgen und keine von Menschen lesbaren Zeichenfolgen benötigen .
Scott Arciszewski
22

Verwenden Sie mcrypt_encrypt()und mcrypt_decrypt()mit entsprechenden Parametern. Wirklich einfach und unkompliziert, und Sie verwenden ein kampferprobtes Verschlüsselungspaket.

BEARBEITEN

5 Jahre und 4 Monate nach dieser Antwort wird die mcryptErweiterung derzeit abgelehnt und schließlich aus PHP entfernt.

Eugen Rieck
quelle
34
Schlacht getestet und seit mehr als 8 Jahren nicht mehr aktualisiert?
Maarten Bodewes
2
Nun, mcrypt ist in PHP7 und nicht veraltet - das ist gut genug für mich. Nicht jeder Code ist von OpenSSLs schrecklicher Qualität und muss alle paar Tage gepatcht werden.
Eugen Rieck
3
mcrypt ist nicht nur in Bezug auf die Unterstützung schrecklich. Es werden auch keine Best Practices wie PKCS # 7-kompatibles Auffüllen und authentifizierte Verschlüsselung implementiert. SHA-3 oder andere neue Algorithmen werden nicht unterstützt, da niemand sie wartet und Ihnen einen Upgrade-Pfad raubt. Darüber hinaus akzeptierte es Dinge wie Teilschlüssel, Null-Auffüllen usw. Es gibt einen guten Grund, warum es gerade aus PHP entfernt wird.
Maarten Bodewes
2
In PHP 7.1 lösen alle mcrypt_ * -Funktionen einen E_DEPRECATED-Hinweis aus. In PHP 7.1 + 1 (sei es 7.2 oder 8.0) wird die mcrypt-Erweiterung aus dem Kern in PECL verschoben, wo Personen, die sie wirklich installieren möchten, dies möglicherweise weiterhin tun, wenn sie PHP-Erweiterungen aus PECL installieren können.
Mladen Janjetovic
4

PHP 7.2 wurde vollständig entfernt Mcryptund die Verschlüsselung basiert nun auf der wartbaren LibsodiumBibliothek.

Alle Ihre Verschlüsselungsanforderungen können grundsätzlich über die LibsodiumBibliothek gelöst werden .

// 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-Dokumentation: https://github.com/paragonie/pecl-libsodium-doc

Hemerson Varela
quelle
2
Wenn Sie Code einfügen, stellen Sie sicher, dass alle Variablen abgedeckt sind. In Ihrem Beispiel sind $ secret_sign_key und $ alice_sign_publickey NULL
undefinedman
1
Die crypto_signAPI verschlüsselt keine Nachrichten - dies erfordert eine der crypto_aead_*_encryptFunktionen.
Roger Dueck
1

WICHTIG Diese Antwort gilt nur für PHP 5, verwenden Sie in PHP 7 integrierte kryptografische Funktionen.

Hier ist die Implementierung einfach, aber sicher genug:

  • AES-256-Verschlüsselung im CBC-Modus
  • PBKDF2 zum Erstellen eines Verschlüsselungsschlüssels aus einem Klartextkennwort
  • HMAC zur Authentifizierung der verschlüsselten Nachricht.

Code und Beispiele finden Sie hier: https://stackoverflow.com/a/19445173/1387163

Eugene Fidelin
quelle
1
Ich bin kein Kryptographie-Experte, aber einen Schlüssel direkt von einem Passwort abzuleiten, scheint eine schreckliche Idee zu sein. Regenbogentabellen + schwaches Passwort und weg ist Ihre Sicherheit. Auch Ihr Link verweist auf mcrypt-Funktionen, die seit PHP 7.1
Alph.Dev
@ Alph.Dev Sie haben Recht, die obige Antwort gilt nur für PHP 5
Eugene Fidelin