Prosty sposób na zakodowanie ciągu znaków na podstawie hasła?

123

Czy Python ma wbudowany, prosty sposób kodowania / dekodowania ciągów za pomocą hasła?

Coś takiego:

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

Zatem ciąg „John Doe” zostaje zaszyfrowany jako „sjkl28cn2sx0”. Aby uzyskać oryginalny ciąg, „odblokowałbym” ten ciąg za pomocą klucza „mypass”, który jest hasłem w moim kodzie źródłowym. Chciałbym, aby był to sposób, w jaki mogę zaszyfrować / odszyfrować dokument Word za pomocą hasła.

Chciałbym użyć tych zaszyfrowanych ciągów jako parametrów adresu URL. Moim celem jest zaciemnianie, a nie silne zabezpieczenia; nic krytycznego dla misji nie jest kodowane. Zdaję sobie sprawę, że mógłbym użyć tabeli bazy danych do przechowywania kluczy i wartości, ale staram się być minimalistyczny.

RexE
źródło
28
Termin „hasło” jest tutaj niewłaściwy. Używasz tego jako KLUCZA kryptograficznego i powinieneś używać tej terminologii, aby uniknąć nieporozumień w swoich pytaniach, a także we wszelkich dokumentach, komentarzach, specyfikacjach, planach testów itp.
Jim Dennis
2
„Chciałbym, aby to był sposób, w jaki mogę zaszyfrować / odszyfrować dokument Worda za pomocą hasła.”, Word ma już wbudowaną opcję szyfrowania dokumentów, jeśli potrzebujesz tylko zaszyfrować dokumenty Word.
Byron Filer
2
Co ciekawe, zgodnie z tym artykułem badawczym dotyczącym pułapek przechowywania haseł, takich jak ten , programiści używający przepełnienia stosu mają tendencję do tworzenia mniej bezpiecznego kodu. Ojej, zastanawiam się dlaczego?
las
Należy również przeczytać tę odpowiedź od ochrony.
SE
Nie wystarczy po prostu zaimplementować kodowanie / dekodowanie
luckyging3r

Odpowiedzi:

70

Zakładając, że szukasz tylko prostego zaciemnienia, które zasłoni rzeczy przed bardzo przypadkowym obserwatorem i nie chcesz korzystać z bibliotek stron trzecich. Poleciłbym coś takiego jak szyfr Vigenere. Jest to jeden z najsilniejszych z prostych starożytnych szyfrów.

Szyfr Vigenère'a

To szybkie i łatwe do wdrożenia. Coś jak:

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)

Dekodowanie jest prawie takie samo, z wyjątkiem odejmowania klucza.

Znacznie trudniej jest złamać, jeśli kodowane łańcuchy są krótkie i / lub jeśli trudno jest odgadnąć długość użytego hasła.

Jeśli szukasz czegoś kryptograficznego, PyCrypto jest prawdopodobnie najlepszym rozwiązaniem, chociaż poprzednie odpowiedzi pomijają niektóre szczegóły: tryb EBC w PyCrypto wymaga, aby twoja wiadomość składała się z wielokrotności 16 znaków. Więc musisz padać. Ponadto, jeśli chcesz ich używać jako parametrów adresu URL, użyj base64.urlsafe_b64_encode()zamiast standardowego. Spowoduje to zastąpienie kilku znaków w alfabecie base64 znakami bezpiecznymi dla adresów URL (jak sugeruje nazwa).

Jednak powinieneś być ABSOLUTNIE pewny, że ta bardzo cienka warstwa zaciemniania wystarczy dla twoich potrzeb przed użyciem. Artykuł w Wikipedii, do którego odsyłam, zawiera szczegółowe instrukcje dotyczące łamania szyfru, aby każdy z umiarkowaną determinacją mógł go łatwo złamać.

smehmood
źródło
6
Naprawiłem skrypt smehmood i dodałem funkcję dekodowania gist.github.com/ilogik/6f9431e4588015ecb194
Adrian Mester
3
Uwaga! Kod smehmood i poprawka Adriana Mestera działają tylko dla łańcuchów zawierających znaki z dolnego zakresu ascii! Zobacz pierwszeństwo operatora%, dane wejściowe unicode itp. Zobacz odpowiedź qneilla na działający kod
le_m
2
@smehmood Otrzymuję następujący błąd'str' object cannot be interpreted as an integer
Rohit Khatri
3
„for i in xrange (string)” może wymagać zmiany na „for i in xrange (len (string))”
user3113626
2
koder i dekoder dla pythona 2 i 3: gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1a
iman
71

