วิธีง่ายๆในการเข้ารหัสสตริงตามรหัสผ่าน?


123

Python มีวิธีเข้ารหัส / ถอดรหัสสตริงในตัวโดยใช้รหัสผ่านหรือไม่?

สิ่งนี้:

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

ดังนั้นสตริง "John Doe" จึงถูกเข้ารหัสเป็น "sjkl28cn2sx0" เพื่อให้ได้สตริงดั้งเดิมฉันจะ "ปลดล็อก" สตริงนั้นด้วยคีย์ "mypass" ซึ่งเป็นรหัสผ่านในซอร์สโค้ดของฉัน ฉันต้องการให้นี่เป็นวิธีที่ฉันสามารถเข้ารหัส / ถอดรหัสเอกสาร Word ด้วยรหัสผ่าน

ฉันต้องการใช้สตริงที่เข้ารหัสเหล่านี้เป็นพารามิเตอร์ URL เป้าหมายของฉันคือความสับสนไม่ใช่ความปลอดภัยที่แข็งแกร่ง ไม่มีการเข้ารหัสภารกิจสำคัญ ฉันรู้ว่าฉันสามารถใช้ตารางฐานข้อมูลเพื่อจัดเก็บคีย์และค่าต่างๆได้ แต่ฉันพยายามทำให้เรียบง่าย


28
คำว่า "รหัสผ่าน" ในที่นี้ไม่เหมาะสม คุณกำลังใช้สิ่งนี้เป็นคีย์การเข้ารหัสและคุณควรใช้คำศัพท์นั้นเพื่อหลีกเลี่ยงความสับสนในคำถามของคุณรวมถึงเอกสารความคิดเห็นข้อกำหนดแผนการทดสอบ ฯลฯ
Jim Dennis

2
"ฉันต้องการให้นี่เป็นวิธีที่ฉันสามารถเข้ารหัส / ถอดรหัสเอกสาร Word ด้วยรหัสผ่าน", Word มีตัวเลือกในตัวสำหรับเข้ารหัสเอกสารของคุณอยู่แล้วหากคุณต้องการเข้ารหัสเอกสารคำ
Byron Filer

2
ที่น่าสนใจคือตามรายงานวิจัยเกี่ยวกับข้อผิดพลาดในการจัดเก็บรหัสผ่านเช่นนี้นักพัฒนาที่ใช้ Stack Overflow มักจะสร้างรหัสที่ปลอดภัยน้อยลง ฉันสงสัยว่าทำไม?
ป่า

นอกจากนี้คุณควรอ่านคำตอบนี้จากsecurity.SE
kelalaka

ไม่มีใครใช้การเข้ารหัส / ถอดรหัสเพียงอย่าง
luckyging3r

คำตอบ:


70

สมมติว่าคุณกำลังเพียงมองหา obfuscation ง่ายๆที่จะปิดบังสิ่งที่จากมากสังเกตการณ์สบายและคุณไม่ต้องการที่จะใช้ห้องสมุดของบุคคลที่สาม ฉันขอแนะนำบางอย่างเช่น Vigenere cipher นับเป็นหนึ่งในเลขยันต์โบราณที่แข็งแกร่งที่สุด

รหัสVigenère

ใช้งานง่ายและรวดเร็ว สิ่งที่ต้องการ:

import base64

def encode(key, string):
    encoded_chars = []
    for i in xrange(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 256)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return base64.urlsafe_b64encode(encoded_string)

การถอดรหัสค่อนข้างเหมือนกันยกเว้นคุณลบคีย์

ยากกว่ามากที่จะทำลายหากสตริงที่คุณเข้ารหัสสั้นและ / หรือยากที่จะคาดเดาความยาวของข้อความรหัสผ่านที่ใช้

หากคุณกำลังมองหาบางสิ่งที่เข้ารหัส PyCrypto อาจเป็นทางออกที่ดีที่สุดของคุณแม้ว่าคำตอบก่อนหน้านี้จะมองข้ามรายละเอียดไปบ้างก็ตามโหมด ECB ใน PyCrypto ต้องการให้ข้อความของคุณมีความยาวหลาย 16 อักขระ ดังนั้นคุณต้องรอง นอกจากนี้หากคุณต้องการใช้เป็นพารามิเตอร์ URL ให้ใช้base64.urlsafe_b64_encode()แทนที่จะเป็นพารามิเตอร์มาตรฐาน สิ่งนี้จะแทนที่อักขระบางตัวในตัวอักษร base64 ด้วยอักขระที่ปลอดภัยสำหรับ URL (ตามชื่อที่แนะนำ)

อย่างไรก็ตามคุณควรแน่ใจอย่างแน่นอนว่าชั้นความสับสนที่บางมากนี้เพียงพอสำหรับความต้องการของคุณก่อนที่จะใช้สิ่งนี้ บทความ Wikipedia ที่ฉันเชื่อมโยงไปให้คำแนะนำโดยละเอียดสำหรับการทำลายรหัสดังนั้นใครก็ตามที่มีความมุ่งมั่นในระดับปานกลางสามารถทำลายมันได้อย่างง่ายดาย


6
ฉันแก้ไขสคริปต์ของ smehmood และเพิ่มฟังก์ชั่นถอดรหัสgist.github.com/ilogik/6f9431e4588015ecb194
Adrian Mester

