Comment crypter / décrypter des données en PHP?

110

Je suis actuellement étudiant et j'étudie PHP, j'essaye de faire un simple chiffrement / déchiffrement de données en PHP. J'ai fait des recherches en ligne et certaines d'entre elles étaient assez déroutantes (du moins pour moi).

Voici ce que j'essaye de faire:

J'ai une table composée de ces champs (UserID, Fname, Lname, Email, Password)

Ce que je veux, c'est que tous les champs soient cryptés puis décryptés (est-il possible de les utiliser sha256pour le cryptage / décryptage, si ce n'est un algorithme de cryptage)

Une autre chose que je veux apprendre est comment créer un sens unique hash(sha256)combiné avec un bon "sel". (Fondamentalement, je veux juste avoir une implémentation simple de cryptage / décryptage, hash(sha256)+salt) Monsieur / Madame, vos réponses seraient d'une grande aide et seraient très appréciées. Merci ++

Randel Ramirez
la source
9
SHA est un hachage, pas un cryptage. Le point clé est qu'un hachage ne peut pas être inversé sur les données d'origine (pas facilement, de toute façon). Vous voulez probablement mcrypt ou s'il n'est pas disponible, je recommanderais phpseclib - bien qu'il soit important de noter que toute implémentation purement PHP de tout ce qui implique beaucoup de mathématiques de bas niveau sera sloooooowww ... C'est pourquoi j'aime phpseclib, car il utilise d'abord mcrypt s'il est disponible et ne revient aux implémentations PHP qu'en dernier recours.
DaveRandom
7
Vous ne voulez normalement pas pouvoir déchiffrer un mot de passe!
Ja͢ck
1
Fondamentalement, vous ne devez pas penser au cryptage à ce niveau, vous devez penser au contrôle d'accès, à la confidentialité, à l'intégrité et à l'authentification. Après cela, vérifiez comment vous pouvez y parvenir, éventuellement en utilisant un cryptage ou un hachage sécurisé. Vous voudrez peut-être lire dans PBKDF2 et bcrypt / scrypt pour comprendre le hachage sécurisé des mots de passe, etc.
Maarten Bodewes

Réponses:

289

Préface

En commençant par la définition de votre table:

- UserID
- Fname
- Lname
- Email
- Password
- IV

Voici les changements:

  1. Les champs Fname, Lnameet Emailseront cryptés à l'aide d'un chiffrement symétrique, fourni par OpenSSL ,
  2. Le IVchamp stockera le vecteur d'initialisation utilisé pour le cryptage. Les exigences de stockage dépendent du chiffrement et du mode utilisés; plus à ce sujet plus tard.
  3. Le Passwordchamp sera haché à l'aide d'un hachage de mot de passe à sens unique,

Chiffrement

Chiffre et mode

Le choix du meilleur chiffrement et du meilleur mode de chiffrement dépasse le cadre de cette réponse, mais le choix final affecte la taille de la clé de chiffrement et du vecteur d'initialisation; pour cet article, nous utiliserons AES-256-CBC qui a une taille de bloc fixe de 16 octets et une taille de clé de 16, 24 ou 32 octets.

Clé de cryptage

Une bonne clé de chiffrement est un objet blob binaire généré à partir d'un générateur de nombres aléatoires fiable. L'exemple suivant serait recommandé (> = 5.3):

$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe

Cela peut être fait une ou plusieurs fois (si vous souhaitez créer une chaîne de clés de chiffrement). Gardez-les aussi privés que possible.

IV

Le vecteur d'initialisation ajoute un caractère aléatoire au cryptage et est requis pour le mode CBC. Ces valeurs devraient idéalement être utilisées une seule fois (techniquement une fois par clé de chiffrement), de sorte qu'une mise à jour de n'importe quelle partie d'une ligne devrait la régénérer.

Une fonction est fournie pour vous aider à générer l'IV:

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

Exemple

Crypterons le champ de nom, en utilisant le précédent $encryption_keyet $iv; pour ce faire, nous devons remplir nos données à la taille du bloc:

function pkcs7_pad($data, $size)
{
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}