Ponieważ wyraźnie stwierdzasz, że wolisz tajemnicę, a nie bezpieczeństwo, unikniemy upominania Cię za słabość tego, co sugerujesz :)

Tak więc, używając 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)

Jeśli ktoś przejmie Twoją bazę danych i bazę kodu, będzie mógł odszyfrować zaszyfrowane dane. Dbaj o swoje secret_keybezpieczeństwo!

Będzie
źródło
3
Nie sądzę, aby to zadziałało, chyba że msg_text jest wielokrotnością 16 bajtów, ponieważ szyfrowanie AES wymaga bloków o długości 16 bajtów. Działająca implementacja dla msg_text o dowolnej długości wymagałaby dopełnienia łańcucha, aby uzyskać wielokrotność 16 długości.
tohster
6
Przykład z dopełnieniem: paste.ubuntu.com/11024555 Działa z dowolnym hasłem i długością wiadomości.
iman
3
@Ethan no, ta konkretna encryptfunkcja jest stanową dlitz.net/software/pycrypto/api/current/, więc nie powinieneś próbować jej używać ponownie.
Będzie
1
@Will +1 Dzięki za informacje i link. Uratował mnie przed bardzo kosztowną naprawą błędów w przyszłości.
Ethan
3
re - „oczywiście nigdy nie używaj EBC w silnych systemach”: po prostu eksperymentuję z tym dla własnej przyjemności. ale widziałem powyższy komentarz w twoim kodzie. dla tych z nas, którzy mają bardzo minimalne doświadczenie w zakresie bezpieczeństwa / szyfrowania / teorii informacji, dlaczego „nigdy nie używać”? może potrzebuje innego pytania ... a może jest na nim link.
Trevor Boyd Smith
69

Python nie ma wbudowanych schematów szyfrowania, nie. Powinieneś również poważnie potraktować przechowywanie zaszyfrowanych danych; trywialne schematy szyfrowania, które jeden programista uważa za niepewne, a schemat zabawkowy może być mylony z bezpiecznym schematem przez mniej doświadczonego programistę. Jeśli szyfrujesz, zaszyfruj poprawnie.

Jednak nie musisz wykonywać wiele pracy, aby zaimplementować odpowiedni schemat szyfrowania. Przede wszystkim nie wymyślaj ponownie koła kryptograficznego , użyj zaufanej biblioteki kryptograficznej, aby załatwić to za Ciebie. W przypadku Pythona 3 ta zaufana biblioteka to cryptography.

Zalecam również, aby szyfrowanie i deszyfrowanie dotyczyło bajtów ; najpierw zakoduj wiadomości tekstowe do bajtów; stringvalue.encode()koduje do UTF8, łatwo przywracany ponownie przy użyciu bytesvalue.decode().

Wreszcie, podczas szyfrowania i odszyfrowywania mówimy o kluczach , a nie hasłach. Klucz nie powinien być niezapomniany dla człowieka, jest to coś, co przechowujesz w tajnym miejscu, ale można je odczytać maszynowo, podczas gdy hasło często może być czytelne dla człowieka i zapamiętane. Państwo może czerpać klucz z hasłem, przy odrobinie opieki.

Ale aby aplikacja internetowa lub proces działały w klastrze bez ludzkiej uwagi, aby go nadal uruchamiać, chcesz użyć klucza. Hasła są używane, gdy tylko użytkownik końcowy potrzebuje dostępu do określonych informacji. Nawet wtedy zazwyczaj zabezpieczasz aplikację hasłem, a następnie wymieniasz zaszyfrowane informacje za pomocą klucza, być może dołączonego do konta użytkownika.

Szyfrowanie klucza symetrycznego

Fernet - AES CBC + HMAC, zdecydowanie zalecane

cryptographyBiblioteka zawiera przepis Fernet , najlepszy przepis na praktykach za pomocą kryptografii. Fernet to otwarty standard , z gotowymi implementacjami w szerokiej gamie języków programowania, który zawiera szyfrowanie AES CBC wraz z informacjami o wersji, sygnaturą czasową i podpisem HMAC, aby zapobiec manipulowaniu wiadomościami.

Fernet bardzo ułatwia szyfrowanie i odszyfrowywanie wiadomości oraz zapewnia bezpieczeństwo. Jest to idealna metoda szyfrowania danych za pomocą sekretu.

