Cách đơn giản để mã hóa một chuỗi theo mật khẩu?


122

Python có tích hợp sẵn một cách mã hóa / giải mã chuỗi đơn giản bằng mật khẩu không?

Một cái gì đó như thế này:

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

Vì vậy, chuỗi "John Doe" được mã hóa thành 'sjkl28cn2sx0'. Để lấy chuỗi gốc, tôi sẽ "mở khóa" chuỗi đó bằng khóa 'mypass', là mật khẩu trong mã nguồn của tôi. Tôi muốn đây là cách tôi có thể mã hóa / giải mã tài liệu Word bằng mật khẩu.

Tôi muốn sử dụng các chuỗi được mã hóa này làm tham số URL. Mục tiêu của tôi là làm xáo trộn chứ không phải bảo mật mạnh; không có nhiệm vụ quan trọng nào được mã hóa. Tôi nhận ra rằng tôi có thể sử dụng một bảng cơ sở dữ liệu để lưu trữ các khóa và giá trị, nhưng tôi đang cố gắng tối giản hóa.


28
Thuật ngữ "mật khẩu" ở đây là không phù hợp. Bạn đang sử dụng điều này như một khóa mật mã và bạn nên sử dụng thuật ngữ để tránh sự nhầm lẫn trong câu hỏi của bạn cũng như bất kỳ tài liệu, ý kiến, thông số kỹ thuật, kế hoạch kiểm tra, vv
Jim Dennis

2
"Tôi muốn đây là cách tôi có thể mã hóa / giải mã tài liệu Word bằng mật khẩu.", Word đã tích hợp sẵn tùy chọn để mã hóa tài liệu của bạn nếu bạn chỉ cần mã hóa tài liệu word.
Byron Filer

2
Điều thú vị là theo bài báo nghiên cứu về cạm bẫy lưu trữ mật khẩu như thế này , các nhà phát triển sử dụng Stack Overflow có xu hướng tạo ra mã kém an toàn hơn. Gee, tôi tự hỏi tại sao?
rừng

Ngoài ra, người ta nên đọc câu trả lời này từ security.SE
kelalaka

Người ta không chỉ thực hiện mã hóa / giải mã một cách đơn giản
luckyging3r

Câu trả lời:


70

Giả sử bạn chỉ tìm kiếm sự xáo trộn đơn giản sẽ che khuất mọi thứ từ người quan sát rất bình thường và bạn không muốn sử dụng thư viện của bên thứ ba. Tôi muốn giới thiệu một cái gì đó giống như mật mã Vigenere. Nó là một trong những mật mã cổ đại mạnh nhất.

Mật mã Vigenère

Nó nhanh chóng và dễ dàng để thực hiện. Cái gì đó như:

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)

Việc giải mã khá giống nhau, ngoại trừ việc bạn trừ đi khóa.

Sẽ khó bị phá hơn nhiều nếu các chuỗi bạn đang mã hóa ngắn và / hoặc nếu khó đoán độ dài của cụm mật khẩu được sử dụng.

Nếu bạn đang tìm kiếm thứ gì đó mang tính chất mật mã, PyCrypto có lẽ là lựa chọn tốt nhất của bạn, mặc dù các câu trả lời trước đó bỏ qua một số chi tiết: Chế độ ECB trong PyCrypto yêu cầu tin nhắn của bạn phải có nhiều 16 ký tự. Vì vậy, bạn phải độn thổ. Ngoài ra, nếu bạn muốn sử dụng chúng làm thông số URL, hãy sử dụng base64.urlsafe_b64_encode()thay vì sử dụng thông số chuẩn. Điều này thay thế một vài ký tự trong bảng chữ cái base64 bằng các ký tự an toàn cho URL (như tên gọi của nó).

Tuy nhiên, bạn nên TUYỆT ĐỐI chắc chắn rằng lớp che phủ rất mỏng này đủ cho nhu cầu của bạn trước khi sử dụng. Bài viết trên Wikipedia mà tôi liên kết để cung cấp hướng dẫn chi tiết về cách phá mật mã, vì vậy bất kỳ ai có quyết tâm vừa phải đều có thể dễ dàng phá vỡ nó.


6
Tôi đã sửa tập lệnh của smehmood và thêm chức năng giải mã gist.github.com/ilogik/6f9431e4588015ecb194
Adrian Mester

3
Chú ý! Mã của smehmood và bản sửa lỗi của Adrian Mester đều chỉ hoạt động với các chuỗi có ký tự từ dải ascii thấp hơn! Xem mức độ ưu tiên của toán tử%, đầu vào unicode, v.v. Xem câu trả lời của qneill cho mã hoạt động
le_m

2
@smehmood Tôi nhận được lỗi sau'str' object cannot be interpreted as an integer
Rohit Khatri

3
"for i in xrange (string)" có thể cần phải chang để "for i in xrange (LEN (string))"
user3113626

