Python hat keine integrierten Verschlüsselungsschemata, nein. Sie sollten auch die verschlüsselte Datenspeicherung ernst nehmen. Triviale Verschlüsselungsschemata, die ein Entwickler als unsicher versteht, und ein Spielzeugschema können von einem weniger erfahrenen Entwickler durchaus als sicheres Schema verwechselt werden. Wenn Sie verschlüsseln, verschlüsseln Sie ordnungsgemäß.
Sie müssen jedoch nicht viel arbeiten, um ein ordnungsgemäßes Verschlüsselungsschema zu implementieren. Zunächst einmal nicht neu erfinden das Rad Kryptographie , verwenden Sie eine vertrauenswürdige Kryptographie - Bibliothek dies für Sie zu behandeln. Für Python 3 ist diese vertrauenswürdige Bibliothek cryptography
.
Ich empfehle auch, dass die Ver- und Entschlüsselung für Bytes gilt . zuerst Textnachrichten in Bytes codieren; stringvalue.encode()
codiert in UTF8 und kann mit einfach wieder zurückgesetzt werden bytesvalue.decode()
.
Last but not least sprechen wir beim Ver- und Entschlüsseln von Schlüsseln , nicht von Passwörtern. Ein Schlüssel sollte nicht menschlich einprägsam sein. Er wird an einem geheimen Ort gespeichert, ist jedoch maschinenlesbar, während ein Kennwort häufig von Menschen lesbar und gespeichert werden kann. Mit ein wenig Sorgfalt können Sie einen Schlüssel aus einem Passwort ableiten.
Für eine Webanwendung oder einen Prozess, der in einem Cluster ohne menschliche Aufmerksamkeit ausgeführt wird, um ihn weiterhin auszuführen, möchten Sie einen Schlüssel verwenden. Passwörter gelten, wenn nur ein Endbenutzer Zugriff auf die spezifischen Informationen benötigt. Selbst dann sichern Sie die Anwendung normalerweise mit einem Kennwort und tauschen dann verschlüsselte Informationen mit einem Schlüssel aus, der möglicherweise an das Benutzerkonto angehängt ist.
Symmetrische Schlüsselverschlüsselung
Fernet - AES CBC + HMAC, dringend empfohlen
Die cryptography
Bibliothek enthält das Fernet-Rezept , ein Best-Practice-Rezept für die Verwendung von Kryptografie. Fernet ist ein offener Standard mit fertigen Implementierungen in einer Vielzahl von Programmiersprachen und bietet Ihnen AES CBC-Verschlüsselung mit Versionsinformationen, einem Zeitstempel und einer HMAC-Signatur, um Manipulationen an Nachrichten zu verhindern.
Fernet macht es sehr einfach, Nachrichten und zu verschlüsseln und zu entschlüsseln Sie zu schützen. Es ist die ideale Methode, um Daten mit einem Geheimnis zu verschlüsseln.
Ich empfehle Ihnen Fernet.generate_key()
, einen sicheren Schlüssel zu generieren. Sie können auch ein Kennwort verwenden (nächster Abschnitt), aber ein vollständiger 32-Byte-Geheimschlüssel (16 Byte zum Verschlüsseln plus weitere 16 Byte für die Signatur) ist sicherer als die meisten Kennwörter, die Sie sich vorstellen können.
Der Schlüssel, den Fernet generiert, ist ein bytes
Objekt mit URL- und dateisicheren Base64-Zeichen, also druckbar:
from cryptography.fernet import Fernet
key = Fernet.generate_key() # store in a secure location
print("Key:", key.decode())
Um Nachrichten zu verschlüsseln oder zu entschlüsseln, erstellen Sie eine Fernet()
Instanz mit dem angegebenen Schlüssel und rufen Sie das Fernet.encrypt()
oder auf Fernet.decrypt()
. Sowohl die zu verschlüsselnde Klartextnachricht als auch das verschlüsselte Token sind bytes
Objekte.
encrypt()
und decrypt()
Funktionen würden aussehen wie:
from cryptography.fernet import Fernet
def encrypt(message: bytes, key: bytes) -> bytes:
return Fernet(key).encrypt(message)
def decrypt(token: bytes, key: bytes) -> bytes:
return Fernet(key).decrypt(token)
Demo:
>>> key = Fernet.generate_key()
>>> print(key.decode())
GZWKEhHGNopxRdOHS4H4IyKhLQ8lwnyU7vRLrM3sebY=
>>> message = 'John Doe'
>>> encrypt(message.encode(), key)
'gAAAAABciT3pFbbSihD_HZBZ8kqfAj94UhknamBuirZWKivWOukgKQ03qE2mcuvpuwCSuZ-X_Xkud0uWQLZ5e-aOwLC0Ccnepg=='
>>> token = _
>>> decrypt(token, key).decode()
'John Doe'
Fernet mit Passwort - Schlüssel vom Passwort abgeleitet, schwächt die Sicherheit etwas
Sie können ein Kennwort anstelle eines geheimen Schlüssels verwenden, vorausgesetzt, Sie verwenden eine Methode zur Ableitung eines starken Schlüssels . Sie müssen dann die Anzahl der Salt- und HMAC-Iterationen in die Nachricht aufnehmen, damit der verschlüsselte Wert nicht mehr mit Fernet kompatibel ist, ohne zuerst Salt, Count und Fernet-Token zu trennen:
import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
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
backend = default_backend()
iterations = 100_000
def _derive_key(password: bytes, salt: bytes, iterations: int = iterations) -> bytes:
"""Derive a secret key from a given password and salt"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(), length=32, salt=salt,
iterations=iterations, backend=backend)
return b64e(kdf.derive(password))
def password_encrypt(message: bytes, password: str, iterations: int = iterations) -> bytes:
salt = secrets.token_bytes(16)
key = _derive_key(password.encode(), salt, iterations)
return b64e(
b'%b%b%b' % (
salt,
iterations.to_bytes(4, 'big'),
b64d(Fernet(key).encrypt(message)),
)
)
def password_decrypt(token: bytes, password: str) -> bytes:
decoded = b64d(token)
salt, iter, token = decoded[:16], decoded[16:20], b64e(decoded[20:])
iterations = int.from_bytes(iter, 'big')
key = _derive_key(password.encode(), salt, iterations)
return Fernet(key).decrypt(token)
Demo:
>>> message = 'John Doe'
>>> password = 'mypass'
>>> password_encrypt(message.encode(), password)
b'9Ljs-w8IRM3XT1NDBbSBuQABhqCAAAAAAFyJdhiCPXms2vQHO7o81xZJn5r8_PAtro8Qpw48kdKrq4vt-551BCUbcErb_GyYRz8SVsu8hxTXvvKOn9QdewRGDfwx'
>>> token = _
>>> password_decrypt(token, password).decode()
'John Doe'
Das Einbeziehen des Salt in die Ausgabe ermöglicht die Verwendung eines zufälligen Salt-Werts, wodurch sichergestellt wird, dass die verschlüsselte Ausgabe unabhängig von der Wiederverwendung des Kennworts oder der Wiederholung von Nachrichten garantiert vollständig zufällig ist. Durch das Einbeziehen der Iterationszahl wird sichergestellt, dass Sie die CPU-Leistung im Laufe der Zeit anpassen können, ohne die Fähigkeit zu verlieren, ältere Nachrichten zu entschlüsseln.
Ein Passwort allein kann so sicher sein wie ein Fernet 32-Byte-Zufallsschlüssel, vorausgesetzt, Sie generieren ein richtig zufälliges Passwort aus einem Pool ähnlicher Größe. 32 Bytes ergeben 256 ^ 32 Schlüssel. Wenn Sie also ein Alphabet mit 74 Zeichen (26 obere, 26 untere, 10 Ziffern und 12 mögliche Symbole) verwenden, sollte Ihr Passwort mindestens math.ceil(math.log(256 ** 32, 74))
== 42 Zeichen lang sein. Eine gut ausgewählte größere Anzahl von HMAC-Iterationen kann jedoch den Mangel an Entropie etwas mildern, da dies es für einen Angreifer viel teurer macht, sich brutal hineinzuzwingen.
Wissen Sie nur, dass die Auswahl eines kürzeren, aber dennoch einigermaßen sicheren Passworts dieses Schema nicht lähmt, sondern nur die Anzahl der möglichen Werte verringert, die ein Brute-Force-Angreifer durchsuchen müsste. Stellen Sie sicher, dass Sie ein Kennwort auswählen, das Ihren Sicherheitsanforderungen entspricht .
Alternativen
Verschleierung
Eine Alternative ist nicht zu verschlüsseln . Seien Sie nicht versucht, nur eine Verschlüsselung mit geringer Sicherheit oder eine hausgemachte Implementierung von beispielsweise Vignere zu verwenden. Diese Ansätze bieten keine Sicherheit, können jedoch einem unerfahrenen Entwickler, der die Aufgabe hat, Ihren Code in Zukunft zu verwalten, die Illusion von Sicherheit geben, die schlimmer ist als gar keine Sicherheit.
Wenn Sie nur Dunkelheit benötigen, geben Sie einfach die Daten ein. Für URL-sichere Anforderungen ist die base64.urlsafe_b64encode()
Funktion in Ordnung. Verwenden Sie hier kein Passwort, codieren Sie einfach und Sie sind fertig. Fügen Sie höchstens eine Komprimierung hinzu (wie zlib
):
import zlib
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
def obscure(data: bytes) -> bytes:
return b64e(zlib.compress(data, 9))
def unobscure(obscured: bytes) -> bytes:
return zlib.decompress(b64d(obscured))
Dies wird b'Hello world!'
zu b'eNrzSM3JyVcozy_KSVEEAB0JBF4='
.
Nur Integrität
Wenn Sie nur sicherstellen müssen, dass die Daten unverändert bleiben, nachdem sie an einen nicht vertrauenswürdigen Client gesendet und zurück empfangen wurden, möchten Sie die Daten signieren. Sie können die hmac
Bibliothek hierfür mit SHA1 verwenden (noch) als sicher für die HMAC-Signatur angesehen ) oder besser:
import hmac
import hashlib
def sign(data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
assert len(key) >= algorithm().digest_size, (
"Key must be at least as long as the digest size of the "
"hashing algorithm"
)
return hmac.new(key, data, algorithm).digest()
def verify(signature: bytes, data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
expected = sign(data, key, algorithm)
return hmac.compare_digest(expected, signature)
Verwenden Sie diese Option, um Daten zu signieren, fügen Sie dann die Signatur den Daten hinzu und senden Sie diese an den Client. Wenn Sie die Daten zurückerhalten, teilen Sie Daten und Signatur auf und überprüfen Sie sie. Ich habe den Standardalgorithmus auf SHA256 gesetzt, daher benötigen Sie einen 32-Byte-Schlüssel:
key = secrets.token_bytes(32)
Vielleicht möchten Sie sich die itsdangerous
Bibliothek ansehen , die dies alles mit Serialisierung und De-Serialisierung in verschiedenen Formaten zusammenfasst.
Verwendung der AES-GCM-Verschlüsselung zur Bereitstellung von Verschlüsselung und Integrität
Fernet baut auf AEC-CBC mit einer HMAC-Signatur auf, um die Integrität der verschlüsselten Daten sicherzustellen. Ein böswilliger Angreifer kann die Unsinndaten Ihres Systems nicht füttern, um Ihren Dienst in Kreisen mit schlechten Eingaben zu betreiben, da der Chiffretext signiert ist.
Die Blockverschlüsselung im Galois / Counter-Modus erzeugt Chiffretext und ein Tag für denselben Zweck und kann daher für dieselben Zwecke verwendet werden. Der Nachteil ist, dass es im Gegensatz zu Fernet kein einfach zu verwendendes Einheitsrezept gibt, das auf anderen Plattformen wiederverwendet werden kann. AES-GCM verwendet auch kein Auffüllen, daher entspricht dieser Verschlüsselungs-Chiffretext der Länge der Eingabenachricht (während Fernet / AES-CBC Nachrichten in Blöcke fester Länge verschlüsselt, wodurch die Nachrichtenlänge etwas verdeckt wird).
AES256-GCM verwendet das übliche 32-Byte-Geheimnis als Schlüssel:
key = secrets.token_bytes(32)
dann benutze
import binascii, time
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidTag
backend = default_backend()
def aes_gcm_encrypt(message: bytes, key: bytes) -> bytes:
current_time = int(time.time()).to_bytes(8, 'big')
algorithm = algorithms.AES(key)
iv = secrets.token_bytes(algorithm.block_size // 8)
cipher = Cipher(algorithm, modes.GCM(iv), backend=backend)
encryptor = cipher.encryptor()
encryptor.authenticate_additional_data(current_time)
ciphertext = encryptor.update(message) + encryptor.finalize()
return b64e(current_time + iv + ciphertext + encryptor.tag)
def aes_gcm_decrypt(token: bytes, key: bytes, ttl=None) -> bytes:
algorithm = algorithms.AES(key)
try:
data = b64d(token)
except (TypeError, binascii.Error):
raise InvalidToken
timestamp, iv, tag = data[:8], data[8:algorithm.block_size // 8 + 8], data[-16:]
if ttl is not None:
current_time = int(time.time())
time_encrypted, = int.from_bytes(data[:8], 'big')
if time_encrypted + ttl < current_time or current_time + 60 < time_encrypted:
# too old or created well before our current time + 1 h to account for clock skew
raise InvalidToken
cipher = Cipher(algorithm, modes.GCM(iv, tag), backend=backend)
decryptor = cipher.decryptor()
decryptor.authenticate_additional_data(timestamp)
ciphertext = data[8 + len(iv):-16]
return decryptor.update(ciphertext) + decryptor.finalize()
Ich habe einen Zeitstempel eingefügt, um dieselben Time-to-Live-Anwendungsfälle zu unterstützen, die Fernet unterstützt.
Andere Ansätze auf dieser Seite in Python 3
AES CFB - wie CBC, jedoch ohne Pad
Dies ist der Ansatz, dem All І s Vаиітy folgt, wenn auch falsch. Dies ist die cryptography
Version, aber beachten Sie, dass ich die IV in den Chiffretext einbeziehe . Sie sollte nicht als global gespeichert werden (die Wiederverwendung einer IV schwächt die Sicherheit des Schlüssels, und das Speichern als globales Modul bedeutet, dass sie neu generiert wird der nächste Python-Aufruf, der den gesamten Chiffretext unverschlüsselbar macht):
import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
backend = default_backend()
def aes_cfb_encrypt(message, key):
algorithm = algorithms.AES(key)
iv = secrets.token_bytes(algorithm.block_size // 8)
cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
encryptor = cipher.encryptor()
ciphertext = encryptor.update(message) + encryptor.finalize()
return b64e(iv + ciphertext)
def aes_cfb_decrypt(ciphertext, key):
iv_ciphertext = b64d(ciphertext)
algorithm = algorithms.AES(key)
size = algorithm.block_size // 8
iv, encrypted = iv_ciphertext[:size], iv_ciphertext[size:]
cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
decryptor = cipher.decryptor()
return decryptor.update(encrypted) + decryptor.finalize()
Diesem fehlt die zusätzliche Panzerung einer HMAC-Signatur, und es gibt keinen Zeitstempel. Sie müssten diese selbst hinzufügen.
Das Obige zeigt auch, wie einfach es ist, grundlegende Kryptographie-Bausteine falsch zu kombinieren. Die falsche Behandlung des IV-Werts durch Vаиітy kann zu einer Datenverletzung führen oder dazu, dass alle verschlüsselten Nachrichten nicht lesbar sind, weil die IV verloren geht. Die Verwendung von Fernet schützt Sie stattdessen vor solchen Fehlern.
AES EZB - nicht sicher
Wenn Sie zuvor die AES-EZB-Verschlüsselung implementiert haben und diese in Python 3 weiterhin unterstützen müssen, können Sie dies auch weiterhin tun cryptography
. Die gleichen Einschränkungen gelten, EZB ist nicht sicher genug für reale Anwendungen . Implementieren Sie diese Antwort für Python 3 erneut und fügen Sie die automatische Behandlung des Auffüllens hinzu:
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
backend = default_backend()
def aes_ecb_encrypt(message, key):
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
encryptor = cipher.encryptor()
padder = padding.PKCS7(cipher.algorithm.block_size).padder()
padded = padder.update(msg_text.encode()) + padder.finalize()
return b64e(encryptor.update(padded) + encryptor.finalize())
def aes_ecb_decrypt(ciphertext, key):
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(cipher.algorithm.block_size).unpadder()
padded = decryptor.update(b64d(ciphertext)) + decryptor.finalize()
return unpadder.update(padded) + unpadder.finalize()
Auch hier fehlt die HMAC-Signatur, und Sie sollten die EZB sowieso nicht verwenden. Das Obige dient lediglich der Veranschaulichung, dass cryptography
die gängigen kryptografischen Bausteine verarbeitet werden können, auch diejenigen, die Sie eigentlich nicht verwenden sollten.