182 votes

Comment encoder une chaîne de caractères en fonction d'un mot de passe ?

Python dispose-t-il d'un moyen simple et intégré d'encoder/décoder des chaînes de caractères à l'aide d'un mot de passe ?

Algo así:

>>> encode('John Doe', password = 'mypass')
'sjkl28cn2sx0'
>>> decode('sjkl28cn2sx0', password = 'mypass')
'John Doe'

Ainsi, la chaîne "John Doe" est cryptée sous la forme "sjkl28cn2sx0". Pour obtenir la chaîne originale, je dois la "déverrouiller" avec la clé "mypass", qui est un mot de passe dans mon code source. J'aimerais que ce soit la façon dont je peux crypter/décrypter un document Word avec un mot de passe.

J'aimerais utiliser ces chaînes cryptées comme paramètres d'URL. Mon objectif est l'obscurcissement, pas une sécurité forte ; rien de critique n'est codé. Je sais que je pourrais utiliser une table de base de données pour stocker les clés et les valeurs, mais j'essaie d'être minimaliste.

7voto

HCLivess Points 402

Si vous voulez être sûr, vous pouvez utiliser le Fernet, qui est cryptographiquement fiable. Vous pouvez utiliser un "sel" statique si vous ne voulez pas le stocker séparément - vous ne perdrez que la prévention des attaques par dictionnaire et arc-en-ciel. Je l'ai choisi parce que je peux choisir des mots de passe longs ou courts, ce qui n'est pas si facile avec AES.

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64

#set password
password = "mysecretpassword"
#set message
message = "secretmessage"

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt="staticsalt", iterations=100000, backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)

#encrypt
encrypted = f.encrypt(message)
print encrypted

#decrypt
decrypted = f.decrypt(encrypted)
print decrypted

Si c'est trop compliqué, quelqu'un a suggéré simplecrypt

from simplecrypt import encrypt, decrypt
ciphertext = encrypt('password', plaintext)
plaintext = decrypt('password', ciphertext)

6voto

Basj Points 776

Je donnerai 4 solutions :

1) Utilisation du cryptage Fernet avec cryptography bibliothèque

Voici une solution utilisant le paquet cryptography que vous pouvez installer comme d'habitude avec pip install cryptography :

import base64
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

def cipherFernet(password):
    key = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=b'abcd', iterations=1000, backend=default_backend()).derive(password)
    return Fernet(base64.urlsafe_b64encode(key))

def encrypt1(plaintext, password):
    return cipherFernet(password).encrypt(plaintext)

def decrypt1(ciphertext, password):
    return cipherFernet(password).decrypt(ciphertext)

# Example:

print(encrypt1(b'John Doe', b'mypass'))  
# b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg=='
print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'mypass')) 
# b'John Doe'
try:  # test with a wrong password
    print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'wrongpass')) 
except InvalidToken:
    print('Wrong password')

Vous pouvez l'adapter avec votre propre sel, votre nombre d'itérations, etc. Ce code n'est pas très éloigné de la réponse de @HCLivess mais le but est ici d'avoir un code prêt à l'emploi. encrypt et decrypt fonctions. La source : https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet .

Note : utiliser .encode() et .decode() partout si vous voulez des chaînes 'John Doe' au lieu d'octets comme b'John Doe' .


2) Cryptage AES simple avec Crypto bibliothèque

Cela fonctionne avec Python 3 :

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

def cipherAES(password, iv):
    key = SHA256.new(password).digest()
    return AES.new(key, AES.MODE_CFB, iv)

def encrypt2(plaintext, password):
    iv = Random.new().read(AES.block_size)
    return base64.b64encode(iv + cipherAES(password, iv).encrypt(plaintext))

def decrypt2(ciphertext, password):
    d = base64.b64decode(ciphertext)
    iv, ciphertext = d[:AES.block_size], d[AES.block_size:]
    return cipherAES(password, iv).decrypt(ciphertext)

# Example:    

print(encrypt2(b'John Doe', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'wrongpass'))  # wrong password: no error, but garbled output

Remarque : vous pouvez retirer base64.b64encode et .b64decode si vous ne voulez pas d'une sortie lisible en texte et/ou si vous voulez de toute façon enregistrer le texte chiffré sur le disque sous la forme d'un fichier binaire.


3) AES utilisant une meilleure fonction de dérivation de la clé du mot de passe et la possibilité de tester si un "mauvais mot de passe a été saisi", avec Crypto bibliothèque

La solution 2) avec AES "CFB mode" est correcte, mais présente deux inconvénients : le fait que SHA256(password) peut être facilement forcé à l'aide d'une table de recherche, et qu'il n'existe aucun moyen de vérifier si un mot de passe erroné a été saisi. Ce problème est résolu ici par l'utilisation d'AES en "mode GCM", comme indiqué dans le document suivant AES : comment détecter qu'un mauvais mot de passe a été saisi ? et Cette méthode qui consiste à dire "Le mot de passe que vous avez saisi est erroné" est-elle sûre ? :

import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES

def cipherAES_GCM(pwd, nonce):
    key = Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100000)
    return Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_GCM, nonce=nonce, mac_len=16)