2
bộ mã hóa và giải mã cho python 2 và 3: gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1a
iman

71

Khi bạn tuyên bố rõ ràng rằng bạn muốn sự che giấu không phải là bảo mật, chúng tôi sẽ tránh khiển trách bạn vì điểm yếu của những gì bạn đề xuất :)

Vì vậy, bằng cách sử dụng 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)

Nếu ai đó nắm giữ cơ sở dữ liệu và cơ sở mã của bạn, họ sẽ có thể giải mã dữ liệu được mã hóa. Giữ secret_keyan toàn của bạn !


3
Tôi không nghĩ rằng điều này sẽ hoạt động trừ khi msg_text là bội số của 16 byte độ dài, vì mã hóa AES yêu cầu các khối có độ dài bội số của 16 byte. Một triển khai hoạt động cho msg_text có độ dài tùy ý sẽ cần thêm phần đệm vào chuỗi để làm cho nó có độ dài bội số là 16.
tohster

6
Ví dụ với padding: paste.ubuntu.com/11024555 Nó hoạt động với mật khẩu và độ dài tin nhắn tùy ý.
iman

3
@ Không, encryptchức năng cụ thể này là trạng thái dlitz.net/software/pycrypto/api/current/…, vì vậy bạn không nên thử và sử dụng lại nó.
Sẽ

1
@Will +1 Cảm ơn vì thông tin và liên kết. Đã cứu tôi khỏi một bản sửa lỗi rất tốn kém trong tương lai.
Ethan

3
re - "không bao giờ sử dụng ECB rõ ràng trong các hệ thống mạnh": Tôi chỉ đang thử nghiệm điều này cho niềm vui của riêng tôi. nhưng tôi đã thấy nhận xét trên trong mã của bạn. đối với những người trong chúng ta với nền tảng lý thuyết bảo mật / mã hóa / thông tin-rất tối thiểu, tại sao lại "không bao giờ sử dụng"? có thể cần một câu hỏi khác ... hoặc có thể có một liên kết trên đó.
Trevor Boyd Smith

68

Python không có lược đồ mã hóa tích hợp, không. Bạn cũng nên coi trọng việc lưu trữ dữ liệu được mã hóa; các lược đồ mã hóa tầm thường mà một nhà phát triển hiểu là không an toàn và một lược đồ đồ chơi cũng có thể bị một nhà phát triển ít kinh nghiệm nhầm với một chương trình an toàn. Nếu bạn mã hóa, hãy mã hóa đúng cách.

Tuy nhiên, bạn không cần phải làm nhiều việc để triển khai một chương trình mã hóa thích hợp. Trước hết, đừng phát minh lại bánh xe mật mã , hãy sử dụng thư viện mật mã đáng tin cậy để xử lý việc này cho bạn. Đối với Python 3, thư viện đáng tin cậy đó là cryptography.

Tôi cũng khuyến nghị rằng mã hóa và giải mã áp dụng cho các byte ; mã hóa tin nhắn văn bản thành byte trước; stringvalue.encode()mã hóa thành UTF8, dễ dàng hoàn nguyên lại bằng cách sử dụng bytesvalue.decode().

Cuối cùng nhưng không kém phần quan trọng, khi mã hóa và giải mã, chúng ta nói về khóa chứ không phải mật khẩu. Một chìa khóa không được con người ghi nhớ, nó là thứ mà bạn lưu trữ ở một vị trí bí mật nhưng máy có thể đọc được, trong khi mật khẩu thường có thể được con người đọc và ghi nhớ. Bạn có thể lấy một khóa từ mật khẩu, với một chút cẩn thận.

Nhưng đối với một ứng dụng web hoặc tiến trình chạy trong một cụm mà không có sự chú ý của con người để tiếp tục chạy nó, bạn muốn sử dụng một khóa. Mật khẩu dành cho khi chỉ người dùng cuối cần truy cập vào thông tin cụ thể. Ngay cả khi đó, bạn thường bảo mật ứng dụng bằng mật khẩu, sau đó trao đổi thông tin được mã hóa bằng khóa, có lẽ là khóa được gắn với tài khoản người dùng.

Mã hóa khóa đối xứng

Fernet - AES CBC + HMAC, rất khuyến khích

Các cryptographythư viện bao gồm các công thức Fernet , một công thức thực hành tốt nhất cho việc sử dụng mật mã. Fernet là một tiêu chuẩn mở , với việc triển khai sẵn sàng trong nhiều ngôn ngữ lập trình và nó đóng gói mã hóa AES CBC cho bạn với thông tin phiên bản, dấu thời gian và chữ ký HMAC để ngăn chặn việc giả mạo tin nhắn.

Fernet làm cho nó rất dễ dàng để mã hóa và giải mã tin nhắn giữ cho bạn an toàn. Đây là phương pháp lý tưởng để mã hóa dữ liệu một cách bí mật.