Zalecam użycie Fernet.generate_key()do wygenerowania bezpiecznego klucza. Możesz też użyć hasła (następna sekcja), ale pełny 32-bajtowy tajny klucz (16 bajtów do zaszyfrowania plus kolejne 16 do podpisu) będzie bezpieczniejszy niż większość haseł, jakie możesz sobie wyobrazić.

Klucz, który generuje Fernet, to bytesobiekt z adresem URL i plikami bezpiecznymi znakami base64, więc można go wydrukować:

from cryptography.fernet import Fernet

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

Aby zaszyfrować lub odszyfrować wiadomości, utwórz Fernet()wystąpienie z podanym kluczem i wywołaj Fernet.encrypt()lub Fernet.decrypt(), zarówno wiadomość w postaci zwykłego tekstu do zaszyfrowania, jak i zaszyfrowany token są bytesobiektami.

encrypt()a decrypt()funkcje wyglądałyby następująco:

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)

Próbny:

>>> 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 z hasłem - klucz pochodzący z hasła, nieco osłabia bezpieczeństwo

Możesz użyć hasła zamiast tajnego klucza, pod warunkiem, że używasz silnej metody wyprowadzania klucza . Następnie musisz uwzględnić sól i liczbę iteracji HMAC w wiadomości, więc zaszyfrowana wartość nie jest już zgodna z Fernetem bez uprzedniego oddzielenia soli, liczby i tokenu Ferneta:

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)

Próbny:

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

Dołączenie soli do danych wyjściowych umożliwia użycie losowej wartości soli, co z kolei gwarantuje, że zaszyfrowane dane wyjściowe będą w pełni losowe, niezależnie od ponownego użycia hasła lub powtórzenia wiadomości. Uwzględnienie liczby iteracji zapewnia, że ​​możesz dostosować się do wzrostu wydajności procesora w czasie bez utraty możliwości odszyfrowania starszych wiadomości.

Samo hasło może być równie bezpieczne, jak 32-bajtowy losowy klucz Fernet, pod warunkiem że wygenerujesz odpowiednio losowe hasło z puli o podobnej wielkości. 32 bajty to 256 ^ 32 liczby kluczy, więc jeśli używasz alfabetu składającego się z 74 znaków (26 górnych, 26 dolnych, 10 cyfr i 12 możliwych symboli), to Twoje hasło powinno mieć co najmniej math.ceil(math.log(256 ** 32, 74))== 42 znaki. Jednak dobrze dobrana większa liczba iteracji HMAC może nieco złagodzić brak entropii, ponieważ sprawia to, że atak brutalny jest znacznie droższy.

Po prostu wiedz, że wybranie krótszego, ale wciąż rozsądnie bezpiecznego hasła nie zepsuje tego schematu, po prostu zmniejszy liczbę możliwych wartości, które atakujący musiałby przeszukać. upewnij się, że wybrałeś wystarczająco silne hasło dla swoich wymagań bezpieczeństwa .

Alternatywy

Niewyraźne

Alternatywą nie jest szyfrowanie . Nie ulegaj pokusie, aby po prostu użyć szyfru o niskim poziomie bezpieczeństwa lub własnej implementacji, powiedzmy Vignere. W tych podejściach nie ma zabezpieczeń, ale może dać niedoświadczonemu programistowi, któremu powierzono zadanie utrzymania twojego kodu w przyszłości, iluzję bezpieczeństwa, która jest gorsza niż brak bezpieczeństwa.

Jeśli potrzebujesz tylko niejasności, po prostu base64 dane; w przypadku wymagań dotyczących bezpieczeństwa adresów URL base64.urlsafe_b64encode()funkcja jest w porządku. Nie używaj tutaj hasła, po prostu zakoduj i gotowe. Co najwyżej dodaj trochę kompresji (na przykład 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))

To zmienia się b'Hello world!'w b'eNrzSM3JyVcozy_KSVEEAB0JBF4='.

Tylko uczciwość

Jeśli potrzebujesz tylko sposobu, aby upewnić się, że dane mogą być niezmienione po wysłaniu do niezaufanego klienta i odebraniu z powrotem, to chcesz podpisać dane, możesz użyć do tego hmacbiblioteki za pomocą SHA1 (nadal uważany za bezpieczny do podpisywania HMAC ) lub lepszy:

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)

Użyj tego do podpisania danych, a następnie dołącz podpis do danych i wyślij go do klienta. Po otrzymaniu danych podziel je i podpis, a następnie zweryfikuj. Ustawiłem domyślny algorytm na SHA256, więc będziesz potrzebować 32-bajtowego klucza:

key = secrets.token_bytes(32)

