Python'da yerleşik şifreleme şemaları yoktur, hayır. Şifrelenmiş veri depolamayı da ciddiye almalısınız; Bir geliştiricinin güvensiz olduğunu anladığı önemsiz şifreleme şemaları ve bir oyuncak şeması, daha az deneyimli bir geliştirici tarafından güvenli bir şema sanılabilir. Şifrelerseniz, doğru şekilde şifreleyin.
Bununla birlikte, uygun bir şifreleme şeması uygulamak için çok fazla iş yapmanıza gerek yoktur. Her şeyden önce, şifreleme tekerleğini yeniden icat etmeyin , bunu sizin için halletmek için güvenilir bir şifreleme kitaplığı kullanın. Python 3 için bu güvenilir kitaplıkcryptography
.
Ayrıca, şifreleme ve şifre çözmenin baytlar için de geçerli olmasını tavsiye ederim ; metin mesajlarını önce bayt olarak kodlayın; stringvalue.encode()
UTF8'e kodlar, kullanılarak tekrar kolayca geri alınabilir bytesvalue.decode()
.
Son olarak, şifreleme ve şifre çözme sırasında şifrelerden değil anahtarlardan bahsediyoruz . Bir anahtar, insanlar tarafından akılda kalıcı olmamalıdır, gizli bir yerde sakladığınız ancak makine tarafından okunabilen bir şeydir, oysa bir şifre genellikle insan tarafından okunabilir ve hafızaya alınabilir. Sen edebilirsiniz Biraz dikkatli bir parola anahtarı türetmek.
Ancak bir kümede insan dikkati olmadan çalışan bir web uygulaması veya işlemin onu çalıştırmaya devam etmesi için bir anahtar kullanmak istersiniz. Parolalar, yalnızca bir son kullanıcının belirli bilgilere erişmesi gerektiğinde kullanılır. O zaman bile, genellikle uygulamayı bir parola ile güvence altına alırsınız ve ardından, belki de kullanıcı hesabına eklenmiş bir anahtar kullanarak şifrelenmiş bilgileri değiş tokuş edersiniz.
Simetrik anahtar şifreleme
Fernet - AES CBC + HMAC, şiddetle tavsiye edilir
cryptography
Kütüphane içerir Fernet tarifi , şifreleme kullanmak için bir en iyi uygulamaları tarifi. Fernet, çok çeşitli programlama dillerinde hazır uygulamalara sahip açık bir standarttır ve mesajın kurcalanmasını önlemek için sürüm bilgisi, bir zaman damgası ve bir HMAC imzası ile sizin için AES CBC şifrelemesini paketler.
Fernet, mesajları şifrelemeyi ve şifresini çözmeyi ve sizi güvende tutmayı çok kolaylaştırır . Verileri bir sır ile şifrelemek için ideal bir yöntemdir.
Fernet.generate_key()
Güvenli bir anahtar oluşturmak için kullanmanızı tavsiye ederim . Bir parola da kullanabilirsiniz (sonraki bölüm), ancak tam 32 baytlık bir gizli anahtar (şifrelemek için 16 bayt, artı imza için 16 bayt) aklınıza gelebilecek çoğu paroladan daha güvenli olacaktır.
Fernet'in ürettiği anahtar, bytes
URL'si ve dosya güvenli base64 karakterleri olan bir nesnedir, dolayısıyla yazdırılabilir:
from cryptography.fernet import Fernet
key = Fernet.generate_key() # store in a secure location
print("Key:", key.decode())
Şifrelemek veya şifresini mesajlar için, oluşturma Fernet()
verilen anahtar ile örnek ve çağrı Fernet.encrypt()
veya Fernet.decrypt()
şifrelemek için düz metin mesajı ve şifrelenmiş belirteci hem de bytes
nesneler.
encrypt()
ve decrypt()
işlevler şöyle görünür:
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'
Parola ile Fernet - paroladan türetilen anahtar, güvenliği biraz zayıflatır
Güçlü bir anahtar türetme yöntemi kullanmanız koşuluyla, gizli anahtar yerine parola kullanabilirsiniz . Ardından, tuz ve HMAC yineleme sayısını mesaja dahil etmeniz gerekir, böylece şifrelenmiş değer, önce salt, count ve Fernet token'ı ayırmadan artık Fernet uyumlu değildir:
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'
Tuzun çıktıya dahil edilmesi, rasgele bir tuz değerinin kullanılmasını mümkün kılar ve bu da şifrelenmiş çıktının, şifrenin yeniden kullanılması veya mesaj tekrarından bağımsız olarak tamamen rasgele olmasını garanti eder. Yineleme sayımının dahil edilmesi, eski mesajların şifresini çözme yeteneğini kaybetmeden zamanla CPU performansı artışlarını ayarlayabilmenizi sağlar.
Benzer boyuttaki bir havuzdan uygun şekilde rastgele bir parola oluşturmanız koşuluyla, tek başına bir parola Fernet 32 bayt rasgele anahtar kadar güvenli olabilir . 32 bayt size 256 ^ 32 tuş sayısı verir, bu nedenle 74 karakterlik bir alfabe kullanıyorsanız (26 üst, 26 alt, 10 basamak ve 12 olası simge), şifreniz en az math.ceil(math.log(256 ** 32, 74))
== 42 karakter uzunluğunda olmalıdır. Ancak, iyi seçilmiş çok sayıda HMAC yinelemesi , entropi eksikliğini bir şekilde azaltabilir çünkü bu, bir saldırganın içeri girmeye zorla girmesini çok daha pahalı hale getirir.
Daha kısa ama yine de makul ölçüde güvenli bir şifre seçmenin bu düzeni bozmayacağını bilin, sadece bir kaba kuvvet saldırganının aramak zorunda kalacağı olası değerlerin sayısını azaltır; güvenlik gereksinimleriniz için yeterince güçlü bir şifre seçtiğinizden emin olun .
Alternatifler
engellemeyecek
Bir alternatif şifrelememektir . Vignere diyelim ki, sadece düşük güvenlikli bir şifre ya da ev yapımı bir uygulama kullanmaktan çekinmeyin. Bu yaklaşımlarda güvenlik yoktur, ancak gelecekte kodunuzu koruma görevi verilen deneyimsiz bir geliştiriciye güvenlik yanılsamasını verebilir, ki bu hiç güvenlik olmamasından daha kötüdür.
İhtiyacınız olan tek şey belirsizlikse, verileri temel alın; URL güvenli gereksinimler için base64.urlsafe_b64encode()
işlev iyidir. Burada şifre kullanmayın, sadece kodlayın ve bitirdiniz. En fazla, biraz sıkıştırma ekleyin (gibi 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))
Bu döner b'Hello world!'
içine b'eNrzSM3JyVcozy_KSVEEAB0JBF4='
.
Yalnızca bütünlük
İhtiyacınız olan tek şey, güvenilmeyen bir istemciye gönderildikten ve geri alındıktan sonra verilerin değiştirilmemesi için güvenilebileceğinden emin olmanın bir yoluysa, verileri imzalamak istiyorsanız, bunun için hmac
kitaplığı SHA1 ile kullanabilirsiniz (yine de HMAC imzalama için güvenli kabul edilir ) veya daha iyisi:
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)
Verileri imzalamak için bunu kullanın, ardından imzayı verilerle ekleyin ve bunu müşteriye gönderin. Verileri geri aldığınızda, verileri ve imzayı bölün ve doğrulayın. Varsayılan algoritmayı SHA256 olarak ayarladım, bu nedenle 32 baytlık bir anahtara ihtiyacınız olacak:
key = secrets.token_bytes(32)
Tüm bunları çeşitli formatlarda serileştirme ve serileştirme çözme ile paketleyen itsdangerous
kitaplığa bakmak isteyebilirsiniz .
Şifreleme ve bütünlük sağlamak için AES-GCM şifrelemesini kullanma
Fernet, şifrelenmiş verilerin bütünlüğünü sağlamak için bir HMAC imzasıyla AEC-CBC üzerine inşa eder; Kötü niyetli bir saldırgan, şifreli metin imzalandığı için, hizmetinizin kötü girdilerle çevrelerde çalışmasını sağlamak için sisteminizin saçma verilerini besleyemez.
Galois / Sayaç modu blok şifreleme şifreli ve üreten etiketi nedenle aynı amaçlara hizmet etmek için kullanılabilir, aynı amaca hizmet etmek. Olumsuz yanı, Fernet'ten farklı olarak, diğer platformlarda yeniden kullanılacak, kullanımı kolay tek beden herkese uyan bir tarifin olmamasıdır. AES-GCM de doldurma kullanmaz, bu nedenle bu şifreleme şifreli metni giriş mesajının uzunluğuyla eşleşir (Fernet / AES-CBC, mesajları sabit uzunluktaki bloklara şifreler ve mesaj uzunluğunu bir şekilde gizler).
AES256-GCM, anahtar olarak normal 32 baytlık sırrı alır:
key = secrets.token_bytes(32)
sonra kullan
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()
Fernet'in desteklediği aynı yaşam süresi kullanım durumlarını desteklemek için bir zaman damgası ekledim.
Bu sayfadaki diğer yaklaşımlar, Python 3'te
AES CFB - CBC'ye benzer, ancak tamponlamaya gerek yoktur
Yanlış da olsa All Іѕ Vаиітy'nin izlediği yaklaşım budur . Bu cryptography
sürümdür, ancak IV'ü şifreli metne dahil ettiğime dikkat edin, küresel olarak depolanmamalıdır (bir IV'ü yeniden kullanmak anahtarın güvenliğini zayıflatır ve bunu bir modül global olarak depolamak, yeniden oluşturulacağı anlamına gelir. sonraki Python çağrısı, tüm şifreli metni şifrelenemez hale getirir):
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()
Bu, bir HMAC imzasının ek zırhından yoksundur ve zaman damgası yoktur; bunları kendiniz eklemeniz gerekir.
Yukarıdakiler ayrıca temel kriptografi yapı taşlarını yanlış bir şekilde birleştirmenin ne kadar kolay olduğunu göstermektedir; Tüm Іѕ Vаиітy'nin IV değerinin yanlış işlenmesi bir veri ihlaline veya tüm şifrelenmiş mesajların okunamaz olmasına neden olabilir çünkü IV kaybolur. Bunun yerine Fernet'i kullanmak sizi bu tür hatalardan korur.
AES ECB - güvenli değil
Daha önce AES ECB şifrelemesini uyguladıysanız ve bunu Python 3'te hala desteklemeniz gerekiyorsa, bunu yine cryptography
de yapabilirsiniz. Aynı uyarılar geçerlidir, ECB gerçek hayattaki uygulamalar için yeterince güvenli değildir . Bu cevabı Python 3 için yeniden uygulayarak, otomatik doldurma işlemlerini ekleyerek:
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()
Yine, bu HMAC imzasından yoksundur ve yine de ECB'yi kullanmamalısınız. Yukarıdakiler, yalnızca cryptography
, aslında kullanmamanız gerekenler de dahil olmak üzere, yaygın kriptografik yapı taşlarının üstesinden gelebileceğini göstermek içindir .