$name = 'Jack';
$enc_name = openssl_encrypt(
    pkcs7_pad($name, 16), // padded data
    'AES-256-CBC',        // cipher and mode
    $encryption_key,      // secret key
    0,                    // options (not used)
    $iv                   // initialisation vector
);

Exigences de stockage

La sortie cryptée, comme l'IV, est binaire; le stockage de ces valeurs dans une base de données peut être effectué en utilisant des types de colonnes désignés tels que BINARYou VARBINARY.

La valeur de sortie, comme l'IV, est binaire; pour stocker ces valeurs dans MySQL, pensez à utiliser des colonnes BINARYouVARBINARY . Si ce n'est pas une option, vous pouvez également convertir les données binaires en une représentation textuelle à l'aide de base64_encode()ou bin2hex(), cela nécessite entre 33% et 100% d'espace de stockage supplémentaire.

Décryptage

Le déchiffrement des valeurs stockées est similaire:

function pkcs7_unpad($data)
{
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}

$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];

$name = pkcs7_unpad(openssl_decrypt(
    $enc_name,
    'AES-256-CBC',
    $encryption_key,
    0,
    $iv
));

Cryptage authentifié

Vous pouvez améliorer encore l'intégrité du texte chiffré généré en ajoutant une signature générée à partir d'une clé secrète (différente de la clé de chiffrement) et du texte chiffré. Avant que le texte chiffré ne soit déchiffré, la signature est d'abord vérifiée (de préférence avec une méthode de comparaison à temps constant).

Exemple

// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);

// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;

// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);

if (hash_equals($auth, $actual_auth)) {
    // perform decryption
}

Voir également: hash_equals()

Hashing

Le stockage d'un mot de passe réversible dans votre base de données doit être évité autant que possible; vous souhaitez uniquement vérifier le mot de passe plutôt que de connaître son contenu. Si un utilisateur perd son mot de passe, il est préférable de lui permettre de le réinitialiser plutôt que de lui envoyer son mot de passe d'origine (assurez-vous que la réinitialisation du mot de passe ne peut être effectuée que pour une durée limitée).

L'application d'une fonction de hachage est une opération unidirectionnelle; ensuite, il peut être utilisé en toute sécurité pour la vérification sans révéler les données originales; pour les mots de passe, une méthode de force brute est une approche réalisable pour le découvrir en raison de sa longueur relativement courte et des mauvais choix de mots de passe de nombreuses personnes.

Des algorithmes de hachage tels que MD5 ou SHA1 ont été créés pour vérifier le contenu du fichier par rapport à une valeur de hachage connue. Ils sont grandement optimisés pour rendre cette vérification aussi rapide que possible tout en restant précise. Compte tenu de leur espace de sortie relativement limité, il était facile de créer une base de données avec des mots de passe connus et leurs sorties de hachage respectives, les tables arc-en-ciel.

Ajouter un sel au mot de passe avant le hachage rendrait une table arc-en-ciel inutile, mais les récentes avancées matérielles ont fait des recherches par force brute une approche viable. C'est pourquoi vous avez besoin d'un algorithme de hachage délibérément lent et tout simplement impossible à optimiser. Il devrait également être en mesure d'augmenter la charge d'un matériel plus rapide sans affecter la capacité de vérifier les hachages de mots de passe existants pour le rendre à l'épreuve du temps.

Actuellement, il existe deux choix populaires disponibles:

  1. PBKDF2 (fonction de dérivation de clé basée sur le mot de passe v2)
  2. bcrypt (alias Blowfish)

Cette réponse utilisera un exemple avec bcrypt.

Génération

Un hachage de mot de passe peut être généré comme ceci:

$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
    13, // 2^n cost factor
    substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);

$hash = crypt($password, $salt);

Le sel est généré avec openssl_random_pseudo_bytes()pour former une goutte aléatoire de données qui est ensuite parcourue base64_encode()et strtr()pour correspondre à l'alphabet requis de [A-Za-z0-9/.].