Możesz zajrzeć do itsdangerousbiblioteki , która pakuje to wszystko z serializacją i deserializacją w różnych formatach.

Korzystanie z szyfrowania AES-GCM w celu zapewnienia szyfrowania i integralności

Fernet opiera się na AEC-CBC z podpisem HMAC, aby zapewnić integralność zaszyfrowanych danych; złośliwy napastnik nie może przesyłać do twojego systemu nonsensownych danych, aby Twoja usługa była zajęta działaniem w kółko ze złymi danymi wejściowymi, ponieważ szyfrogram jest podpisany.

Galois / Licznik szyfr blokowy tryb wytwarza szyfrogram i tag służyć temu samemu celowi, więc może być używany, aby służyć tym samym celom. Wadą jest to, że w przeciwieństwie do Ferneta nie ma łatwego w użyciu uniwersalnego przepisu do ponownego wykorzystania na innych platformach. AES-GCM również nie używa dopełnienia, więc ten zaszyfrowany tekst jest zgodny z długością wiadomości wejściowej (podczas gdy Fernet / AES-CBC szyfruje wiadomości do bloków o stałej długości, nieco zaciemniając długość wiadomości).

AES256-GCM przyjmuje zwykły 32-bajtowy sekret jako klucz:

key = secrets.token_bytes(32)

następnie użyj

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

Dodałem sygnaturę czasową, aby obsługiwać te same przypadki użycia, które obsługuje Fernet.

Inne podejścia na tej stronie, w Pythonie 3

AES CFB - jak CBC, ale bez potrzeby stosowania podkładek

Jest to podejście, które stosuje All Іѕ Vаиітy , aczkolwiek niepoprawne. To jest cryptographywersja, ale zauważ, że dołączam IV do zaszyfrowanego tekstu , nie powinien być przechowywany jako globalny (ponowne użycie IV osłabia bezpieczeństwo klucza, a przechowywanie go jako modułu globalnego oznacza, że ​​zostanie on ponownie wygenerowany następne wywołanie Pythona, uniemożliwiające odszyfrowanie całego tekstu zaszyfrowanego):

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

Brakuje dodatkowego opancerzenia podpisu HMAC i nie ma znacznika czasu; musiałbyś je dodać samodzielnie.

Powyższe pokazuje również, jak łatwo jest niepoprawnie łączyć podstawowe elementy kryptograficzne; Wszystkie niepoprawne obchodzenie się z wartością IV przez Vаиітy może prowadzić do naruszenia danych lub nieczytelności wszystkich zaszyfrowanych wiadomości z powodu utraty IV. Korzystanie z Ferneta chroni Cię przed takimi błędami.

AES EBC - niezabezpieczone

Jeśli wcześniej zaimplementowałeś szyfrowanie AES ECB i nadal potrzebujesz go obsługiwać w Pythonie 3, możesz to zrobić cryptographyrównież z . Obowiązują te same zastrzeżenia, EBC nie jest wystarczająco bezpieczny dla rzeczywistych zastosowań . Ponowna implementacja tej odpowiedzi dla Pythona 3, dodając automatyczną obsługę wypełnienia:

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

Ponownie, brakuje podpisu HMAC i i tak nie powinieneś używać EBC. Powyższe ma na celu jedynie zilustrowanie, że cryptographyporadzi sobie z typowymi elementami kryptograficznymi, nawet tymi, których w rzeczywistości nie powinieneś używać.

Martijn Pieters
źródło
51

Element „encoded_c” wspomniany w odpowiedzi szyfru Vigenere @ smehmood powinien mieć wartość „key_c”.

Oto działające funkcje kodowania / dekodowania.

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)

Zastrzeżenie: jak sugerują komentarze, nie należy tego używać do ochrony danych w prawdziwej aplikacji, chyba że przeczytasz to i nie masz nic przeciwko rozmowie z prawnikami:

Co jest nie tak z szyfrowaniem XOR?

qneill
źródło
2
Bardzo przydatne, dzięki. Poniżej zamieściłem wersję Pythona 3 (w komentarzach wyglądała brzydko)
Ryan Barrett
1
„Prawo Schneiera” : Każdy, od najbardziej bezmyślnego amatora po najlepszego kryptografa, może stworzyć algorytm, którego sam nie może złamać. Nie używaj tego, nie jest nawet blisko zabezpieczenia.
zaph
3
Wspaniały! Ta wersja działa również dla smyczków z akcentami, podczas gdy wersja @ smehmood nie. Przykład użycia: encodedmsg = encode('mypassword', 'this is the message éçàèç"') print encodedmsg print decode('mypassword', encodedmsg)działa dobrze.
Basj
2
Oto wtyczka tekstowa Sublime oparta na tym kodzie, umożliwiająca łatwe kodowanie / dekodowanie tekstu za pomocą CTRL + SHIFT + P, a następnie „Eeencode” lub „Dddecode”.
Basj
2
@basj Dziękuję za przykład
sk03
49

