web-dev-qa-db-fra.com

Comment chiffrer du texte avec un mot de passe en python?

Étonnamment difficile de trouver une réponse directe à cette question sur Google.

Je veux collecter un morceau de texte et un message d'un utilisateur tel que 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDchello world.

Ensuite, je veux pouvoir crypter/décrypter le message avec le texte d'une manière ou d'une autre pour pouvoir l'enregistrer dans ma base de données et ne pas m'inquiéter des données exposées si mon site Web est piraté, encrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', 'hello world')decrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', <encrypted_text>)

Existe-t-il un moyen simple de réaliser cela avec python et s'il vous plaît, quelqu'un peut-il me donner/me diriger vers un exemple?.

Peut-être un exemple de création de paires de clés publique/privée en utilisant une valeur de départ telle que '1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc'?

Merci d'avance :)

EDIT: Juste pour être clair, je cherche un moyen de chiffrer les données de mes utilisateurs d’une manière déterministe et non d’obscurcir le message.

Si cela signifie que je dois générer une paire de clés PGP/GPG pub ​​/ pri à la volée en utilisant le texte 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc comme valeur de départ, tout va bien, mais quelle est la méthode pour ce faire?

6
derrend

Voici comment le faire correctement en mode CBC, y compris le remplissage de PKCS # 7:

import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random

def encrypt(key, source, encode=True):
    key = SHA256.new(key).digest()  # use SHA-256 over our key to get a proper-sized AES key
    IV = Random.new().read(AES.block_size)  # generate IV
    encryptor = AES.new(key, AES.MODE_CBC, IV)
    padding = AES.block_size - len(source) % AES.block_size  # calculate needed padding
    source += bytes([padding]) * padding  # Python 2.x: source += chr(padding) * padding
    data = IV + encryptor.encrypt(source)  # store the IV at the beginning and encrypt
    return base64.b64encode(data).decode("latin-1") if encode else data

def decrypt(key, source, decode=True):
    if decode:
        source = base64.b64decode(source.encode("latin-1"))
    key = SHA256.new(key).digest()  # use SHA-256 over our key to get a proper-sized AES key
    IV = source[:AES.block_size]  # extract the IV from the beginning
    decryptor = AES.new(key, AES.MODE_CBC, IV)
    data = decryptor.decrypt(source[AES.block_size:])  # decrypt
    padding = data[-1]  # pick the padding value from the end; Python 2.x: ord(data[-1])
    if data[-padding:] != bytes([padding]) * padding:  # Python 2.x: chr(padding) * padding
        raise ValueError("Invalid padding...")
    return data[:-padding]  # remove the padding

Il est configuré pour fonctionner avec les données bytes. Par conséquent, si vous souhaitez chiffrer des chaînes ou utiliser des mots de passe de chaîne, assurez-vous de les encode() avec un codec approprié avant de les transmettre aux méthodes. Si vous laissez le paramètre encode à True, la sortie de encrypt() sera une chaîne codée en base64, et la source de decrypt() devrait également être une chaîne base64.

Maintenant, si vous le testez en tant que:

my_password = b"secret_AES_key_string_to_encrypt/decrypt_with"
my_data = b"input_string_to_encrypt/decrypt"