La crypt()fonction effectue le hachage en fonction de l'algorithme ( $2y$pour Blowfish), du facteur de coût (un facteur de 13 prend environ 0,40s sur une machine 3GHz) et du sel de 22 caractères.

Validation

Une fois que vous avez récupéré la ligne contenant les informations utilisateur, vous validez le mot de passe de cette manière:

$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash

$given_hash = crypt($given_password, $db_hash);

if (isEqual($given_hash, $db_hash)) {
    // user password verified
}

// constant time string compare
function isEqual($str1, $str2)
{
    $n1 = strlen($str1);
    if (strlen($str2) != $n1) {
        return false;
    }
    for ($i = 0, $diff = 0; $i != $n1; ++$i) {
        $diff |= ord($str1[$i]) ^ ord($str2[$i]);
    }
    return !$diff;
}

Pour vérifier un mot de passe, vous appelez à crypt()nouveau mais vous transmettez le hachage précédemment calculé comme valeur de sel. La valeur de retour donne le même hachage si le mot de passe donné correspond au hachage. Pour vérifier le hachage, il est souvent recommandé d'utiliser une fonction de comparaison à temps constant pour éviter les attaques de synchronisation.

Hachage de mot de passe avec PHP 5.5

PHP 5.5 a introduit les fonctions de hachage de mot de passe que vous pouvez utiliser pour simplifier la méthode de hachage ci-dessus:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

Et vérifier:

if (password_verify($given_password, $db_hash)) {
    // password valid
}

Voir aussi: password_hash(),password_verify()

Jack
la source
Quelle longueur dois-je utiliser pour stocker le nom, le prénom, l'adresse e-mail, etc. pour un pari plus sûr? varbinary (???)
BentCoder
2
Bien sûr, mais cela dépend de la façon dont il est utilisé. Si vous publiez une bibliothèque de chiffrement, vous ne savez pas comment les développeurs vont l'implémenter. C'est pourquoi github.com/defuse/php-encryption fournit un cryptage à clé symétrique authentifié et ne permet pas aux développeurs de l'affaiblir sans modifier son code.
Scott Arciszewski
2
@Scott Très bien, j'ai ajouté un exemple de cryptage authentifié; merci pour la poussée :)
Ja͢ck
1
+1 pour le cryptage authentifié. Il n'y a pas assez d'informations dans la question pour dire que AE n'est pas nécessaire ici. Il est certain que le trafic SQL passe souvent sur un réseau avec des propriétés de sécurité inconnues, tout comme le trafic de la base de données au stockage. Sauvegardes et réplication aussi. Quel est le modèle de menace? La question ne dit pas, et il pourrait être dangereux de faire des hypothèses.
Jason Orendorff
1
Au lieu d'un codage en dur $iv_size = 16;, j'utiliserais: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))pour indiquer le lien entre la taille de iv à utiliser avec le chiffrement utilisé. Vous pouvez également développer un peu la nécessité (ou non) de pkcs7_pad()/ pkcs7_unpad(), ou simplement simplifier le message en vous en débarrassant et en utilisant "aes-256-ctr". Great post @ Ja͢ck
Patrick Allaert
24

Je pense que cela a déjà été répondu ... mais de toute façon, si vous voulez crypter / décrypter des données, vous ne pouvez pas utiliser SHA256

//Key
$key = 'SuperSecretKey';

//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);

//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);
romo
la source
7
Vous ne devriez pas non plus utiliser ECB, d'ailleurs.
Maarten Bodewes
7
Les clés doivent être des octets aléatoires, ou vous devez utiliser une fonction de dérivation de clé sécurisée.
Maarten Bodewes
4
MCRYPT_RIJNDAEL_256 n'est pas une fonction standardisée, vous devriez utiliser AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes
14

Réponse Contexte et explication

Pour comprendre cette question, vous devez d'abord comprendre ce qu'est SHA256. SHA256 est une fonction de hachage cryptographique . Une fonction de hachage cryptographique est une fonction unidirectionnelle, dont la sortie est sécurisée par cryptographie. Cela signifie qu'il est facile de calculer un hachage (équivalent au chiffrement des données), mais difficile d'obtenir l'entrée d'origine en utilisant le hachage (équivalent au déchiffrement des données). Étant donné que l'utilisation d'une fonction de hachage cryptographique signifie que le décryptage est impossible sur le plan informatique, vous ne pouvez donc pas effectuer de décryptage avec SHA256.