3
ความสนใจ! รหัสของ smehmood และการแก้ไขของ Adrian Mester ใช้ได้เฉพาะกับสตริงที่มีอักขระจากช่วง ascii ที่ต่ำกว่าเท่านั้น! ดูลำดับความสำคัญของตัวดำเนินการ% อินพุตยูนิโคดและอื่น ๆ ดูคำตอบของ qneill สำหรับรหัสการทำงาน
le_m

2
@smehmood ฉันได้รับข้อผิดพลาดต่อไปนี้'str' object cannot be interpreted as an integer
Rohit Khatri

3
"for i in xrange (string)" อาจต้องเปลี่ยนเป็น "for i in xrange (len (string))"
user3113626

2
ตัวเข้ารหัสและตัวถอดรหัสสำหรับ python 2 และ 3: gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1a
iman

71

ในขณะที่คุณระบุไว้อย่างชัดเจนว่าคุณต้องการให้ความคลุมเครือไม่ใช่ความปลอดภัยเราจะหลีกเลี่ยงการตำหนิคุณสำหรับความอ่อนแอของสิ่งที่คุณแนะนำ :)

ดังนั้นการใช้ PyCrypto:

import base64
from Crypto.Cipher import AES

msg_text = b'test some plain text here'.rjust(32)
secret_key = b'1234567890123456'

cipher = AES.new(secret_key,AES.MODE_ECB) # never use ECB in strong systems obviously
encoded = base64.b64encode(cipher.encrypt(msg_text))
print(encoded)
decoded = cipher.decrypt(base64.b64decode(encoded))
print(decoded)

หากมีใครบางคนยึดฐานข้อมูลและฐานรหัสของคุณไว้พวกเขาจะสามารถถอดรหัสข้อมูลที่เข้ารหัสได้ ให้secret_keyปลอดภัย!


3
ฉันไม่คิดว่ามันจะได้ผลเว้นแต่ msg_text จะมีความยาว 16 ไบต์หลายเท่าเนื่องจากการเข้ารหัส AES ต้องใช้บล็อกที่มีความยาว 16 แบบทวีคูณ การนำไปใช้งานสำหรับ msg_text ที่มีความยาวโดยพลการจะต้องเพิ่มช่องว่างภายในสตริงเพื่อให้มีความยาวหลาย 16
tohster

6
ตัวอย่างที่มีช่องว่างภายใน: paste.ubuntu.com/11024555 ใช้งานได้กับรหัสผ่านและความยาวของข้อความโดยพลการ
iman

3
@ แทนไม่ได้encryptฟังก์ชันเฉพาะนี้คือ stateful dlitz.net/software/pycrypto/api/current/…ดังนั้นคุณไม่ควรลองและใช้ซ้ำ
จะ

1
@ จะ +1 ขอบคุณสำหรับข้อมูลและลิงค์ ช่วยฉันจากการแก้ไขข้อบกพร่องที่มีราคาแพงมากในอนาคต
Ethan

3
re - "ไม่เคยใช้ ECB ในระบบที่แข็งแกร่งอย่างเห็นได้ชัด": ฉันแค่ทดลองสิ่งนี้เพื่อความสนุกของตัวเอง แต่ฉันเห็นความคิดเห็นด้านบนในรหัสของคุณ สำหรับพวกเราที่มีพื้นฐานด้านความปลอดภัย / การเข้ารหัส / ข้อมูล - ทฤษฎีที่น้อยมากเหตุใดจึง "ไม่ใช้"? อาจต้องการคำถามอื่น ... หรืออาจมีลิงค์อยู่
Trevor Boyd Smith

69

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สามารถจัดการกับหน่วยการสร้างการเข้ารหัสทั่วไปได้แม้กระทั่งสิ่งที่คุณไม่ควรใช้จริงๆ


51

"encoded_c" ที่กล่าวถึงในคำตอบรหัส Vigenereของ @ smehmood ควรเป็น "key_c"

นี่คือฟังก์ชันการเข้ารหัส / ถอดรหัสที่ใช้งานได้

import base64
def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

ข้อจำกัดความรับผิดชอบ:ตามนัยโดยความคิดเห็นนี้ไม่ควรใช้เพื่อปกป้องข้อมูลในแอปพลิเคชันจริงเว้นแต่คุณจะอ่านสิ่งนี้และไม่สนใจที่จะพูดคุยกับทนายความ:

เกิดอะไรขึ้นกับการเข้ารหัส XOR


2
มีประโยชน์มากขอบคุณ ฉันโพสต์เวอร์ชัน Python 3 ไว้ด้านล่าง (มันดูน่าเกลียดในความคิดเห็น)
Ryan Barrett

1
"กฎของชไนเออร์" : ทุกคนตั้งแต่มือสมัครเล่นที่ไร้เงื่อนงำที่สุดไปจนถึงนักเข้ารหัสที่เก่งที่สุดสามารถสร้างอัลกอริทึมที่ตัวเขาเองไม่สามารถทำลายได้ อย่าใช้สิ่งนี้มันไม่ได้อยู่ใกล้กับความปลอดภัย
zaph

