Python ไม่มีรูปแบบการเข้ารหัสในตัวไม่มี คุณควรใช้การจัดเก็บข้อมูลที่เข้ารหัสอย่างจริงจัง แผนการเข้ารหัสเล็กน้อยที่นักพัฒนาคนหนึ่งเข้าใจว่าไม่ปลอดภัยและโครงการของเล่นอาจถูกเข้าใจผิดว่าเป็นโครงการที่ปลอดภัยโดยนักพัฒนาที่มีประสบการณ์น้อย หากคุณเข้ารหัสให้เข้ารหัสอย่างถูกต้อง
อย่างไรก็ตามคุณไม่จำเป็นต้องทำงานมากเพื่อใช้รูปแบบการเข้ารหัสที่เหมาะสม ก่อนอื่นอย่าคิดค้นวงล้อการเข้ารหัสขึ้นมาใหม่ใช้ไลบรารีการเข้ารหัสที่เชื่อถือได้เพื่อจัดการสิ่งนี้ให้คุณ สำหรับงูหลาม 3 cryptography
ที่ห้องสมุดที่เชื่อถือได้คือ
ผมขอแนะนำว่าการเข้ารหัสและการถอดรหัสนำไปใช้กับไบต์ ; เข้ารหัสข้อความเป็นไบต์ก่อน stringvalue.encode()
เข้ารหัสเป็น UTF8 ย้อนกลับได้อย่างง่ายดายอีกครั้งโดยใช้bytesvalue.decode()
ไฟล์.
สุดท้าย แต่ไม่ท้ายสุดเมื่อเข้ารหัสและถอดรหัสเราพูดถึงคีย์ไม่ใช่รหัสผ่าน คีย์ไม่ควรเป็นที่จดจำของมนุษย์เป็นสิ่งที่คุณเก็บไว้ในสถานที่ลับ แต่สามารถอ่านได้โดยเครื่องในขณะที่รหัสผ่านมักจะสามารถอ่านได้โดยมนุษย์และจดจำได้ คุณสามารถรับรหัสจากรหัสผ่านได้โดยใช้ความระมัดระวังเล็กน้อย
แต่สำหรับเว็บแอปพลิเคชันหรือกระบวนการที่ทำงานในคลัสเตอร์โดยไม่ได้รับความสนใจจากมนุษย์ในการเรียกใช้งานต่อไปคุณต้องใช้คีย์ รหัสผ่านมีไว้สำหรับเมื่อผู้ใช้ปลายทางเท่านั้นที่ต้องการเข้าถึงข้อมูลเฉพาะ ถึงอย่างนั้นคุณมักจะรักษาความปลอดภัยแอปพลิเคชันด้วยรหัสผ่านจากนั้นแลกเปลี่ยนข้อมูลที่เข้ารหัสโดยใช้คีย์ซึ่งอาจเป็นรหัสที่แนบมากับบัญชีผู้ใช้
การเข้ารหัสคีย์สมมาตร
Fernet - AES CBC + HMAC ขอแนะนำ
cryptography
ห้องสมุดรวมถึงสูตร Fernetสูตรปฏิบัติที่ดีที่สุดสำหรับการใช้การเข้ารหัส Fernet เป็นมาตรฐานแบบเปิดพร้อมการใช้งานที่พร้อมใช้งานในภาษาการเขียนโปรแกรมที่หลากหลายและบรรจุการเข้ารหัส AES CBC สำหรับคุณด้วยข้อมูลเวอร์ชันการประทับเวลาและลายเซ็น HMAC เพื่อป้องกันการปลอมแปลงข้อความ
Fernet ทำให้การเข้ารหัสและถอดรหัสข้อความเป็นเรื่องง่ายมากและทำให้คุณปลอดภัย เป็นวิธีที่เหมาะสำหรับการเข้ารหัสข้อมูลด้วยความลับ
ฉันขอแนะนำให้คุณใช้Fernet.generate_key()
เพื่อสร้างคีย์ที่ปลอดภัย คุณสามารถใช้รหัสผ่านได้เช่นกัน (ส่วนถัดไป) แต่รหัสลับแบบเต็ม 32 ไบต์ (เข้ารหัส 16 ไบต์และอีก 16 สำหรับลายเซ็น) จะปลอดภัยกว่ารหัสผ่านส่วนใหญ่ที่คุณคิด
คีย์ที่ Fernet สร้างคือbytes
อ็อบเจ็กต์ที่มี URL และ file safe base64 จึงพิมพ์ได้:
from cryptography.fernet import Fernet
key = Fernet.generate_key() # store in a secure location
print("Key:", key.decode())
ในการเข้ารหัสหรือถอดรหัสข้อความให้สร้างFernet()
อินสแตนซ์ด้วยคีย์ที่กำหนดและเรียกFernet.encrypt()
หรือFernet.decrypt()
ทั้งข้อความธรรมดาเพื่อเข้ารหัสและโทเค็นที่เข้ารหัสเป็นbytes
วัตถุ
encrypt()
และdecrypt()
ฟังก์ชั่นจะมีลักษณะดังนี้:
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)
การสาธิต:
>>> 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 ด้วยรหัสผ่าน - คีย์ที่ได้มาจากรหัสผ่านทำให้ความปลอดภัยลดลงเล็กน้อย
คุณสามารถใช้รหัสผ่านแทนคีย์ลับได้หากคุณใช้วิธีการหาคีย์ที่คาดเดายาก จากนั้นคุณต้องรวมเกลือและจำนวนการวนซ้ำ HMAC ในข้อความดังนั้นค่าที่เข้ารหัสจึงไม่สามารถใช้งานร่วมกับ Fernet ได้อีกต่อไปโดยไม่ต้องแยกเกลือนับและโทเค็น Fernet ก่อน:
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)
การสาธิต:
>>> message = 'John Doe'
>>> password = 'mypass'
>>> password_encrypt(message.encode(), password)
b'9Ljs-w8IRM3XT1NDBbSBuQABhqCAAAAAAFyJdhiCPXms2vQHO7o81xZJn5r8_PAtro8Qpw48kdKrq4vt-551BCUbcErb_GyYRz8SVsu8hxTXvvKOn9QdewRGDfwx'
>>> token = _
>>> password_decrypt(token, password).decode()
'John Doe'
การรวมเกลือในเอาต์พุตทำให้สามารถใช้ค่าเกลือแบบสุ่มซึ่งจะทำให้มั่นใจได้ว่าเอาต์พุตที่เข้ารหัสจะได้รับการสุ่มอย่างสมบูรณ์โดยไม่คำนึงถึงการใช้รหัสผ่านซ้ำหรือการทำซ้ำข้อความ การรวมการนับการวนซ้ำช่วยให้คุณสามารถปรับเปลี่ยนเพื่อให้ประสิทธิภาพของ CPU เพิ่มขึ้นเมื่อเวลาผ่านไปโดยไม่สูญเสียความสามารถในการถอดรหัสข้อความเก่า
รหัสผ่านเพียงอย่างเดียวอาจปลอดภัยเท่ากับคีย์สุ่ม Fernet 32 ไบต์หากคุณสร้างรหัสผ่านแบบสุ่มอย่างถูกต้องจากพูลขนาดใกล้เคียงกัน 32 ไบต์ให้จำนวนคีย์ 256 ^ 32 ดังนั้นหากคุณใช้ตัวอักษร 74 ตัวอักษร (26 ตัวบน 26 ตัวล่าง 10 หลักและ 12 สัญลักษณ์ที่เป็นไปได้) รหัสผ่านของคุณควรมีความยาวอย่างน้อยmath.ceil(math.log(256 ** 32, 74))
== 42 อักขระ อย่างไรก็ตามการทำซ้ำ HMAC จำนวนมากที่ได้รับการคัดเลือกมาเป็นอย่างดีสามารถบรรเทาการขาดเอนโทรปีได้บ้างเนื่องจากทำให้ผู้โจมตีมีราคาแพงกว่ามากสำหรับผู้โจมตีในการบังคับให้เข้ามา
เพียงแค่รู้ว่าการเลือกรหัสผ่านที่สั้นกว่า แต่ยังคงมีความปลอดภัยพอสมควรจะไม่ทำให้โครงร่างนี้พิการเพียงแค่ลดจำนวนค่าที่เป็นไปได้ที่ผู้โจมตีที่ดุร้ายจะต้องค้นหา ให้แน่ใจว่าจะเลือกรหัสผ่านที่แข็งแกร่งพอสำหรับความต้องการความปลอดภัยของคุณ
ทางเลือก
การซ่อนเร้น
ทางเลือกคือไม่เข้ารหัส อย่าถูกล่อลวงให้ใช้เพียงแค่การเข้ารหัสที่มีความปลอดภัยต่ำหรือการใช้งานแบบโฮมสปันเช่น Vignere ไม่มีความปลอดภัยในแนวทางเหล่านี้ แต่อาจให้นักพัฒนาที่ไม่มีประสบการณ์ซึ่งได้รับมอบหมายงานในการรักษารหัสของคุณในอนาคตภาพลวงตาของความปลอดภัยซึ่งแย่กว่าไม่มีความปลอดภัยเลย
หากสิ่งที่คุณต้องการคือความสับสนเพียงแค่ base64 ข้อมูล สำหรับข้อกำหนด URL ปลอดภัยbase64.urlsafe_b64encode()
ฟังก์ชันนี้ใช้ได้ อย่าใช้รหัสผ่านที่นี่เพียงแค่เข้ารหัสและคุณก็เสร็จแล้ว อย่างมากให้เพิ่มการบีบอัด (เช่น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))
ผลัดกันนี้เข้าไปb'Hello world!'
b'eNrzSM3JyVcozy_KSVEEAB0JBF4='
ความซื่อสัตย์เท่านั้น
หากสิ่งที่คุณต้องการคือวิธีตรวจสอบให้แน่ใจว่าข้อมูลสามารถเชื่อถือได้ว่าจะไม่มีการเปลี่ยนแปลงหลังจากที่ส่งไปยังไคลเอนต์ที่ไม่น่าเชื่อถือและได้รับกลับมาแล้วคุณต้องการลงนามข้อมูลคุณสามารถใช้hmac
ไลบรารีสำหรับสิ่งนี้กับ SHA1 (ยังถือว่าปลอดภัยสำหรับการลงนาม HMAC ) หรือดีกว่า:
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)
ใช้สิ่งนี้เพื่อลงนามในข้อมูลจากนั้นแนบลายเซ็นพร้อมข้อมูลและส่งไปยังไคลเอนต์ เมื่อคุณได้รับข้อมูลคืนให้แยกข้อมูลและลายเซ็นและตรวจสอบ ฉันได้ตั้งค่าอัลกอริทึมเริ่มต้นเป็น SHA256 ดังนั้นคุณจะต้องมีคีย์ 32 ไบต์:
key = secrets.token_bytes(32)
คุณอาจต้องการดูที่itsdangerous
ไลบรารีซึ่งบรรจุสิ่งเหล่านี้ทั้งหมดด้วยการทำให้เป็นอนุกรมและการแยกอนุกรมในรูปแบบต่างๆ
การใช้การเข้ารหัส AES-GCM เพื่อให้การเข้ารหัสและความสมบูรณ์
Fernet สร้างบน AEC-CBC พร้อมลายเซ็น HMAC เพื่อให้แน่ใจว่าข้อมูลที่เข้ารหัสมีความสมบูรณ์ ผู้โจมตีที่เป็นอันตรายไม่สามารถป้อนข้อมูลที่ไร้สาระในระบบของคุณเพื่อให้บริการของคุณไม่ว่างทำงานในแวดวงที่มีการป้อนข้อมูลที่ไม่ถูกต้องเนื่องจากมีการเซ็นชื่อรหัส
การเข้ารหัสบล็อกโหมด Galois / Counterจะสร้างการเข้ารหัสและแท็กเพื่อตอบสนองวัตถุประสงค์เดียวกันดังนั้นจึงสามารถใช้เพื่อตอบสนองวัตถุประสงค์เดียวกันได้ ข้อเสียคือแตกต่างจาก Fernet ไม่มีสูตรอาหารขนาดเดียวที่ใช้งานง่ายเพื่อนำมาใช้ซ้ำบนแพลตฟอร์มอื่น ๆ AES-GCM ยังไม่ใช้ช่องว่างภายในดังนั้นการเข้ารหัสการเข้ารหัสนี้จึงตรงกับความยาวของข้อความที่ป้อน (ในขณะที่ Fernet / AES-CBC เข้ารหัสข้อความไปยังบล็อกที่มีความยาวคงที่ซึ่งจะบดบังความยาวของข้อความไว้บ้าง)
AES256-GCM ใช้รหัสลับ 32 ไบต์ตามปกติ:
key = secrets.token_bytes(32)
จากนั้นใช้
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 รองรับ
แนวทางอื่น ๆ ในหน้านี้ใน Python 3
AES CFB - เหมือน CBC แต่ไม่ต้องใช้แผ่นรอง
นี่คือแนวทางที่All ІѕVаиітyปฏิบัติตามแม้ว่าจะไม่ถูกต้องก็ตาม นี่เป็นcryptography
เวอร์ชัน แต่โปรดทราบว่าฉันรวม IV ไว้ในไซเฟอร์เท็กซ์ไม่ควรจัดเก็บเป็นโกลบอล (การใช้ IV ซ้ำจะทำให้ความปลอดภัยของคีย์ลดลงและการจัดเก็บเป็นโมดูลส่วนกลางหมายความว่าจะถูกสร้างขึ้นใหม่ การเรียกใช้ Python ครั้งถัดไปซึ่งจะแสดงการเข้ารหัสข้อความเข้ารหัสทั้งหมดที่ไม่สามารถถอดรหัสได้):
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()
สิ่งนี้ขาดการเสริมเกราะของลายเซ็น HMAC และไม่มีการประทับเวลา คุณต้องเพิ่มด้วยตัวเอง
ข้างต้นยังแสดงให้เห็นว่าการรวมบล็อคการเข้ารหัสพื้นฐานอย่างไม่ถูกต้องนั้นง่ายเพียงใด การจัดการค่า IV ที่ไม่ถูกต้องทั้งหมดของІѕVаиітyอาจทำให้เกิดการละเมิดข้อมูลหรือข้อความที่เข้ารหัสทั้งหมดไม่สามารถอ่านได้เนื่องจาก IV สูญหาย การใช้ Fernet แทนช่วยปกป้องคุณจากความผิดพลาดดังกล่าว
AES ECB - ไม่ปลอดภัย
หากคุณเคยใช้การเข้ารหัส AES ECB มาก่อนและจำเป็นต้องรองรับสิ่งนี้ใน Python 3 คุณก็สามารถทำได้เช่นcryptography
กัน ใช้คำเตือนเดียวกัน ECB ไม่ปลอดภัยเพียงพอสำหรับการใช้งานในชีวิตจริง นำคำตอบนั้นไปใช้ใหม่สำหรับ Python 3 เพิ่มการจัดการช่องว่างภายในอัตโนมัติ:
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()
อีกครั้งสิ่งนี้ไม่มีลายเซ็น HMAC และคุณไม่ควรใช้ ECB ต่อไป ข้างต้นเป็นเพียงการแสดงให้เห็นว่าcryptography
สามารถจัดการกับหน่วยการสร้างการเข้ารหัสทั่วไปได้แม้กระทั่งสิ่งที่คุณไม่ควรใช้จริงๆ