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.