3
ที่ดี! เวอร์ชันนี้ใช้งานได้กับสตริงที่มีสำเนียงในขณะที่เวอร์ชันของ @ smehmood ใช้ไม่ได้ ตัวอย่างการใช้งาน: encodedmsg = encode('mypassword', 'this is the message éçàèç"') print encodedmsg print decode('mypassword', encodedmsg)ใช้งานได้ดี
Basj

2
นี่คือปลั๊กอินข้อความ Sublime ที่ใช้รหัสนี้ทำให้สามารถเข้ารหัส / ถอดรหัสข้อความด้วย CTRL + SHIFT + P ตามด้วย "Eeencode" หรือ "Dddecode"
Basj

2
@basj ขอบคุณสำหรับตัวอย่าง
sk03

49

นี่คือฟังก์ชัน Python 3 เวอร์ชันจากคำตอบของ @qneill :

import base64
def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc).encode()).decode()

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc).decode()
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

จำเป็นต้องมีการเข้ารหัส / ถอดรหัสเพิ่มเติมเนื่องจาก Python 3 ได้แบ่งสตริง / ไบต์อาร์เรย์ออกเป็นสองแนวคิดที่แตกต่างกันและอัปเดต API เพื่อแสดงว่า ..


4
ขอบคุณ Ryan fwiw คุณพิมพ์ผิด @qniell
qneill

3
สำหรับผู้ที่สงสัยความแตกต่างคือ.encode()).decode(). ในการกลับมาของencode()และในบรรทัดที่สองใน.decode() decode()
RolfBly

2
อืมรหัสที่เข้ารหัสไม่ซ้ำกันจริงๆฉันทำการทดสอบและแสดงรหัสทุกรหัสที่ 11,22,33,44, ... , 88,99,111,222, ... จะมีรหัสอื่นเหมือนเดิมเสมอ แต่ฉันรู้สึกขอบคุณ
Hzzkygcs

1
@Ryan Barrett ตรวจสอบความถูกต้องของรหัสผ่านเมื่อถอดรหัสได้หรือไม่ สมมติว่าฉันส่งสตริงที่เข้ารหัสหนึ่งตัวซึ่งรู้คีย์จะเป็นอย่างไรถ้าเขาป้อนคีย์ด้วยการพิมพ์ผิด การถอดรหัสยังคงให้สตริงที่ "ถอดรหัส" แก่เขา แต่มันไม่ถูกต้องเขาจะบอกได้อย่างไร?
Heinz

26

ข้อจำกัดความรับผิดชอบ:ตามที่ระบุไว้ในความคิดเห็นนี้ไม่ควรใช้เพื่อปกป้องข้อมูลในแอปพลิเคชันจริง

เกิดอะไรขึ้นกับการเข้ารหัส XOR

/crypto/56281/breaking-a-xor-cipher-of-known-key-length

https://github.com/hellman/xortool


ดังที่ได้กล่าวไปแล้วว่าไลบรารี PyCrypto มีชุดการเข้ารหัส XOR "cipher" สามารถใช้เพื่อทำงานสกปรกได้หากคุณไม่ต้องการทำเอง:

from Crypto.Cipher import XOR
import base64

def encrypt(key, plaintext):
  cipher = XOR.new(key)
  return base64.b64encode(cipher.encrypt(plaintext))

def decrypt(key, ciphertext):
  cipher = XOR.new(key)
  return cipher.decrypt(base64.b64decode(ciphertext))

การเข้ารหัสจะทำงานดังต่อไปนี้โดยไม่ต้องใส่ข้อความธรรมดา:

>>> encrypt('notsosecretkey', 'Attack at dawn!')
'LxsAEgwYRQIGRRAKEhdP'

>>> decrypt('notsosecretkey', encrypt('notsosecretkey', 'Attack at dawn!'))
'Attack at dawn!'

เครดิตhttps://stackoverflow.com/a/2490376/241294สำหรับฟังก์ชันการเข้ารหัส / ถอดรหัส base64 (ฉันเป็นมือใหม่ python)


หมายเหตุ: โมดูล Crypto ถูกติดตั้งใน python3 โดยติดตั้ง pycrptop ไม่ใช่ Crypto sudo pip3 install pycrypto.
Nikhil VJ

2
หมายเหตุ: pycrypto ไม่สามารถติดตั้งบน herokuapp ในตอนท้ายของฉัน ฉันพบโพสต์นี้ .. ดูเหมือนจะบอกว่าแพ็คเกจ pycrypto ถูกแทนที่ด้วยอันอื่นที่เรียกว่า pycryptodome insteal และวิธีการ XOR นั้นเลิกใช้แล้ว: github.com/digitalocean/netbox/issues/1527
Nikhil VJ

2
อย่าใช้วิธีนี้โปรดสังเกตคำอธิบายของ 'การเข้ารหัส' ในเอกสารประกอบ : การเข้ารหัสของเล่น XOR XOR เป็นหนึ่งในการเข้ารหัสสตรีมที่ง่ายที่สุด การเข้ารหัสและถอดรหัสดำเนินการโดยข้อมูล XOR กับคีย์สตรีมที่สร้างขึ้นโดยการต่อคีย์ ห้ามนำไปใช้งานจริง! .
Martijn Pieters