Tôi khuyên bạn nên sử dụng Fernet.generate_key()để tạo khóa an toàn. Bạn cũng có thể sử dụng mật khẩu (phần tiếp theo), nhưng khóa bí mật 32 byte đầy đủ (16 byte để mã hóa, cộng với 16 byte khác cho chữ ký) sẽ an toàn hơn hầu hết các mật khẩu bạn có thể nghĩ đến.

Khóa mà Fernet tạo là một bytesđối tượng có URL và các ký tự base64 an toàn cho tệp, vì vậy có thể in được:

from cryptography.fernet import Fernet

key = Fernet.generate_key()  # store in a secure location
print("Key:", key.decode())

Để mã hóa hoặc giải mã thông điệp, hãy tạo một Fernet()phiên bản với khóa đã cho và gọi Fernet.encrypt()hoặc Fernet.decrypt(), cả thông điệp văn bản rõ để mã hóa và mã thông báo được mã hóa đều là bytesđối tượng.

encrypt()và các decrypt()chức năng sẽ giống như sau:

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)

Bản giới thiệu:

>>> 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 với mật khẩu - khóa bắt nguồn từ mật khẩu, làm yếu đi phần nào tính bảo mật

Bạn có thể sử dụng mật khẩu thay vì khóa bí mật, miễn là bạn sử dụng phương pháp dẫn xuất khóa mạnh . Sau đó, bạn phải bao gồm muối và số lần lặp HMAC trong tin nhắn, vì vậy giá trị được mã hóa không còn tương thích với Fernet nữa nếu không tách muối, số và mã thông báo Fernet trước:

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)

Bản giới thiệu:

>>> message = 'John Doe'
>>> password = 'mypass'
>>> password_encrypt(message.encode(), password)
b'9Ljs-w8IRM3XT1NDBbSBuQABhqCAAAAAAFyJdhiCPXms2vQHO7o81xZJn5r8_PAtro8Qpw48kdKrq4vt-551BCUbcErb_GyYRz8SVsu8hxTXvvKOn9QdewRGDfwx'
>>> token = _
>>> password_decrypt(token, password).decode()
'John Doe'

Việc bao gồm muối trong đầu ra làm cho nó có thể sử dụng một giá trị muối ngẫu nhiên, do đó đảm bảo đầu ra được mã hóa được đảm bảo hoàn toàn ngẫu nhiên bất kể việc sử dụng lại mật khẩu hoặc lặp lại tin nhắn. Bao gồm số lần lặp lại đảm bảo rằng bạn có thể điều chỉnh để tăng hiệu suất CPU theo thời gian mà không làm mất khả năng giải mã các thông báo cũ hơn.

Một mật khẩu thể an toàn như một khóa ngẫu nhiên 32 byte Fernet, miễn là bạn tạo một mật khẩu ngẫu nhiên thích hợp từ một nhóm có kích thước tương tự. 32 byte cung cấp cho bạn 256 ^ 32 số khóa, vì vậy nếu bạn sử dụng bảng chữ cái gồm 74 ký tự (26 chữ hoa, 26 chữ thường, 10 chữ số và 12 ký hiệu có thể có), thì mật khẩu của bạn phải dài ít nhất math.ceil(math.log(256 ** 32, 74))== 42 ký tự. Tuy nhiên, số lượng lặp lại HMAC lớn hơn được lựa chọn kỹ càng có thể giảm thiểu phần nào việc thiếu entropy vì điều này làm cho kẻ tấn công trở nên tốn kém hơn rất nhiều khi xâm nhập vào.

Chỉ cần biết rằng việc chọn một mật khẩu ngắn hơn nhưng vẫn an toàn hợp lý sẽ không làm tê liệt kế hoạch này, nó chỉ làm giảm số lượng giá trị có thể mà kẻ tấn công bạo lực sẽ phải tìm kiếm; đảm bảo chọn một mật khẩu đủ mạnh cho các yêu cầu bảo mật của bạn .

Giải pháp thay thế

Che khuất

Một giải pháp thay thế là không mã hóa . Vignere nói: Đừng bị cám dỗ chỉ sử dụng mật mã bảo mật thấp hoặc triển khai tại nhà. Không có bảo mật trong các cách tiếp cận này, nhưng có thể khiến một nhà phát triển thiếu kinh nghiệm được giao nhiệm vụ duy trì mã của bạn trong tương lai ảo tưởng về bảo mật, tệ hơn là không có bảo mật nào cả.

Nếu tất cả những gì bạn cần là không rõ ràng, chỉ cần dữ liệu base64; đối với các yêu cầu an toàn cho URL, base64.urlsafe_b64encode()chức năng vẫn ổn. Không sử dụng mật khẩu ở đây, chỉ cần mã hóa và bạn đã hoàn tất. Tối đa, hãy thêm một số nén (như 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))