def encrypt3(plaintext, password):
    nonce = Crypto.Random.new().read(16)
    return nonce + b''.join(cipherAES_GCM(password, nonce).encrypt_and_digest(plaintext))  # you case base64.b64encode it if needed

def decrypt3(ciphertext, password):
    nonce, ciphertext, tag = ciphertext[:16], ciphertext[16:len(ciphertext)-16], ciphertext[-16:]
    return cipherAES_GCM(password, nonce).decrypt_and_verify(ciphertext, tag)

# Example:

print(encrypt3(b'John Doe', b'mypass'))
print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'mypass'))
try:
    print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'wrongpass'))
except ValueError:
    print("Wrong password")

4) Utilisation de RC4 (aucune bibliothèque n'est nécessaire)

Adapté de https://github.com/bozhu/RC4-Python/blob/master/rc4.py .

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        yield S[(S[i] + S[j]) % 256]

def encryptRC4(plaintext, key, hexformat=False):
    key, plaintext = bytearray(key), bytearray(plaintext)  # necessary for py2, not for py3
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    keystream = PRGA(S)
    return b''.join(b"%02X" % (c ^ next(keystream)) for c in plaintext) if hexformat else bytearray(c ^ next(keystream) for c in plaintext)

print(encryptRC4(b'John Doe', b'mypass'))                           # b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a'
print(encryptRC4(b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a', b'mypass'))   # b'John Doe'

(périmé depuis les dernières éditions, mais conservé pour référence future) : J'ai eu des problèmes en utilisant Windows + Python 3.6 + toutes les réponses impliquant pycrypto (pas en mesure de pip install pycrypto sous Windows) ou pycryptodome (les réponses ici avec from Crypto.Cipher import XOR a échoué parce que XOR n'est pas pris en charge par cette pycrypto et les solutions utilisant ... AES a également échoué avec TypeError: Object type <class 'str'> cannot be passed to C code ). En outre, la bibliothèque simple-crypt a pycrypto en tant que dépendance, ce n'est donc pas une option.

3voto

Alan Points 21367

Vous pouvez utiliser AES pour crypter votre chaîne avec un mot de passe. Cependant, vous devrez choisir un mot de passe suffisamment fort pour que les gens ne puissent pas facilement le deviner (désolé, je ne peux pas m'en empêcher, je suis un fanatique de la sécurité).

AES est puissant avec une bonne taille de clé, mais il est également facile à utiliser avec PyCrypto.

3voto

Pratik Deoghare Points 9766

Cela fonctionne, mais la longueur du mot de passe doit être exactement 8 . Cette opération est simple et nécessite pyDes .

from pyDes import *

def encode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.encrypt(data)
    return d

def decode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.decrypt(data)
    return d

x = encode('John Doe', 'mypass12')
y = decode(x,'mypass12')

print x
print y

SORTIE :

³.\Þ\åS¾+æÅ`;Ê
John Doe

3voto

towc Points 1401

CETTE RÉPONSE EST TRÈS MAUVAISE POUR LA SÉCURITÉ. NE PAS UTILISER POUR TOUT CE QUI EST SENSIBLE

Les personnes qui sont venues ici (et le bountier) semblaient rechercher des phrases simples avec peu de configuration, ce que les autres réponses n'offrent pas. Je propose donc base64.

Gardez à l'esprit qu'il ne s'agit là que d'un obscurcissement de base, et qu'il est en PAS DU TOUT OK POUR LA SÉCURITÉ mais voici quelques répliques :

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data, key): # the key DOES NOT make this safe
    return urlsafe_b64encode(bytes(key+data, 'utf-8'))

def decode(enc, key):
    return urlsafe_b64decode(enc)[len(key):].decode('utf-8')

print(encode('hi', 'there')) # b'dGhlcmVoaQ=='
print(decode(encode('hi', 'there'), 'there')) # 'hi'

Quelques points à noter :

  • vous devrez vous occuper vous-même de l'encodage et du décodage de l'octet à la chaîne, en fonction de vos E/S. Regardez du côté de bytes() et bytes::decode()
  • base64 est facilement reconnaissable par les types de caractères utilisés, et se termine souvent par = caractères. Les gens comme moi doivent absolument les décoder dans la console javascript lorsqu'ils les voient sur les sites web. C'est aussi simple que btoa(string) (js)
  • l'ordre est clé+données, comme en b64, les caractères qui apparaissent à la fin dépendent de ceux qui se trouvent au début (en raison des décalages d'octets). Wikipedia (en anglais) contient de bonnes explications). Dans ce scénario, le début de la chaîne codée sera le même pour tout ce qui est codé avec cette clé. L'avantage est que les données seront plus obscures. Dans l'autre sens, la partie des données sera exactement la même pour tout le monde, quelle que soit la clé.

Maintenant, si ce que vous voulez n'a même pas besoin d'une clé quelconque, mais juste d'un obscurcissement, vous pouvez encore une fois utiliser base64, sans aucune sorte de clé :

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data):
    return urlsafe_b64encode(bytes(data, 'utf-8'))

def decode(enc):
    return urlsafe_b64decode(enc).decode()

print(encode('hi')) # b'aGk='
print(decode(encode('hi'))) # 'hi'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X