@MartijnPieters คุณพูดถูก หวังว่าการแก้ไขของฉันจะทำให้ประเด็นนั้นชัดเจน
poida

12

นี่คือการใช้งานการเข้ารหัสและถอดรหัส URL ที่ปลอดภัยโดยใช้ AES (PyCrypto) และ base64

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

AKEY = b'mysixteenbytekey' # AES key must be either 16, 24, or 32 bytes long

iv = Random.new().read(AES.block_size)

def encode(message):
    obj = AES.new(AKEY, AES.MODE_CFB, iv)
    return base64.urlsafe_b64encode(obj.encrypt(message))

def decode(cipher):
    obj2 = AES.new(AKEY, AES.MODE_CFB, iv)
    return obj2.decrypt(base64.urlsafe_b64decode(cipher))

หากคุณประสบปัญหาเช่นนี้https://bugs.python.org/issue4329 ( TypeError: character mapping must return integer, None or unicode) ให้ใช้str(cipher)ขณะถอดรหัสดังนี้:

return obj2.decrypt(base64.urlsafe_b64decode(str(cipher)))

ทดสอบ:

In [13]: encode(b"Hello World")
Out[13]: b'67jjg-8_RyaJ-28='

In [14]: %timeit encode("Hello World")
100000 loops, best of 3: 13.9 µs per loop

In [15]: decode(b'67jjg-8_RyaJ-28=')
Out[15]: b'Hello World'

In [16]: %timeit decode(b'67jjg-8_RyaJ-28=')
100000 loops, best of 3: 15.2 µs per loop

ข้อผิดพลาดกับ Windows x64 + Python 3.6 + PyCryptodome (ตาม pycrypto TypeError: Object type <class 'str'> cannot be passed to C codeเลิก):
Basj

@Basj ขออภัย .. ฉันไม่ได้ใช้ windows ดังนั้นฉันจึงไม่สามารถแก้ไขได้
ทั้งหมดІѕVаиітy

อย่าสร้าง IV และเก็บไว้ที่ระดับโมดูลคุณต้องรวม IV ไว้ในข้อความรหัสที่ส่งคืน! ตอนนี้คุณพบปัญหาสองประการ: การรีสตาร์ทกระบวนการ Python จะทำให้คุณมี IV ใหม่ทำให้ไม่สามารถถอดรหัสข้อความที่เข้ารหัสไว้ก่อนหน้านี้ได้และในระหว่างนี้คุณกำลังใช้ IV ซ้ำสำหรับข้อความหลายข้อความซึ่งจะลดความปลอดภัยกลับสู่ระดับ ECB ได้อย่างมีประสิทธิภาพ
Martijn Pieters

1
@ AllІѕVаиітyแก้ไขด้วยb'...'ฉันแก้ไขคำตอบสำหรับการอ้างอิงในอนาคต!
Basj

8

ฟังก์ชันเข้ารหัส / ถอดรหัสการทำงานใน python3 (ดัดแปลงน้อยมากจากคำตอบของ qneill):

def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = (ord(clear[i]) + ord(key_c)) % 256
        enc.append(enc_c)
    return base64.urlsafe_b64encode(bytes(enc))

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + enc[i] - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

8

ขอบคุณสำหรับคำตอบที่ดี ไม่มีอะไรเป็นต้นฉบับให้เพิ่ม แต่นี่คือการเขียนคำตอบของ qneill แบบก้าวหน้าโดยใช้สิ่งอำนวยความสะดวก Python ที่มีประโยชน์ ฉันหวังว่าคุณจะเห็นด้วยกับการลดความซับซ้อนและชี้แจงรหัส

import base64


def qneill_encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))


def qneill_decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

enumerate()- จับคู่รายการในรายการกับดัชนี

วนซ้ำอักขระในสตริง

def encode_enumerate(key, clear):
    enc = []
    for i, ch in enumerate(clear):
        key_c = key[i % len(key)]
        enc_c = chr((ord(ch) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))