Điều này biến b'Hello world!'thành b'eNrzSM3JyVcozy_KSVEEAB0JBF4='.

Chỉ toàn vẹn

Nếu tất cả những gì bạn cần là một cách để đảm bảo rằng dữ liệu có thể được tin cậy để không bị thay đổi sau khi được gửi đến một ứng dụng khách không đáng tin cậy và được nhận lại, thì bạn muốn ký dữ liệu, bạn có thể sử dụng hmacthư viện cho việc này với SHA1 (vẫn được coi là an toàn cho việc ký HMAC ) hoặc tốt hơn:

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)

Sử dụng quyền này để ký dữ liệu, sau đó đính kèm chữ ký với dữ liệu và gửi chữ ký đó cho khách hàng. Khi bạn nhận lại dữ liệu, hãy tách dữ liệu và chữ ký và xác minh. Tôi đã đặt thuật toán mặc định thành SHA256, vì vậy bạn sẽ cần một khóa 32 byte:

key = secrets.token_bytes(32)

Bạn có thể muốn xem itsdangerousthư viện , gói tất cả điều này với tuần tự hóa và hủy tuần tự hóa ở các định dạng khác nhau.

Sử dụng mã hóa AES-GCM để cung cấp mã hóa và tính toàn vẹn

Fernet xây dựng dựa trên AEC-CBC với chữ ký HMAC để đảm bảo tính toàn vẹn của dữ liệu được mã hóa; kẻ tấn công độc hại không thể cung cấp dữ liệu vô nghĩa cho hệ thống của bạn để giữ cho dịch vụ của bạn luôn hoạt động trong các vòng kết nối với đầu vào không tốt, vì bản mã đã được ký.

Các chế độ Galois / Counter mã khối sản xuất bản mã và thẻ để phục vụ cùng một mục đích, như vậy có thể được sử dụng để phục vụ cho mục đích tương tự. Nhược điểm là không giống như Fernet, không có công thức một kích cỡ phù hợp với tất cả dễ sử dụng để sử dụng lại trên các nền tảng khác. AES-GCM cũng không sử dụng đệm, do đó, bản mã mã hóa này khớp với độ dài của thông điệp đầu vào (trong khi Fernet / AES-CBC mã hóa thông báo thành các khối có độ dài cố định, che khuất độ dài thông báo phần nào).

AES256-GCM lấy bí mật 32 byte thông thường làm khóa:

key = secrets.token_bytes(32)

sau đó sử dụng

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()

Tôi đã bao gồm một dấu thời gian để hỗ trợ các trường hợp sử dụng theo thời gian tồn tại tương tự như Fernet hỗ trợ.

Các cách tiếp cận khác trên trang này, bằng Python 3

AES CFB - giống như CBC nhưng không cần đệm

Đây là cách tiếp cận mà All Іѕ Vаиітy tuân theo, mặc dù không chính xác. Đây là cryptographyphiên bản, nhưng lưu ý rằng tôi đưa IV vào bản mã , nó không nên được lưu trữ dưới dạng toàn cục (việc sử dụng lại IV sẽ làm suy yếu tính bảo mật của khóa và lưu trữ dưới dạng toàn cục mô-đun có nghĩa là nó sẽ được tạo lại lệnh gọi Python tiếp theo, hiển thị tất cả bản mã không thể giải mã được):

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()

Điều này thiếu trang bị bổ sung của chữ ký HMAC và không có dấu thời gian; bạn phải tự thêm chúng.

Phần trên cũng minh họa việc dễ dàng kết hợp các khối xây dựng mật mã cơ bản không chính xác như thế nào; Tất cả việc xử lý sai giá trị IV của Іѕ Vаиітy có thể dẫn đến vi phạm dữ liệu hoặc tất cả các thư được mã hóa không thể đọc được vì IV bị mất. Thay vào đó, sử dụng Fernet sẽ bảo vệ bạn khỏi những sai lầm như vậy.

AES ECB - không an toàn

Nếu trước đây bạn đã triển khai mã hóa AES ECB và vẫn cần hỗ trợ điều này trong Python 3, thì bạn cũng có thể làm như vậy cryptography. Cảnh báo tương tự cũng được áp dụng, ECB không đủ an toàn cho các ứng dụng trong đời thực . Triển khai lại câu trả lời đó cho Python 3, thêm tính năng tự động xử lý phần đệm:

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()

Một lần nữa, điều này thiếu chữ ký HMAC và bạn không nên sử dụng ECB. Trên đây chỉ là để minh họa rằng cryptographycó thể xử lý các khối xây dựng mật mã phổ biến, ngay cả những khối mà bạn thực sự không nên sử dụng.


51

"Encoded_c" được đề cập trong câu trả lời mật mã Vigenere của @ smehmood phải là "key_c".

Đây là các chức năng mã hóa / giải mã đang hoạt động.

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)

