Prima di fare qualsiasi altra cosa, cerca di capire la differenza tra crittografia e autenticazione e perché probabilmente desideri la crittografia autenticata anziché solo la crittografia .
Per implementare la crittografia autenticata, si desidera crittografare quindi MAC. L'ordine di crittografia e autenticazione è molto importante! Una delle risposte esistenti a questa domanda ha fatto questo errore; come fanno molte librerie di crittografia scritte in PHP.
Dovresti evitare di implementare la tua crittografia e invece utilizzare una libreria sicura scritta e rivista da esperti di crittografia.
Aggiornamento: PHP 7.2 ora fornisce libsodium ! Per la massima sicurezza, aggiorna i tuoi sistemi per utilizzare PHP 7.2 o versioni successive e segui solo i consigli di libsodium in questa risposta.
Utilizzare libsodium se si dispone dell'accesso PECL (o sodium_compat se si desidera libsodium senza PECL); altrimenti ...
Usa crittografia disuso / php ; non girare la tua crittografia!
Entrambe le librerie collegate sopra rendono facile e indolore implementare la crittografia autenticata nelle proprie librerie.
Se vuoi ancora scrivere e distribuire la tua libreria di crittografia, contro la saggezza convenzionale di ogni esperto di crittografia su Internet, questi sono i passi che dovresti prendere.
crittografia:
- Crittografare utilizzando AES in modalità CTR. È inoltre possibile utilizzare GCM (che elimina la necessità di un MAC separato). Inoltre, ChaCha20 e Salsa20 (forniti da libsodium ) sono cifre di stream e non necessitano di modalità speciali.
- A meno che tu non abbia scelto GCM sopra, dovresti autenticare il testo cifrato con HMAC-SHA-256 (o, per i stream stream, Poly1305 - la maggior parte delle API libsodium lo fa per te). Il MAC dovrebbe coprire il IV così come il testo cifrato!
decrittazione:
- A meno che non venga utilizzato Poly1305 o GCM, ricalcolare il MAC del testo cifrato e confrontarlo con il MAC inviato
hash_equals()
. Se fallisce, interrompere.
- Decifrare il messaggio.
Altre considerazioni sulla progettazione:
- Non comprimere mai niente. Il testo cifrato non è comprimibile; la compressione del testo in chiaro prima della crittografia può causare perdite di informazioni (ad esempio CRIME e BREACH su TLS).
- Assicurati di usare
mb_strlen()
e mb_substr()
, usando la '8bit'
modalità set di caratteri per evitare mbstring.func_overload
problemi.
- I IV dovrebbero essere generati usando un CSPRNG ; Se stai usando
mcrypt_create_iv()
, NON USAREMCRYPT_RAND
!
- A meno che non si stia utilizzando un costrutto AEAD, crittografare SEMPRE quindi MAC!
bin2hex()
, base64_encode()
ecc. potrebbero perdere informazioni sulle chiavi di crittografia tramite i tempi della cache. Evitali se possibile.
Anche se segui i consigli forniti qui, molto può andare storto con la crittografia. Chiedi sempre a un esperto di crittografia di rivedere la tua implementazione. Se non sei abbastanza fortunato da essere amico personale di uno studente di crittografia nella tua università locale, puoi sempre provare il forum Scambio di stack di crittografia per un consiglio.
Se hai bisogno di un'analisi professionale della tua implementazione, puoi sempre assumere un rispettabile team di consulenti per la sicurezza per rivedere il tuo codice di crittografia PHP (divulgazione: il mio datore di lavoro).
Importante: quando non utilizzare la crittografia
Non crittografare le password . Si desidera hash invece, utilizzando uno di questi algoritmi di hashing delle password:
Non utilizzare mai una funzione hash per scopi generici (MD5, SHA256) per la memorizzazione delle password.
Non crittografare i parametri URL . È lo strumento sbagliato per il lavoro.
Esempio di crittografia di stringhe PHP con Libsodium
Se sei su PHP <7.2 o altrimenti non hai installato libsodium, puoi usare sodium_compat per ottenere lo stesso risultato (anche se più lentamente).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Quindi per provarlo:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - Libsodium reso più facile
Uno dei progetti a cui sto lavorando è una libreria di crittografia chiamata Halite , che mira a rendere libsodium più semplice e intuitivo.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Tutta la crittografia sottostante è gestita da libsodium.
Esempio con disinnesto / crittografia php
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Nota : Crypto::encrypt()
restituisce output con codifica esadecimale.
Gestione delle chiavi di crittografia
Se sei tentato di usare una "password", fermati subito. È necessaria una chiave di crittografia casuale a 128 bit, non una password memorabile.
È possibile memorizzare una chiave di crittografia per un uso a lungo termine in questo modo:
$storeMe = bin2hex($key);
E, su richiesta, è possibile recuperarlo in questo modo:
$key = hex2bin($storeMe);
Consiglio vivamente di archiviare una chiave generata casualmente per un uso a lungo termine anziché qualsiasi tipo di password come chiave (o per derivare la chiave).
Se stai usando la libreria di Defuse:
"Ma voglio davvero usare una password."
Questa è una cattiva idea, ma va bene, ecco come farlo in sicurezza.
Innanzitutto, genera una chiave casuale e memorizzala in una costante.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Nota che stai aggiungendo lavoro extra e potresti semplicemente usare questa costante come chiave e risparmiarti un sacco di angoscia!
Quindi utilizzare PBKDF2 (in questo modo) per ricavare una chiave di crittografia adatta dalla password anziché crittografarla direttamente con la password.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
Non usare solo una password di 16 caratteri. La tua chiave di crittografia verrà comicamente rotta.