def decode_enumerate(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i, ch in enumerate(enc):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(ch) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

สร้างรายการโดยใช้ความเข้าใจรายการ

def encode_comprehension(key, clear):
    enc = [chr((ord(clear_char) + ord(key[i % len(key)])) % 256)
                for i, clear_char in enumerate(clear)]
    return base64.urlsafe_b64encode("".join(enc))


def decode_comprehension(key, enc):
    enc = base64.urlsafe_b64decode(enc)
    dec = [chr((256 + ord(ch) - ord(key[i % len(key)])) % 256)
           for i, ch in enumerate(enc)]
    return "".join(dec)

บ่อยครั้งใน Python ไม่จำเป็นต้องมีดัชนีรายการเลย กำจัดตัวแปรดัชนีการวนซ้ำทั้งหมดโดยใช้ zip และ cycle:

from itertools import cycle


def encode_zip_cycle(key, clear):
    enc = [chr((ord(clear_char) + ord(key_char)) % 256)
                for clear_char, key_char in zip(clear, cycle(key))]
    return base64.urlsafe_b64encode("".join(enc))


def decode_zip_cycle(key, enc):
    enc = base64.urlsafe_b64decode(enc)
    dec = [chr((256 + ord(enc_char) - ord(key_char)) % 256)
                for enc_char, key_char in zip(enc, cycle(key))]
    return "".join(dec)

และการทดสอบบางอย่าง ...

msg = 'The quick brown fox jumps over the lazy dog.'
key = 'jMG6JV3QdtRh3EhCHWUi'
print('cleartext: {0}'.format(msg))
print('ciphertext: {0}'.format(encode_zip_cycle(key, msg)))

encoders = [qneill_encode, encode_enumerate, encode_comprehension, encode_zip_cycle]
decoders = [qneill_decode, decode_enumerate, decode_comprehension, decode_zip_cycle]

# round-trip check for each pair of implementations
matched_pairs = zip(encoders, decoders)
assert all([decode(key, encode(key, msg)) == msg for encode, decode in matched_pairs])
print('Round-trips for encoder-decoder pairs: all tests passed')

# round-trip applying each kind of decode to each kind of encode to prove equivalent
from itertools import product
all_combinations = product(encoders, decoders)
assert all(decode(key, encode(key, msg)) == msg for encode, decode in all_combinations)
print('Each encoder and decoder can be swapped with any other: all tests passed')

>>> python crypt.py
cleartext: The quick brown fox jumps over the lazy dog.
ciphertext: vrWsVrvLnLTPlLTaorzWY67GzYnUwrSmvXaix8nmctybqoivqdHOic68rmQ=
Round-trips for encoder-decoder pairs: all tests passed
Each encoder and decoder can be swapped with any other: all tests passed

@Nick ดีมากความก้าวหน้าที่ดีของ pythonisms และการทดสอบก็บู๊ตด้วย ที่จะให้เครดิตที่เหมาะสมผมก็แค่การแก้ไขข้อผิดพลาดใน Smehmood ของคำตอบเดิมstackoverflow.com/a/2490718/468252
qneill

4

หากต้องการความปลอดภัยคุณสามารถใช้ Fernet ซึ่งเป็นเสียงที่เข้ารหัส คุณสามารถใช้ "เกลือ" แบบคงที่ได้หากคุณไม่ต้องการจัดเก็บแยกกันคุณจะสูญเสียพจนานุกรมและการป้องกันการโจมตีด้วยสายรุ้งเท่านั้น ฉันเลือกเพราะฉันสามารถเลือกรหัสผ่านแบบยาวหรือแบบสั้นได้ซึ่ง 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

หากซับซ้อนเกินไปมีคนแนะนำ simplecrypt

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

เพียงแค่สร้างเกลือและรวมไว้ในผลลัพธ์การเข้ารหัสดังนั้นรหัสผ่านและข้อความที่ซ้ำกันจะยังคงส่งผลแบบสุ่ม รวมค่าการทำซ้ำไว้ด้วยเพื่อพิสูจน์อัลกอริทึมในอนาคต แต่ยังสามารถถอดรหัสข้อความโดยใช้การนับการวนซ้ำที่แตกต่างกัน
Martijn Pieters

4

ฉันจะให้ 4 วิธีแก้ปัญหา:

1) การใช้การเข้ารหัส Fernet กับcryptographyไลบรารี

นี่คือวิธีแก้ปัญหาโดยใช้แพ็คเกจcryptographyซึ่งคุณสามารถติดตั้งได้ตามปกติด้วย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')

คุณสามารถปรับให้เข้ากับเกลือของคุณเองการนับซ้ำ ฯลฯ รหัสนี้อยู่ไม่ไกลจากคำตอบของ @ HCLivess มากนัก แต่เป้าหมายอยู่ที่นี่เพื่อให้พร้อมใช้งานencryptและมีdecryptฟังก์ชัน ที่มา: https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet

หมายเหตุ: ใช้.encode()และ.decode()ทุกที่หากคุณต้องการสตริง'John Doe'แทนไบต์เช่นb'John Doe'.


2) การเข้ารหัส AES อย่างง่ายด้วยCryptoไลบรารี

สิ่งนี้ใช้ได้กับ 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

หมายเหตุ: คุณสามารถลบbase64.b64encodeและ.b64decodeหากคุณไม่ต้องการเอาต์พุตที่อ่านข้อความได้และ / หรือหากคุณต้องการบันทึกไซเฟอร์เท็กซ์ลงในดิสก์เป็นไฟล์ไบนารี


3) AES ใช้ฟังก์ชันการสร้างรหัสผ่านที่ดีขึ้นและความสามารถในการทดสอบว่า "ป้อนรหัสผ่านผิด" ด้วยCryptoไลบรารีหรือไม่

วิธีแก้ปัญหา 2) ด้วย AES "โหมด CFB" นั้นใช้ได้ แต่มีข้อเสียอยู่สองประการคือความจริงที่ว่าSHA256(password)สามารถใช้ตารางการค้นหาได้อย่างดุเดือดและไม่มีวิธีทดสอบว่าป้อนรหัสผ่านผิดหรือไม่ สิ่งนี้แก้ไขได้ที่นี่โดยการใช้ AES ใน "โหมด GCM" ตามที่กล่าวไว้ในAES: จะตรวจสอบได้อย่างไรว่ามีการป้อนรหัสผ่านที่ไม่ถูกต้อง และวิธีนี้จะบอกว่า“ รหัสผ่านที่คุณป้อนผิด” ปลอดภัยหรือไม่? :

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) การใช้ RC4 (ไม่จำเป็นต้องใช้ไลบรารี)