Tuyên bố từ chối trách nhiệm: Như ngụ ý trong các nhận xét, điều này không nên được sử dụng để bảo vệ dữ liệu trong một ứng dụng thực, trừ khi bạn đọc điều này và không ngại nói chuyện với luật sư:

Có gì sai với mã hóa XOR?


2
Rất hữu ích, cảm ơn. Tôi đã đăng tải một phiên bản Python 3 dưới đây (nó trông xấu xí trong các ý kiến)
Ryan Barrett

1
"Định luật Schneier" : Bất kỳ ai, từ những người nghiệp dư khó hiểu nhất đến nhà mật mã giỏi nhất, đều có thể tạo ra một thuật toán mà bản thân anh ta không thể phá vỡ. Đừng sử dụng cái này, nó thậm chí còn không an toàn.
zaph

3
Tuyệt quá! Phiên bản này cũng hoạt động với các chuỗi có dấu, trong khi phiên bản của @ smehmood thì không. Ví dụ sử dụng:, encodedmsg = encode('mypassword', 'this is the message éçàèç"') print encodedmsg print decode('mypassword', encodedmsg)nó hoạt động tốt.
Basj

2
Đây là một plugin văn bản Sublime dựa trên mã này, cho phép dễ dàng mã hóa / giải mã văn bản bằng CTRL + SHIFT + P rồi đến "Eeencode" hoặc "Dddecode".
Basj

2
@basj Cảm ơn bạn vì ví dụ
sk03

49

Đây là phiên bản Python 3 của các hàm từ câu trả lời của @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)

Mã hóa / giải mã bổ sung là cần thiết vì Python 3 đã chia chuỗi / mảng byte thành hai khái niệm khác nhau và cập nhật API của chúng để phản ánh điều đó ..


4
Cảm ơn Ryan, fwiw bạn typo'd @qniell
qneill

3
Đối với những người tự hỏi, sự khác biệt là .encode()).decode(). ngược lại encode().decode()ở dòng thứ hai trong decode().
RolfBly

2
Rất tiếc, mã được mã hóa không thực sự duy nhất, tôi đã chạy thử nghiệm và nó hiển thị mọi khóa mã của 11,22,33,44, ..., 88,99,111,222, ... luôn có một mã khác giống như trước đây. Nhưng tôi đánh giá cao nó
Hzzkygcs

1
@Ryan Barrett, có thể kiểm tra độ chính xác của mật khẩu khi giải mã không. Giả sử tôi gửi một chuỗi mã hóa cho người biết khóa, điều gì sẽ xảy ra nếu anh ta nhập khóa với lỗi đánh máy? Người giải mã vẫn cho anh ta một chuỗi "đã giải mã", nhưng nó không phải là đúng, làm sao anh ta biết được?
Heinz

26

Tuyên bố từ chối trách nhiệm: Như đã đề cập trong các nhận xét, điều này không nên được sử dụng để bảo vệ dữ liệu trong một ứng dụng thực.

Có gì sai với mã hóa XOR?

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

https://github.com/hellman/xortool


Như đã được đề cập, thư viện PyCrypto chứa một bộ mật mã. XOR "cipher" có thể được sử dụng để thực hiện công việc bẩn thỉu nếu bạn không muốn tự làm:

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))

Mật mã hoạt động như sau mà không cần phải chèn bản rõ:

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

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

Ghi có vào https://stackoverflow.com/a/2490376/241294 cho các chức năng mã hóa / giải mã base64 (Tôi là một người mới chơi python).


lưu ý: mô-đun Crypto được cài đặt trong python3 bởi pycrptop được cài đặt, không phải Crypto. sudo pip3 install pycrypto.
Nikhil VJ

2
lưu ý: không thể cài đặt pycrypto trên herokuapp. Tôi thấy đăng tải này .. dường như nói rằng gói pycrypto đã được thay thế bằng nhau gọi là pycryptodome insteal, và rằng phương pháp XOR đã được chấp nhận: github.com/digitalocean/netbox/issues/1527
Nikhil VJ

2
Đừng bao giờ sử dụng phương pháp này , hãy lưu ý mô tả về 'mật mã' này trong tài liệu : Mật mã đồ chơi XOR, XOR là một trong những mật mã dòng đơn giản nhất. Mã hóa và giải mã được thực hiện bởi dữ liệu XOR-ing với một dòng khóa được thực hiện bằng cách ghép khóa. Không sử dụng nó cho các ứng dụng thực tế!.
Martijn Pieters

@MartijnPieters bạn nói đúng. Hy vọng rằng bản chỉnh sửa của tôi đã làm rõ điểm đó.
poida

12

Đây là cách triển khai Mã hóa và Giải mã An toàn URL bằng AES (PyCrypto) và 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))

Nếu bạn gặp phải một số vấn đề như thế này, hãy https://bugs.python.org/issue4329 ( TypeError: character mapping must return integer, None or unicode) sử dụng str(cipher)trong khi giải mã như sau:

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