Oto Python 3 wersja funkcji z @qneill jest odpowiedź :

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)

Dodatkowe kodowanie / dekodowanie jest potrzebne, ponieważ Python 3 podzielił ciągi / tablice bajtów na dwie różne koncepcje i zaktualizował ich interfejsy API, aby to odzwierciedlić.

Ryan Barrett
źródło
4
Dzięki Ryan, fwiw, że wpisałeś @qniell
qneill
3
Dla zastanawiających się, różnice są następujące .encode()).decode(). w powrocie encode()i .decode()w drugim wierszu w decode().
RolfBly
2
Hmm, zakodowany kod nie jest zbyt unikalny, przeprowadziłem test i pokazuje, że każdy klucz kodu 11,22,33,44, ..., 88,99,111,222, ... zawsze ma inny taki sam kod jak poprzednio. Ale doceniam to
Hzzkygcs
1
@Ryan Barrett, czy można sprawdzić poprawność hasła podczas dekodowania. Powiedzmy, że wysyłam zakodowany ciąg znaków, który zna klucz, a co, jeśli wprowadzi klucz z literówką? Dekodowanie nadal daje mu „zdekodowany” ciąg, ale nie jest to właściwy, po czym może to stwierdzić?
Heinz
26

Zastrzeżenie: jak wspomniano w komentarzach, nie należy tego używać do ochrony danych w prawdziwej aplikacji.

Co jest nie tak z szyfrowaniem XOR?

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

https://github.com/hellman/xortool


Jak już wspomniano, biblioteka PyCrypto zawiera zestaw szyfrów. „Szyfr” XOR może być użyty do wykonania brudnej roboty, jeśli nie chcesz tego robić samodzielnie:

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

Szyfr działa w następujący sposób bez konieczności uzupełniania tekstu jawnego:

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

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

Kredyt dla https://stackoverflow.com/a/2490376/241294 za funkcje kodowania / dekodowania base64 (jestem nowicjuszem w Pythonie).

poida
źródło
Uwaga: moduł Crypto jest instalowany w python3 przez zainstalowany pycrptop, a nie Crypto. sudo pip3 install pycrypto.
Nikhil VJ
2
uwaga: pycrypto nie udało się zainstalować na herokuapp na moim końcu. Znalazłem ten post ... wydaje się mówić, że pakiet pycrypto został zastąpiony innym o nazwie pycryptodome insteal, a metoda XOR została wycofana: github.com/digitalocean/netbox/issues/1527
Nikhil VJ
2
Nigdy nie używaj tej metody , zwróć uwagę na opis tego „szyfru” w dokumentacji : zabawkowy szyfr XOR, XOR jest jednym z najprostszych szyfrów strumieniowych. Szyfrowanie i deszyfrowanie są wykonywane przez XOR-owanie danych za pomocą strumienia klucza utworzonego przez kontenację klucza. Nie używaj go do prawdziwych aplikacji! .
Martijn Pieters
@MartijnPieters masz rację. Mam nadzieję, że moja edycja jasno to wyjaśniła.
poida
12

Oto implementacja bezpiecznego szyfrowania i deszyfrowania adresów URL przy użyciu AES (PyCrypto) i 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))

Jeśli napotkasz jakiś problem, taki jak ten https://bugs.python.org/issue4329 ( TypeError: character mapping must return integer, None or unicode), użyj str(cipher)podczas dekodowania w następujący sposób:

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

Test:

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
Wszystko jest Vаиітy
źródło
Bug z Windows x64 + Python 3.6 + PyCryptodome (jak pycrypto jest przestarzałe) TypeError: Object type <class 'str'> cannot be passed to C code.
Basj
@Basj aww przepraszam .. Nie używam okien, więc nie mogę naprawić.
Wszystko jest Vаиітy
Nie generuj IV i przechowuj go na poziomie modułu, musisz dołączyć IV do zwracanej zaszyfrowanej wiadomości! Wprowadziłeś teraz dwa problemy: ponowne uruchomienie procesu Pythona daje nowy IV, uniemożliwiający odszyfrowanie wcześniej zaszyfrowanych wiadomości, a w międzyczasie ponownie używasz IV do wielu wiadomości, co skutecznie obniża poziom bezpieczeństwa do poziomu EBC.
Martijn Pieters
1
@ AllІѕVаиітy Rozwiązany z b'...', zredagowałem odpowiedź na przyszłość!
Basj
8