ที่ดัดแปลงมาจาก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'

(ล้าสมัยตั้งแต่การแก้ไขล่าสุด แต่เก็บไว้เพื่อใช้อ้างอิงในอนาคต): ฉันมีปัญหาในการใช้ Windows + Python 3.6 + คำตอบทั้งหมดที่เกี่ยวข้องpycrypto(ไม่สามารถpip install pycryptoบน Windows) หรือpycryptodome(คำตอบที่นี่from Crypto.Cipher import XORล้มเหลวเนื่องจากXORไม่ได้รับการสนับสนุนจากpycryptoทางแยกนี้และ การแก้ปัญหาโดยใช้... AESล้มเหลวด้วยTypeError: Object type <class 'str'> cannot be passed to C code) นอกจากนี้ไลบรารีsimple-cryptยังมีการpycryptoพึ่งพาดังนั้นจึงไม่ใช่ตัวเลือก


1
คุณไม่ต้องการเข้ารหัสเกลือและการนับซ้ำ สร้างเกลือแบบสุ่มและทำให้การนับการนับซ้ำสามารถกำหนดค่าได้ในการเข้ารหัสรวมข้อมูลนั้นในผลลัพธ์การเข้ารหัสและแยกและใช้ค่าในการถอดรหัส เกลือช่วยปกป้องคุณจากการจดจำรหัสผ่านที่นำกลับมาใช้ซ้ำในข้อความที่ระบุการทำซ้ำนับเป็นการพิสูจน์อัลกอริทึมในอนาคต
Martijn Pieters

ใช่แน่นอน @MartijnPieters แต่เป้าหมายอยู่ที่นี่เพื่อให้มีรหัสง่าย ๆ สำหรับวัตถุประสงค์ง่ายๆตามที่ OP ร้องขอโดยมีพารามิเตอร์สองตัวคือข้อความธรรมดา + รหัสผ่าน แน่นอนสำหรับสถานการณ์ที่ซับซ้อนมากขึ้น (เช่นฐานข้อมูล) คุณจะใช้พารามิเตอร์เพิ่มเติมเหล่านี้ทั้งหมด
Basj

ไม่จำเป็นต้องมีพารามิเตอร์เพิ่มเติม! password_encrypt()ฉันรวมถึงข้อมูลที่เข้ารหัสในค่าตอบแทนทึบแสง
Martijn Pieters

@MartijnPieters ทางออกที่ดีแน่นอน
Basj

3

ใครก็ตามที่มาที่นี่ (และผู้ให้รางวัล) ดูเหมือนจะมองหาหนึ่งสมุทรที่มีการตั้งค่าไม่มากนักซึ่งคำตอบอื่น ๆ ไม่มีให้ ผมก็เลยเอา base64 ไปข้างหน้า

ตอนนี้โปรดทราบว่านี่เป็นการทำให้สับสนขั้นพื้นฐานเท่านั้นและอยู่ใน** NO WAY OK FOR SECURITY **แต่นี่คือบางส่วน:

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data, key):
    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'

สิ่งที่ควรทราบ:

  • คุณจะต้องการจัดการกับการเข้ารหัส / ถอดรหัสแบบไบต์ต่อสตริงมาก / น้อยขึ้นอยู่กับ I / O ของคุณ มองเข้าไปbytes()และbytes::decode()
  • base64 สามารถจดจำได้ง่ายตามประเภทของอักขระที่ใช้และมักจะลงท้ายด้วย=อักขระ คนอย่างฉันมักจะถอดรหัสพวกเขาในคอนโซล javascript เมื่อเราเห็นพวกเขาบนเว็บไซต์ ง่ายเหมือนbtoa(string)(js)
  • ลำดับคือคีย์ + ข้อมูลเช่นเดียวกับใน b64 อักขระใดที่ปรากฏในตอนท้ายขึ้นอยู่กับว่าอักขระใดอยู่ที่จุดเริ่มต้น (เนื่องจากออฟเซ็ตไบต์Wikipediaมีคำอธิบายที่ดี) ในสถานการณ์นี้จุดเริ่มต้นของสตริงที่เข้ารหัสจะเหมือนกันสำหรับทุกสิ่งที่เข้ารหัสด้วยคีย์นั้น ข้อดีคือข้อมูลจะสับสนมากขึ้น การทำในทางกลับกันจะส่งผลให้ส่วนข้อมูลเหมือนกันทุกประการสำหรับทุกคนโดยไม่คำนึงถึงคีย์

ตอนนี้หากสิ่งที่คุณต้องการไม่จำเป็นต้องใช้คีย์ใด ๆ แต่มีเพียงความสับสนคุณสามารถใช้ base64 ได้อีกครั้งโดยไม่ต้องใช้คีย์ใด ๆ :

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'

2
ใช่ถ้าคุณไม่ได้กังวลเกี่ยวกับความปลอดภัย base64 เป็นวิธีที่ดีกว่าการเข้ารหัส
Martijn Pieters

เกี่ยวกับคำตอบอื่น ๆ ที่ไม่ได้เป็นเพียงเส้นเดียวนั่นไม่ใช่ประเด็นของคำถาม พวกเขาขอสองฟังก์ชันในการโทร และFernet(key).encrypt(message)เป็นเพียงนิพจน์เดียวเช่นเดียวกับการโทร base64 ของคุณ
Martijn Pieters