Kiểm tra:

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

Lỗi với Windows x64 + Python 3.6 + PyCryptodome (như pycrypto bị phản đối): TypeError: Object type <class 'str'> cannot be passed to C code.
Basj

@Basj aww xin lỗi .. Tôi không sử dụng windows nên không thể sửa được.
Tất cả Іѕ Vаиітy

Không tạo IV và lưu trữ nó ở cấp mô-đun, bạn cần đưa IV vào thông báo bản mã được trả về! Bây giờ bạn đã giới thiệu hai vấn đề: khởi động lại quy trình Python cung cấp cho bạn một IV mới khiến bạn không thể giải mã các thư đã mã hóa trước đó và trong thời gian chờ đợi, bạn đang sử dụng lại IV cho nhiều thư, điều này làm giảm hiệu quả bảo mật trở lại mức ECB.
Martijn Pieters

1
@ AllІѕVаиітy Đã giải quyết với b'...', tôi đã chỉnh sửa câu trả lời để tham khảo trong tương lai!
Basj

8

Các chức năng mã hóa / giải mã hoạt động trong python3 (được điều chỉnh rất ít từ câu trả lời của 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

Cảm ơn vì một số câu trả lời tuyệt vời. Không có gì nguyên bản để thêm, nhưng đây là một số bản viết lại tiến bộ của câu trả lời của qneill bằng cách sử dụng một số cơ sở Python hữu ích. Tôi hy vọng bạn đồng ý rằng họ đơn giản hóa và làm rõ mã.

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()- ghép nối các mục trong danh sách với chỉ mục của chúng

lặp lại các ký tự trong một chuỗi

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)

xây dựng danh sách bằng cách hiểu danh sách

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)

Thường thì trong Python không cần chỉ mục danh sách. Loại bỏ các biến chỉ số vòng lặp hoàn toàn bằng cách sử dụng zip và 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)

và một số bài kiểm tra ...

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 rất tốt, tiến triển tốt của trăn và các bài kiểm tra cũng khởi động. Để ghi nhận chính xác, tôi chỉ đang sửa một lỗi trong câu trả lời ban đầu của Smehmood stackoverflow.com/a/2490718/468252 .
qneill

4

Nếu bạn muốn an toàn, bạn có thể sử dụng Fernet, một loại mật mã tốt. Bạn có thể sử dụng "muối" tĩnh nếu không muốn lưu trữ riêng - bạn sẽ chỉ mất từ ​​điển và phòng chống tấn công cầu vồng. Tôi chọn nó vì tôi có thể chọn mật khẩu dài hoặc ngắn, điều này không dễ dàng với 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

Nếu điều đó quá phức tạp, ai đó đã đề xuất simplecrypt

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

Chỉ cần tạo một muối và đưa nó vào kết quả mã hóa để mật khẩu và tin nhắn lặp lại vẫn dẫn đến kết quả ngẫu nhiên. Bao gồm cả giá trị lặp lại để thuật toán chứng minh trong tương lai nhưng vẫn có thể giải mã thông báo bằng cách sử dụng số lần lặp khác.
Martijn Pieters

3

Bất cứ ai đã đến đây (và người chơi) dường như đang tìm kiếm một lớp lót với không nhiều thiết lập, mà các câu trả lời khác không cung cấp. Vì vậy, tôi đang đưa ra base64.

Bây giờ, hãy nhớ rằng đây chỉ là sự xáo trộn cơ bản và nằm trong ** KHÔNG CÓ CÁCH NÀO ĐỂ BẢO MẬT ** , nhưng đây là một số lớp lót:

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'

Một số điều cần lưu ý:

  • bạn sẽ muốn tự xử lý mã hóa / giải mã byte thành chuỗi nhiều hơn / ít hơn, tùy thuộc vào I / O của bạn. Nhìn vào bytes()bytes::decode()
  • base64 có thể dễ dàng nhận ra bởi các loại ký tự được sử dụng và thường kết thúc bằng =ký tự. Những người như tôi hoàn toàn đi xung quanh giải mã chúng trong bảng điều khiển javascript khi chúng tôi nhìn thấy chúng trên các trang web. Nó dễ dàng như btoa(string)(js)
  • thứ tự là khóa + dữ liệu, như trong b64, những ký tự nào xuất hiện ở cuối phụ thuộc vào những ký tự nào ở đầu (vì hiệu số byte. Wikipedia có một số giải thích hay). Trong trường hợp này, phần đầu của chuỗi được mã hóa sẽ giống nhau đối với mọi thứ được mã hóa bằng khóa đó. Điểm cộng là dữ liệu sẽ khó bị xáo trộn hơn. Làm theo cách khác sẽ dẫn đến phần dữ liệu hoàn toàn giống nhau đối với mọi người, bất kể khóa nào.