Działające funkcje kodujące / dekodujące w pythonie3 (bardzo niewiele zaadaptowane z odpowiedzi qneilla):

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)
chrześcijanin
źródło
8

Dzięki za kilka świetnych odpowiedzi. Nie ma nic oryginalnego do dodania, ale oto kilka stopniowych przeróbek odpowiedzi qneilla przy użyciu przydatnych funkcji Pythona. Mam nadzieję, że zgadzasz się, że upraszczają i wyjaśniają kod.

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()- sparuj elementy na liście z ich indeksem

iteruj po znakach w ciągu

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)

budować listy przy użyciu rozumienia list

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)

Często w Pythonie indeksy list w ogóle nie są potrzebne. Całkowicie wyeliminuj zmienne indeksu pętli za pomocą zip i 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)

i kilka testów ...

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
Nacięcie
źródło
Bardzo fajny @Nick, dobry postęp w pytonizmach i testy też bootują. Aby przyznać odpowiednie uznanie, właśnie naprawiałem błąd w oryginalnej odpowiedzi stackoverflow.com/a/2490718/468252 Smehmood .
qneill
4

Jeśli chcesz być bezpieczny, możesz użyć Ferneta, który jest bezpieczny pod względem kryptograficznym. Możesz użyć statycznej „soli”, jeśli nie chcesz jej przechowywać osobno - stracisz jedynie słownik i zapobiegniesz atakowi tęczy. Wybrałem to, ponieważ mogę wybierać długie lub krótkie hasła ”, co nie jest takie łatwe w przypadku 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

Jeśli to zbyt skomplikowane, ktoś zasugerował simplecrypt

from simplecrypt import encrypt, decrypt
ciphertext = encrypt('password', plaintext)
plaintext = decrypt('password', ciphertext)
HCLivess
źródło
Wystarczy wygenerować sól i uwzględnić ją w wyniku szyfrowania, aby powtarzane hasła i wiadomości nadal dawały losowe wyniki. Uwzględnij także wartość iteracji, aby zabezpieczyć algorytm w przyszłości, ale nadal móc odszyfrować wiadomości przy użyciu innej liczby iteracji.
Martijn Pieters
3

Ktokolwiek tu przybył (i hojniejszy), wydawał się szukać jednowierszowych z niewielką konfiguracją, których inne odpowiedzi nie zapewniają. Dlatego przedstawiam base64.

Teraz pamiętaj, że jest to tylko podstawowe zaciemnianie i jest w ** ŻADNYM ŻADNYM SPOSOBIE BEZPIECZEŃSTWA ** , ale oto kilka linijek:

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'

Kilka uwag:

  • będziesz chciał poradzić sobie z mniej / więcej kodowaniem / dekodowaniem bajtów do łańcucha, w zależności od twojego I / O. Zajrzyj do bytes()ibytes::decode()
  • base64 można łatwo rozpoznać po typach używanych znaków i często kończących się =znakami. Ludzie tacy jak ja absolutnie dekodują je w konsoli javascript, kiedy widzimy je na stronach internetowych. To tak proste, jak btoa(string)(js)
  • kolejność to klucz + dane, tak jak w b64, jakie znaki pojawią się na końcu, zależy od tego, jakie znaki są na początku (ze względu na przesunięcia bajtów. Wikipedia ma kilka fajnych wyjaśnień). W tym scenariuszu początek zakodowanego ciągu będzie taki sam dla wszystkiego zakodowanego tym kluczem. Plus jest taki, że dane będą bardziej zaciemnione. Zrobienie tego w drugą stronę spowoduje, że część danych będzie dokładnie taka sama dla wszystkich, niezależnie od klucza.

Teraz, jeśli to, czego chciałeś, nie wymagało nawet żadnego klucza, ale tylko trochę zaciemnienia, możesz ponownie po prostu użyć base64, bez żadnego rodzaju klucza:

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'
towc
źródło
2
Tak, jeśli nie przejmujesz się bezpieczeństwem, base64 jest o wiele lepszy niż szyfrowanie.
Martijn Pieters
O innych odpowiedziach, które nie są jednowierszowymi: nie o to chodzi. Proszą o wywołanie dwóch funkcji. I Fernet(key).encrypt(message)jest tylko jednym wyrażeniem, tak jak wywołanie base64.
Martijn Pieters
I należy usunąćkey całkowicie. Mnóstwo twórców zamiar skopiować i wkleić z przepełnienie stosu bez zwracania uwagi i będzie zakładać klucz jako tajne. Jeśli musisz go uwzględnić, przynajmniej nie używaj go i ostrzegaj lub zgłaszaj wyjątek, jeśli mimo to jest używany. Nie lekceważ głupoty kultury kopiowania i wklejania oraz swoich obowiązków dostarczania rozsądnych funkcji.
Martijn Pieters
3