และคุณควรจะลบkeyโดยสิ้นเชิง นักพัฒนาจำนวนมากจะคัดลอกและวางจาก Stack Overflow โดยไม่สนใจและจะถือว่าคีย์เป็นความลับ หากคุณต้องรวมไว้อย่างน้อยที่สุดอย่าใช้และเตือนหรือยกข้อยกเว้นหากใช้ต่อไป อย่าดูถูกความโง่เขลาของวัฒนธรรมการคัดลอกและวางและความรับผิดชอบของคุณในการส่งมอบฟังก์ชันที่มีเหตุผล
Martijn Pieters

2

ผลงาน 8แต่รหัสผ่านนี้ยาวควรจะตรง นี้เป็นเรื่องง่ายและต้อง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

เอาท์พุท:

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

อย่าใช้ IV คงที่! สุ่ม IV และรวมเข้ากับ ciphertext แทน มิฉะนั้นคุณอาจใช้โหมด ECB ได้เช่นกัน ข้อความธรรมดาซ้ำ ๆ เป็นเรื่องเล็กน้อยที่จะจดจำ
Martijn Pieters

นอกจากนี้โครงการ pyDes ดูเหมือนจะตายไปแล้ว หน้าแรกหายไปและรุ่นล่าสุดของ PyPI ตอนนี้มีอายุ 9 ปีแล้ว
Martijn Pieters

2

การใช้งานโค้ด @qneill อื่น ๆ ซึ่งรวมถึงการตรวจสอบ CRC ของข้อความต้นฉบับจะทำให้เกิดข้อยกเว้นหากการตรวจสอบล้มเหลว:

import hashlib
import struct
import zlib

def vigenere_encode(text, key):
    text = '{}{}'.format(text, struct.pack('i', zlib.crc32(text)))

    enc = []
    for i in range(len(text)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(text[i]) + ord(key_c)) % 256)
        enc.append(enc_c)

    return base64.urlsafe_b64encode("".join(enc))


def vigenere_decode(encoded_text, key):
    dec = []
    encoded_text = base64.urlsafe_b64decode(encoded_text)
    for i in range(len(encoded_text)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(encoded_text[i]) - ord(key_c)) % 256)
        dec.append(dec_c)

    dec = "".join(dec)
    checksum = dec[-4:]
    dec = dec[:-4]

    assert zlib.crc32(dec) == struct.unpack('i', checksum)[0], 'Decode Checksum Error'

    return dec

2

คุณสามารถใช้ AES เพื่อเข้ารหัสสตริงของคุณด้วยรหัสผ่าน แม้ว่าคุณจะต้องเลือกรหัสผ่านที่รัดกุมเพียงพอเพื่อให้ผู้คนไม่สามารถเดาได้ง่ายว่ามันคืออะไร (ขออภัยฉันช่วยไม่ได้ฉันเป็นคนขี้สงสัยด้านความปลอดภัย)

AES แข็งแกร่งด้วยขนาดคีย์ที่ดี แต่ก็ใช้งานง่ายกับ PyCrypto


3
ขอบคุณอลัน แต่เพื่อความชัดเจนฉันไม่ได้เข้ารหัสรหัสผ่านเอง ในตัวอย่างข้างต้นฉันกำลังเข้ารหัสสตริง "John Doe" ตามรหัสผ่าน "mypass" ซึ่งเป็นรหัสผ่านง่ายๆที่ฉันใช้ในซอร์สโค้ดของฉัน รหัสผ่านผู้ใช้ไม่เกี่ยวข้องและไม่มีข้อมูลที่ละเอียดอ่อนอื่น ๆ ฉันแก้ไขคำถามเพื่อชี้แจงเรื่องนี้
RexE

1
AES ดีมากหากใช้อย่างถูกต้อง มันง่ายที่จะใช้อย่างไม่ถูกต้องอย่างไรก็ตาม มีอย่างน้อยหนึ่งคำตอบที่นี่ซึ่งใช้โหมดการเข้ารหัสบล็อกที่ไม่ปลอดภัยอีกสองคำตอบที่คลำหาค่า IV ดีกว่าที่จะใช้ห้องสมุดที่ดีพร้อมสูตรอาหารที่กำหนดไว้อย่างดีเช่น Fernet!
Martijn Pieters

จริงๆแล้วนั่นเป็นข้อสังเกตที่ชาญฉลาดมาก ฉันเคยคลำ IV ไปแล้วครั้งหนึ่ง
Alan

0

ไลบรารีภายนอกจัดเตรียมอัลกอริธึมการเข้ารหัสลับคีย์

ตัวอย่างเช่นCypherโมดูลใน PyCryptoมีอัลกอริธึมการเข้ารหัสให้เลือกมากมาย:

  • Crypto.Cipher.AES
  • Crypto.Cipher.ARC2
  • Crypto.Cipher.ARC4
  • Crypto.Cipher.Blowfish
  • Crypto.Cipher.CAST
  • Crypto.Cipher.DES
  • Crypto.Cipher.DES3
  • Crypto.Cipher.IDEA
  • Crypto.Cipher.RC5
  • Crypto.Cipher.XOR