Bây giờ, nếu những gì bạn muốn thậm chí không cần bất kỳ loại khóa nào, mà chỉ cần một số xáo trộn, bạn có thể lại chỉ sử dụng base64 mà không cần bất kỳ loại khóa nào:

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
Có, nếu bạn không lo lắng về bảo mật, thì base64 là cách tốt hơn là mã hóa.
Martijn Pieters

Về các câu trả lời khác không phải là một chữ lót: đó không phải là vấn đề của câu hỏi. Họ yêu cầu hai chức năng để gọi. Và Fernet(key).encrypt(message)chỉ là một biểu thức giống như lệnh gọi base64 của bạn.
Martijn Pieters

Và bạn nên loại bỏ những keyhoàn toàn. Rất nhiều nhà phát triển sẽ sao chép và dán từ Stack Overflow mà không chú ý và sẽ cho rằng khóa là bí mật. Nếu bạn phải bao gồm nó, thì ít nhất là không sử dụng nó và cảnh báo hoặc nêu ra một ngoại lệ nếu vẫn sử dụng. Đừng đánh giá thấp sự ngu ngốc của văn hóa sao chép và dán và trách nhiệm của bạn trong việc cung cấp các chức năng lành mạnh.
Martijn Pieters

3

Tôi sẽ đưa ra 4 giải pháp:

1) Sử dụng mã hóa Fernet với cryptographythư viện

Đây là giải pháp sử dụng gói cryptographymà bạn có thể cài đặt như bình thường với 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')

Bạn có thể thích ứng với muối, số lần lặp lại của riêng mình, v.v. Đoạn mã này không xa lắm so với câu trả lời của @ HCLivess nhưng mục tiêu là ở đây có các chức năng encryptvà sẵn sàng sử dụng decrypt. Nguồn: https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet .

Lưu ý: sử dụng .encode().decode()ở mọi nơi nếu bạn muốn chuỗi 'John Doe'thay vì byte như b'John Doe'.


2) Mã hóa AES đơn giản với Cryptothư viện

Điều này hoạt động với 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

Lưu ý: bạn có thể xóa base64.b64encode.b64decodenếu bạn không muốn đầu ra văn bản có thể đọc được và / hoặc nếu bạn muốn lưu bản mã vào đĩa dưới dạng tệp nhị phân.


3) AES sử dụng chức năng dẫn xuất khóa mật khẩu tốt hơn và khả năng kiểm tra nếu "nhập sai mật khẩu", với Cryptothư viện

Giải pháp 2) với "chế độ CFB" của AES là ok, nhưng có hai nhược điểm: thực tế là SHA256(password)có thể dễ dàng cưỡng bức bằng bảng tra cứu và không có cách nào để kiểm tra xem có nhập sai mật khẩu hay không. Điều này được giải quyết ở đây bằng cách sử dụng AES trong "chế độ GCM", như đã thảo luận trong AES: làm thế nào để phát hiện rằng một mật khẩu xấu đã được nhập? Phương pháp này để thông báo “Mật khẩu bạn đã nhập sai” có an toàn không? :

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) Sử dụng RC4 (không cần thư viện)

Phỏng theo 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'

(Đã lỗi thời kể từ các chỉnh sửa mới nhất, nhưng được giữ lại để tham khảo trong tương lai): Tôi gặp sự cố khi sử dụng Windows + Python 3.6 + tất cả các câu trả lời liên quan đến pycrypto(không thể pip install pycryptotrên Windows) hoặc pycryptodome(các câu trả lời ở đây from Crypto.Cipher import XORkhông thành công vì XORkhông được hỗ trợ bởi pycryptofork này ; và các giải pháp sử dụng ... AEScũng không thành công với TypeError: Object type <class 'str'> cannot be passed to C code). Ngoài ra, thư viện simple-cryptpycryptophụ thuộc, vì vậy nó không phải là một tùy chọn.


1
Bạn không muốn mã hóa muối và số lần lặp lại; tạo một muối ngẫu nhiên và làm cho số lần lặp lại có thể định cấu hình khi mã hóa, đưa thông tin đó vào kết quả mã hóa, trích xuất và sử dụng các giá trị khi giải mã. Muối bảo vệ bạn khỏi sự nhận dạng tầm thường đối với các mật khẩu được sử dụng lại trên một tin nhắn nhất định, số lần lặp lại sẽ chứng minh thuật toán trong tương lai.
Martijn Pieters

Tất nhiên là có @MartijnPieters, nhưng mục tiêu ở đây là có một mã đơn giản cho các mục đích đơn giản, theo yêu cầu của OP, với hai tham số : văn bản thuần túy + ​​mật khẩu. Tất nhiên đối với kịch bản phức tạp hơn (tức là cơ sở dữ liệu), bạn sẽ sử dụng tất cả các tham số bổ sung này.
Basj

Không cần thêm thông số! Tôi bao gồm thông tin đó được mã hóa trong giá trị trả về không rõ ràng của password_encrypt().
Martijn Pieters