Podam 4 rozwiązania:

1) Korzystanie z szyfrowania Fernet z cryptographybiblioteką

Oto rozwiązanie wykorzystujące pakiet cryptography, który możesz zainstalować jak zwykle za pomocą 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')

Możesz dostosować się za pomocą własnej soli, liczby iteracji itp. Ten kod nie jest bardzo odległy od odpowiedzi @ HCLivess, ale celem jest posiadanie gotowych do użycia encrypti decryptfunkcji. Źródło: https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet .

Uwaga: użyj .encode()i .decode()wszędzie, jeśli chcesz ciągów 'John Doe'zamiast bajtów, takich jak b'John Doe'.


2) Proste szyfrowanie AES z Cryptobiblioteką

Działa to z Pythonem 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

Uwaga: możesz usunąć, base64.b64encodea .b64decodejeśli nie chcesz, aby dane wyjściowe były czytelne dla tekstu i / lub jeśli i tak chcesz zapisać zaszyfrowany tekst na dysk jako plik binarny.


3) AES wykorzystujący lepszą funkcję wyprowadzania klucza hasła i możliwość sprawdzenia, czy wprowadzono nieprawidłowe hasło, w Cryptobibliotece

Rozwiązanie 2) z „trybem CFB” AES jest w porządku, ale ma dwie wady: fakt, że SHA256(password)można go łatwo zmodyfikować za pomocą tabeli przeglądowej oraz że nie ma możliwości sprawdzenia, czy wprowadzono nieprawidłowe hasło. Można to tutaj rozwiązać przez użycie AES w „trybie GCM”, jak omówiono w AES: jak wykryć, że wprowadzono złe hasło? i czy ta metoda powiedzenia „Podane hasło jest nieprawidłowe” jest bezpieczna? :

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) Korzystanie z RC4 (bez biblioteki)

Na podstawie 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'

(Nieaktualne od ostatnich zmian, ale zachowane do przyszłego użytku): Miałem problemy z używaniem systemu Windows + Python 3.6 + wszystkie odpowiedzi dotyczące pycrypto(niemożliwe pip install pycryptow systemie Windows) lub pycryptodome(odpowiedzi tutaj from Crypto.Cipher import XORnie powiodły się, ponieważ XORnie są obsługiwane przez to pycryptorozwidlenie); i rozwiązania wykorzystujące ... AESrównież zawiodły TypeError: Object type <class 'str'> cannot be passed to C code). Biblioteka simple-cryptma pycryptorównież zależność, więc nie jest to opcja.

Basj
źródło
1
Nie chcesz zakodować na stałe liczby soli i iteracji; wygeneruj losową sól i skonfiguruj liczbę iteracji podczas szyfrowania, uwzględnij te informacje w wyniku szyfrowania oraz wyodrębnij i użyj wartości podczas odszyfrowywania. Sól chroni Cię przed trywialnym rozpoznaniem ponownie użytych haseł w danej wiadomości, liczba iteracji zabezpiecza algorytm w przyszłości.
Martijn Pieters
Tak, oczywiście @MartijnPieters, ale celem jest tutaj posiadanie prostego kodu do prostych celów, zgodnie z życzeniem OP, z dwoma parametrami : zwykły tekst + hasło. Oczywiście w przypadku bardziej złożonego scenariusza (np. Bazy danych) wykorzystasz wszystkie te dodatkowe parametry.
Basj
Nie ma potrzeby stosowania dodatkowych parametrów! Uwzględniam te informacje zakodowane w nieprzezroczystej wartości zwracanej password_encrypt().
Martijn Pieters
@MartijnPieters Naprawdę fajne rozwiązanie.
Basj
2

To działa, ale długość hasła powinna być dokładnie 8. Jest to proste i wymaga 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

WYNIK:

³.\Þ\åS¾+æÅ`;Ê
John Doe
Pratik Deoghare
źródło
Nie używaj stałego IV! Losuj IV i dołącz go zamiast tekstu zaszyfrowanego. W przeciwnym razie równie dobrze możesz skorzystać z trybu EBC; powtarzające się zwykłe wiadomości tekstowe są poza tym łatwe do rozpoznania.
Martijn Pieters
Wygląda na to, że projekt pyDes nie żyje; strona główna zniknęła, a ostatnie wydanie na PyPI ma już 9 lat.
Martijn Pieters
2

Inna implementacja kodu @qneill, która zawiera sumę kontrolną CRC oryginalnej wiadomości, zgłasza wyjątek, jeśli sprawdzenie nie powiedzie się:

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
ahmed
źródło
2

Możesz użyć AES do zaszyfrowania ciągu za pomocą hasła. Jednak będziesz chciał wybrać wystarczająco silne hasło, aby ludzie nie mogli łatwo odgadnąć, co to jest (przepraszam, nie mogę na to poradzić. Jestem niedoszłym słabym zabezpieczeniem).

AES jest silny i ma dobry rozmiar klucza, ale jest również łatwy w użyciu z PyCrypto.

Alan
źródło
3
Dzięki Alan. Ale dla wyjaśnienia nie szyfruję samych haseł. W powyższym przykładzie szyfruję ciąg „John Doe” zgodnie z hasłem „mypass”, które jest prostym hasłem używanym w kodzie źródłowym. Hasła użytkowników nie są używane, podobnie jak inne bardzo poufne informacje. Zredagowałem moje pytanie, aby to wyjaśnić.
RexE
1
AES jest świetny, jeśli jest używany prawidłowo. Łatwo jest jednak używać go nieprawidłowo; jest tu co najmniej jedna odpowiedź, która używa niezabezpieczonego trybu szyfrowania blokowego, a kolejne dwie, które grzebią w obsłudze wartości IV. Lepiej użyć dobrej biblioteki z dobrze zdefiniowanym przepisem, takim jak Fernet!
Martijn Pieters
Właściwie to bardzo wnikliwa obserwacja. Kiedyś grzebałem w kroplówce.
Alan
0

Biblioteki zewnętrzne zapewniają algorytmy szyfrowania tajnego klucza.

Na przykład Cyphermoduł w PyCrypto oferuje wybór wielu algorytmów szyfrowania:

  • 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 jest Pythonopakowaniem dla OpenSSL i zapewnia (między innymi) w pełni funkcjonalną bibliotekę kryptograficzną ogólnego przeznaczenia. Zawarte są symetryczne szyfry (takie jak AES).

gimel
źródło
0

jeśli chcesz mieć bezpieczne szyfrowanie:

w przypadku Pythona 2 powinieneś użyć keyczar http://www.keyczar.org/

dla pythona 3, dopóki keyczar nie będzie dostępny, napisałem simple-crypt http://pypi.python.org/pypi/simple-crypt

w obu przypadkach będą one wzmacniane kluczem, co czyni je bezpieczniejszymi niż większość innych odpowiedzi tutaj. a ponieważ są tak łatwe w użyciu, możesz chcieć ich używać nawet wtedy, gdy bezpieczeństwo nie jest krytyczne ...

Andrew Cooke
źródło
Z repozytorium Keyczar : Ważna uwaga: Keyczar jest przestarzały. Programiści Keyczar polecają Tink , ale nie ma wersji Tink w Pythonie.
Martijn Pieters
0

Tak więc, ponieważ nic krytycznego dla misji nie jest kodowane , a chcesz tylko zaszyfrować w celu zaciemnienia .

Pozwólcie, że przedstawię szyfr Cezara

wprowadź opis obrazu tutaj

Szyfr Cezara lub przesunięcie Cezara jest jedną z najprostszych i najbardziej znanych technik szyfrowania. Jest to rodzaj szyfru podstawieniowego, w którym każda litera w tekście jawnym jest zastępowana literą o określoną liczbę pozycji w dół alfabetu. Na przykład, przy przesunięciu w lewo o 3, D zostanie zastąpione przez A, E stanie się B i tak dalej.

Przykładowy kod w celach informacyjnych:

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

Zalety: spełnia Twoje wymagania, jest prosty i zajmuje się kodowaniem.

Wada: może zostać złamany przez proste algorytmy brutalnej siły (jest bardzo mało prawdopodobne, aby ktokolwiek próbował przejść przez wszystkie dodatkowe wyniki).

Yash Mishra
źródło
0

Dodanie jeszcze jednego kodu z dekodowaniem i kodowaniem w celach informacyjnych

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()
Sriram Suruliandi
źródło