print("key:  {}".format(my_password))
print("data: {}".format(my_data))
encrypted = encrypt(my_password, my_data)
print("\nenc:  {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec:  {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))
print("\nSecond round....")
encrypted = encrypt(my_password, my_data)
print("\nenc:  {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec:  {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))

votre sortie serait semblable à:

key:  b'secret_AES_key_string_to_encrypt/decrypt_with'
data: b'input_string_to_encrypt/decrypt'

enc:  7roSO+P/4eYdyhCbZmraVfc305g5P8VhDBOUDGrXmHw8h5ISsS3aPTGfsTSqn9f5
dec:  b'input_string_to_encrypt/decrypt'

data match: True

Second round....

enc:  BQm8FeoPx1H+bztlZJYZH9foI+IKAorCXRsMjbiYQkqLWbGU3NU50OsR+L9Nuqm6
dec:  b'input_string_to_encrypt/decrypt'

data match: True

Prouver que la même clé et les mêmes données produisent toujours un cryptogramme différent à chaque fois.

Maintenant, c'est beaucoup mieux que la BCE mais ... si vous allez utiliser ça pour la communication - ne le faites pas! C’est plus pour expliquer comment il devrait être construit, ne pas vraiment être utilisé dans un environnement de production et surtout pas pour la communication car il lui manque un ingrédient crucial - l’authentification de message. N'hésitez pas à jouer avec, mais vous ne devriez pas lancer votre propre crypto, il existe des protocoles bien vérifiés qui vous aideront à éviter les pièges courants et que vous devriez utiliser.

16
zwer

Basé sur les réponses de zwer mais résout un petit bug lorsque la source est exactement un multiple de 16.

Code:

from builtins import bytes
import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random

def encrypt(string, password):
    """
    It returns an encrypted string which can be decrypted just by the 
    password.
    """
    key = password_to_key(password)
    IV = make_initialization_vector()
    encryptor = AES.new(key, AES.MODE_CBC, IV)

    # store the IV at the beginning and encrypt
    return IV + encryptor.encrypt(pad_string(string))

def decrypt(string, password):
    key = password_to_key(password)   

    # extract the IV from the beginning
    IV = string[:AES.block_size]  
    decryptor = AES.new(key, AES.MODE_CBC, IV)

    string = decryptor.decrypt(string[AES.block_size:])
    return unpad_string(string)

def password_to_key(password):
    """
    Use SHA-256 over our password to get a proper-sized AES key.
    This hashes our password into a 256 bit string. 
    """
    return SHA256.new(password).digest()

def make_initialization_vector():
    """
    An initialization vector (IV) is a fixed-size input to a cryptographic
    primitive that is typically required to be random or pseudorandom.
    Randomization is crucial for encryption schemes to achieve semantic 
    security, a property whereby repeated usage of the scheme under the 
    same key does not allow an attacker to infer relationships 
    between segments of the encrypted message.
    """
    return Random.new().read(AES.block_size)

def pad_string(string, chunk_size=AES.block_size):
    """
    Pad string the peculirarity that uses the first byte
    is used to store how much padding is applied
    """
    assert chunk_size  <= 256, 'We are using one byte to represent padding'
    to_pad = (chunk_size - (len(string) + 1)) % chunk_size
    return bytes([to_pad]) + string + bytes([0] * to_pad)
def unpad_string(string):
    to_pad = string[0]
    return string[1:-to_pad]

def encode(string):
    """
    Base64 encoding schemes are commonly used when there is a need to encode 
    binary data that needs be stored and transferred over media that are 
    designed to deal with textual data.
    This is to ensure that the data remains intact without 
    modification during transport.
    """
    return base64.b64encode(string).decode("latin-1")

def decode(string):
    return base64.b64decode(string.encode("latin-1"))

Tests:

def random_text(length):
    def Rand_lower():
        return chr(randint(ord('a'), ord('z')))
    string = ''.join([Rand_lower() for _ in range(length)])
    return bytes(string, encoding='utf-8')

def test_encoding():
    string = random_text(100)
    assert encode(string) != string
    assert decode(encode(string)) == string

def test_padding():
    assert len(pad_string(random_text(14))) == 16
    assert len(pad_string(random_text(15))) == 16
    assert len(pad_string(random_text(16))) == 32

def test_encryption():
    string = random_text(100)
    password = random_text(20)
    assert encrypt(string, password) != string
    assert decrypt(encrypt(string, password), password) == string
1
Ignacio Tartavull
  1. Si vous allez utiliser la base de données mentionnée pour autoriser les utilisateurs, vous devez utiliser hashes ou des résumés de message des mots de passe des utilisateurs, au lieu d’algorithmes de chiffrement à 2 voies, qui rendraient vos données difficiles à utiliser même en cas de fuite de la base de données.
  2. Vous ne pouvez pas utiliser la méthode ci-dessus pour protéger les données qui doivent être déchiffrées à un moment donné, mais vous pouvez quand même utiliser un moyen plus sûr que de simplement chiffrer les mots de passe des utilisateurs en utilisant une clé fixe (qui est la pire méthode). Jetez un coup d'œil à Feuille de triche pour le stockage du mot de passe de OWASP .

Comme vous l'avez écrit "Je veux être capable de chiffrer/déchiffrer le message", j'attache une source python simple (testée sous 2.7) pour encr/decr en utilisant Blowfish.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os
from Crypto.Cipher import Blowfish     # pip install pycrypto

BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]

def doEncrypt(phrase, key):
    c1  = Blowfish.new(key, Blowfish.MODE_ECB)
    return c1.encrypt(pad(phrase))

def doDecrypt(phrase, key):
    c1  = Blowfish.new(key, Blowfish.MODE_ECB)
    return unpad(c1.decrypt(phrase))

def testing123(phrase, key):
    encrypted = doEncrypt(phrase, key)
    decrypted = doDecrypt(encrypted, key)
    assert phrase == decrypted, "Blowfish ECB enc/dec verification failed"
    print ("Blowfish ECB enc/dec verified ok")
    print ('phrase/key(hex)/enc+dec: {}/{}/{}'.format(phrase, key.encode('hex'), decrypted))

if __name__== "__main__":
    phrase= 'Ala ma kota, a kot ma AIDS.'
    key= os.urandom(32)
    testing123(phrase, key)
0
internety

Vous pouvez le faire en utilisant deux des fonctions intégrées à la bibliothèque Python standard. Le premier est la fonction ord () , qui prend un caractère de chaîne unicode en tant que paramètre d'entrée unique et le convertit en son code unicode correspondant (un entier). Deux exemples simples d'utilisation de cette fonction sont fournis:

>>> ord('a')
    97

>>> ord('b')
    98

Ensuite, vous avez également la fonction inverse de ord (): chr () . Et comme vous pouvez l’imaginer, cela fonctionne parfaitement: il a un code Unicode en entrée (entier) et obtient le caractère Unicode correspondant (chaîne):

>>> chr(97)
    'a'

>>> chr(98)
    'b'

Ensuite, vous pouvez faire une simple inscription en ajoutant ou en soustrayant par un nombre entier arbitraire ... dans ce cas, le nombre 2:

REMARQUE: Faites attention de ne pas énoncer de très grandes valeurs, sinon vous obtiendrez un identifiant d'erreur qui vous permettra d'atteindre un nombre négatif, par exemple.

def encrypt(message):
    newS=''
    for car in message:
        newS=newS+chr(ord(car)+2)
    return newS


print(encrypt('hello world'))

Et en conséquence:

jgnnq"yqtnf

Vous pouvez maintenant copier et coller la même fonction et générer la fonction de déchiffrement. Dans ce cas, il faut évidemment soustraire par 2:

def decrypt(message):
    newS=''
    for car in message:
        newS=newS+chr(ord(car)-2)
    return newS


print(decrypt('jgnnq"yqtnf'))

Et le résultat sera à nouveau le message d'origine:

'hello world'

Ce serait un excellent moyen de chiffrer les messages destinés aux non-programmeurs. Cependant, toute personne ayant un minimum de connaissances en programmation peut écrire un programme qui modifie le nombre entier que nous avons utilisé jusqu'à ce que nous découvrions que nous venons d'ajouter (2) aux caractères unicode pour chiffrer le code ...

Pour éviter cela, je proposerais deux alternatives plus complexes.

1. Le premier est le plus simple: il consiste à appliquer une valeur de somme différente à la fonction chr en fonction de la position du caractère (par exemple, l'ajout de 2 à chaque code unicode lorsqu'il occupe une position paire chaîne et soustraction 3 quand est assis sur une position impaire).

2. Le second générera une sécurité maximale. Cela consistera à ajouter ou à soustraire chaque code unicode pour un numéro qui sera généré aléatoirement pour chaque caractère. Il faudra stocker un tableau de valeurs pour décrypter le message. Assurez-vous donc que ce tableau de valeurs n'est pas disponible pour les tiers.

Voilà une solution possible pour 1 .:

def encryptHard(message):
newS=''
for i in range(len(message)):
  if i%2==0:
    newS=newS+chr(ord(message[i])+2)
  else:
    newS=newS+chr(ord(message[i])-3)
return newS


print(encryptHard('hello world'))

Et le résultat serait:

jbniqyltif

Avec les informations fournies par les présentes, le script de déchiffrement est évident, je ne vais donc pas vous déranger pour faire face, modifier et modifier deux valeurs.

Enfin, passons à une analyse approfondie de la deuxième alternative plus complexe. Avec celui-ci, nous pouvons dire que l'inscription sera presque indéfendable. L'idée est de faire varier la valeur ajoutée ou soustraite à chaque code unicode par un nombre aléatoire compris entre 0 et 255 (il s'agit de la plage de nombres que la fonction chr () admet, n'essayez donc pas de jouer avec d'autres nombres définitivement avoir une erreur).

Dans ce cas, ma proposition randomise également l’opération (somme ou soustraction) et évite que le nombre final soit un 0 (c’est-à-dire que nous aurions un caractère original). Enfin, il retourne également une liste des nombres auxquels il a été soustrait, ce dont vous aurez besoin pour déchiffrer le message.

Les chances que vous receviez le même message crypté si vous appelez deux fois cette fonction en utilisant le même message de longueur n sont assez proches de 255 ^ n ... Alors ne vous inquiétez pas (je dis un peu , car l’algorithme créé générerait en réalité davantage de valeurs répétées sur la plage de valeurs inférieure ou supérieure, par exemple, au cas où les caractères les plus fréquents ne seraient pas centrés dans cet ensemble de caracrer de distribution unicode (de 0 à 255), qui Cependant, le programme, bien que pas parfait, fonctionne parfaitement et protège les informations. 

import random as r
def encryptSuperHard(message):
  newS=''
  l_trans=[]
  for car in message:
    code=ord(car)
    add_subtract=r.choice([True,False])
    if add_subtract:
      transpose=r.randint(0,code-1)
      newS=newS+chr(code-transpose)
      l_trans=l_trans+[-transpose]
    else:
      transpose=r.randint(code+1,255)
      newS=newS+chr(code+transpose)
      l_trans=l_trans+[transpose]
  return newS, l_trans

print(encryptSuperHard('hello world'))

Dans ce cas, le script de chiffrement aléatoire que j'ai créé a renvoyé ces deux valeurs, où la première valeur est le message chiffré et la seconde valeur qui a "transposé" chaque caractère dans l'ordre d'apparition.

('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16,     226, -40, -55, -64, 124])

Le déchiffrement, dans ce cas, devrait prendre le message chiffré et la liste et procéder comme suit:

def decryptSuperHard(encriptedS,l):
  newS=''
  for i in range(len(l)):
    newS=newS+chr(ord(encriptedS[i])-l[i])
  return newS

print(decryptSuperHard('A0ŤłY\x10řG;,à', [-39,-53,248,214,-22,-16,226,-40,-55,-64,124]))

Et le résultat remonte à:

bonjour le monde

print(deccryptSuperHard('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16,     226, -40, -55, -64, 124])
0
americansanti