@MartijnPieters Thực sự là giải pháp tốt.
Basj

2

Điều này hoạt động nhưng độ dài mật khẩu phải chính xác 8. Điều này đơn giản và yêu cầu 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

ĐẦU RA:

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

Không sử dụng IV cố định! Ngẫu nhiên IV và bao gồm nó với bản mã thay thế. Nếu không, bạn cũng có thể sử dụng chế độ ECB; Các tin nhắn văn bản thuần túy lặp đi lặp lại rất khó nhận ra.
Martijn Pieters

Ngoài ra, dự án pyDes dường như đã chết; trang chủ đã biến mất và bản phát hành cuối cùng trên PyPI hiện đã được 9 tuổi.
Martijn Pieters

2

Một cách triển khai khác của mã @qneill bao gồm tổng kiểm tra CRC của thư gốc, nó đưa ra một ngoại lệ nếu kiểm tra không thành công:

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

Bạn có thể sử dụng AES để mã hóa chuỗi của mình bằng mật khẩu. Mặc dù vậy, bạn sẽ muốn chọn một mật khẩu đủ mạnh để mọi người không thể dễ dàng đoán được nó là gì (xin lỗi, tôi không thể giúp gì được. Tôi là người rất muốn bảo mật).

AES mạnh mẽ với kích thước khóa tốt, nhưng nó cũng dễ sử dụng với PyCrypto.


3
Cảm ơn Alan. Nhưng để làm rõ, tôi không mã hóa mật khẩu. Trong ví dụ trên, tôi đang mã hóa chuỗi "John Doe" theo mật khẩu "mypass", đây là mật khẩu đơn giản mà tôi sử dụng trong mã nguồn của mình. Mật khẩu người dùng không liên quan, không có bất kỳ thông tin rất nhạy cảm nào khác. Tôi đã chỉnh sửa câu hỏi của mình để làm rõ điều này.
RexE

1
AES là tuyệt vời, nếu được sử dụng đúng cách. Tuy nhiên, rất dễ sử dụng nó không chính xác; có ít nhất một câu trả lời ở đây sử dụng chế độ mật mã khối không an toàn, hai câu trả lời khác xử lý giá trị IV một cách vụng về. Tốt hơn là sử dụng một thư viện tốt với công thức được xác định rõ ràng như Fernet!
Martijn Pieters

Thực ra, đó là một quan sát rất sắc sảo. Tôi đã lần mò IV một lần.
Alan

0

Thư viện bên ngoài cung cấp các thuật toán mã hóa khóa bí mật.

Ví dụ: Cyphermô-đun trong PyCrypto cung cấp lựa chọn nhiều thuật toán mã hóa:

  • 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 là một Pythontrình bao bọc cho OpenSSL và cung cấp (trong số các chức năng khác) một thư viện mật mã đa năng có sức mạnh toàn diện. Bao gồm các mật mã đối xứng (như AES).


0

nếu bạn muốn mã hóa an toàn:

đối với python 2, bạn nên sử dụng keyczar http://www.keyczar.org/

cho python 3, cho đến khi có keyczar, tôi đã viết simple-crypt http://pypi.python.org/pypi/simple-crypt

cả hai điều này sẽ sử dụng tính năng tăng cường khóa để làm cho chúng an toàn hơn hầu hết các câu trả lời khác ở đây. và vì chúng rất dễ sử dụng, bạn có thể muốn sử dụng chúng ngay cả khi bảo mật không quan trọng ...


Từ kho lưu trữ Keyczar : Lưu ý quan trọng: Keyczar không được dùng nữa. Các nhà phát triển Keyczar khuyên bạn nên sử dụng Tink , nhưng không có phiên bản Python nào của Tink.
Martijn Pieters

0

Vì vậy, không có nhiệm vụ quan trọng nào được mã hóa và bạn chỉ muốn mã hóa để che khuất .

Hãy để tôi trình bày mật mã của caeser

nhập mô tả hình ảnh ở đây

Caesar's cipher hay Caesar shift, là một trong những kỹ thuật mã hóa đơn giản nhất và được biết đến rộng rãi nhất. Nó là một loại mật mã thay thế trong đó mỗi chữ cái trong bản rõ được thay thế bằng một chữ cái với một số vị trí cố định trong bảng chữ cái. Ví dụ, với sự dịch chuyển sang trái là 3, D sẽ được thay thế bằng A, E sẽ trở thành B, v.v.

Mã mẫu để bạn tham khảo:

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))

Ưu điểm: nó đáp ứng yêu cầu của bạn và đơn giản và không mã hóa được.

Nhược điểm: có thể bị bẻ khóa bởi các thuật toán brute force đơn giản (rất ít có khả năng bất kỳ ai cố gắng vượt qua tất cả các kết quả phụ).


0

Thêm một mã nữa với giải mã và mã hóa để tham khảo

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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.