MeTooCryptoเป็นPythonกระดาษห่อหุ้มสำหรับOpenSSLและมี (ท่ามกลางฟังก์ชันอื่น ๆ ) ไลบรารีการเข้ารหัสสำหรับวัตถุประสงค์ทั่วไปที่มีประสิทธิภาพเต็มรูปแบบ รวมเป็นรหัสสมมาตร (เช่น AES)


0

หากคุณต้องการการเข้ารหัสที่ปลอดภัย:

สำหรับ python 2 คุณควรใช้ keyczar http://www.keyczar.org/

สำหรับ python 3 จนกว่า keyczar จะพร้อมใช้งานฉันได้เขียน simple-crypt http://pypi.python.org/pypi/simple-crypt

ทั้งสองอย่างนี้จะใช้การเสริมความแข็งแกร่งที่สำคัญซึ่งทำให้ปลอดภัยมากกว่าคำตอบอื่น ๆ ส่วนใหญ่ที่นี่ และเนื่องจากใช้งานง่ายมากคุณอาจต้องการใช้แม้ว่าความปลอดภัยจะไม่สำคัญก็ตาม ...


จากที่เก็บ Keyczar : หมายเหตุสำคัญ: Keyczar เลิกใช้แล้ว นักพัฒนา Keyczar แนะนำTinkแต่ไม่มี Tink เวอร์ชัน Python
Martijn Pieters

0

จึงเป็นภารกิจที่สำคัญอะไรที่จะถูกเข้ารหัสและคุณเพียงต้องการที่จะเข้ารหัสสำหรับobsfuscation

ให้ฉันนำเสนอการเข้ารหัสของ Caeser

ใส่คำอธิบายภาพที่นี่

การเข้ารหัสของซีซาร์หรือ Caesar shift เป็นหนึ่งในเทคนิคการเข้ารหัสที่ง่ายที่สุดและเป็นที่รู้จักกันอย่างแพร่หลาย เป็นรหัสแทนชนิดหนึ่งซึ่งตัวอักษรแต่ละตัวในข้อความธรรมดาจะถูกแทนที่ด้วยตัวอักษรจำนวนตำแหน่งคงที่ตามตัวอักษร ตัวอย่างเช่นด้วยการเลื่อนซ้ายเป็น 3 D จะถูกแทนที่ด้วย A, E จะกลายเป็น B และอื่น ๆ

รหัสตัวอย่างสำหรับการอ้างอิงของคุณ:

def encrypt(text,s): 
        result = "" 

        # traverse text 
        for i in range(len(text)): 
            char = text[i] 

            # Encrypt uppercase characters 
            if (char.isupper()): 
                result += chr((ord(char) + s-65) % 26 + 65) 

            # Encrypt lowercase characters 
            else: 
                result += chr((ord(char) + s - 97) % 26 + 97) 

        return result 

    def decrypt(text,s): 
        result = "" 

        # traverse text 
        for i in range(len(text)): 
            char = text[i] 

            # Encrypt uppercase characters 
            if (char.isupper()): 
                result += chr((ord(char) - s-65) % 26 + 65) 

            # Encrypt lowercase characters 
            else: 
                result += chr((ord(char) - s - 97) % 26 + 97) 

        return result 

    #check the above function 
    text = "ATTACKATONCE"
    s = 4
    print("Text  : " + text) 
    print("Shift : " + str(s)) 
    print("Cipher: " + encrypt(text,s))
    print("Original text: " + decrypt(encrypt(text,s),s))

ข้อดี: ตรงตามความต้องการของคุณและเรียบง่ายและเข้ารหัสสิ่งที่ 'y'

ข้อเสีย: สามารถแตกได้โดยอัลกอริธึมกำลังเดรัจฉานอย่างง่าย (ไม่น่าจะมีใครพยายามที่จะผ่านผลลัพธ์พิเศษทั้งหมด)


0

การเพิ่มรหัสอีกหนึ่งรหัสพร้อมถอดรหัสและเข้ารหัสสำหรับการอ้างอิง

import base64

def encode(key, string):
    encoded_chars = []
    for i in range(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 128)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    arr2 = bytes(encoded_string, 'utf-8')
    return base64.urlsafe_b64encode(arr2)

def decode(key, string):
    encoded_chars = []
    string = base64.urlsafe_b64decode(string)
    string = string.decode('utf-8')
    for i in range(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) - ord(key_c) % 128)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return encoded_string

def main():
    answer = str(input("EorD"))
    if(answer in ['E']):
        #ENCODE
        file = open("D:\enc.txt")
        line = file.read().replace("\n", " NEWLINEHERE ")
        file.close()
        text = encode("4114458",line)
        fnew = open("D:\\new.txt","w+")
        fnew.write(text.decode('utf-8'))
        fnew.close()
    else:
        #DECODE
        file = open("D:\\new.txt",'r+')
        eline = file.read().replace("NEWLINEHERE","\n")
        file.close()
        print(eline)
        eline = eline.encode('utf-8')
        dtext=decode("4114458",eline)
        print(dtext)
        fnew = open("D:\\newde.txt","w+")
        fnew.write(dtext)
        fnew.close

if __name__ == '__main__':
    main()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.