Ce que vous souhaitez utiliser est une fonction bidirectionnelle, mais plus spécifiquement, un chiffrement par bloc . Une fonction qui permet à la fois le cryptage et le décryptage des données. Les fonctions mcrypt_encryptet mcrypt_decryptpar défaut utilisent l'algorithme Blowfish. L'utilisation de mcrypt par PHP peut être trouvée dans ce manuel . Une liste de définitions de chiffrement pour sélectionner le chiffrement utilisé par mcrypt existe également. Un wiki sur Blowfish peut être trouvé sur Wikipedia . Un chiffrement par bloc crypte l'entrée en blocs de taille et de position connues avec une clé connue, de sorte que les données puissent être déchiffrées ultérieurement à l'aide de la clé. C'est ce que SHA256 ne peut pas vous fournir.

Code

$key = 'ThisIsTheCipherKey';

$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);
cytinus
la source
Vous ne devriez pas non plus utiliser ECB, d'ailleurs.
Maarten Bodewes
Les clés doivent être des octets aléatoires, ou vous devez utiliser une fonction de dérivation de clé sécurisée.
Maarten Bodewes
4
N'utilisez jamais le mode ECB. Il n'est pas sûr et la plupart du temps n'aide pas vraiment à crypter les données (plutôt que de simplement les encoder). Voir l' excellent article de Wikipédia sur le sujet pour plus d'informations.
Holger juste le
1
Il est préférable de ne pas utiliser mcrypt, c'est abandonware, n'a pas été mis à jour depuis des années et ne prend pas en charge le remplissage standard PKCS # 7 (née PKCS # 5), uniquement un remplissage nul non standard qui ne peut même pas être utilisé avec des données binaires . mcrypt avait de nombreux bogues en suspens datant de 2003 .. Au lieu de cela, envisagez d'utiliser defuse , il est maintenu et est correct.
zaph
9

Voici un exemple utilisant openssl_encrypt

//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);

//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;
Vivek
la source
2
Au lieu de mcrypt_create_iv(), j'utiliserais:, de openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))cette façon, la méthodologie fonctionne pour n'importe quelle valeur de $ encryptionMethod et utiliserait uniquement l'extension openssl.
Patrick Allaert
Le code ci-dessus renvoie falsepour openssl_decrypt(). Voir stackoverflow.com/q/41952509/1066234 Étant donné que les chiffrements par blocs tels que AES exigent que les données d'entrée soient un multiple exact de la taille de bloc (16 octets pour AES), un remplissage est nécessaire.
Kai Noack
6
     function my_simple_crypt( $string, $action = 'e' ) {
        // you may change these values to your own
        $secret_key = 'my_simple_secret_key';
        $secret_iv = 'my_simple_secret_iv';

        $output = false;
        $encrypt_method = "AES-256-CBC";
        $key = hash( 'sha256', $secret_key );
        $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

        if( $action == 'e' ) {
            $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
        }
        else if( $action == 'd' ){
            $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
        }

        return $output;
    }
gauravbhai daxini
la source
très simple ! Je l'utilise pour le cryptage-décryptage du segment d'url. Merci
Mahbub Tito
0

Il m'a fallu un certain temps pour comprendre comment ne pas en avoir falselors de l'utilisation openssl_decrypt()et faire fonctionner le chiffrement et le déchiffrement.

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

Si vous souhaitez transmettre la chaîne chiffrée via une URL, vous devez encoder l'URL de la chaîne:

    $encrypted = urlencode($encrypted);

Pour mieux comprendre ce qui se passe, lisez:

Pour générer des clés de 16 octets, vous pouvez utiliser:

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

Pour voir les messages d'erreur de openssl, vous pouvez utiliser: echo openssl_error_string();

J'espère que cela pourra aider.

Kai Noack
la source