암호에 따라 문자열을 인코딩하는 간단한 방법?


123

파이썬에는 암호를 사용하여 문자열을 인코딩 / 디코딩하는 내장 된 간단한 방법이 있습니까?

이 같은:

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

따라서 문자열 "John Doe"는 'sjkl28cn2sx0'으로 암호화됩니다. 원래 문자열을 얻으려면 소스 코드의 암호 인 'mypass'키를 사용하여 해당 문자열을 "잠금 해제"합니다. 암호를 사용하여 Word 문서를 암호화 / 복호화 할 수있는 방법이 되었으면합니다.

이 암호화 된 문자열을 URL 매개 변수로 사용하고 싶습니다. 내 목표는 강력한 보안이 아니라 난독 화입니다. 미션 크리티컬 한 것은 인코딩되지 않습니다. 데이터베이스 테이블을 사용하여 키와 값을 저장할 수 있다는 것을 알고 있지만 최소화하려고 노력하고 있습니다.


28
여기서 "비밀번호"라는 용어는 부적절합니다. 당신은 암호화 키와이를 사용하고 등 질문뿐만 아니라 문서, 주석, 사양, 테스트 계획, 피하기 혼란에 그 용어를 사용한다
짐 데니스

2
"암호를 사용하여 Word 문서를 암호화 / 복호화 할 수있는 방법이 되었으면합니다." Word에는 Word 문서 를 암호화해야하는 경우 문서를 암호화하는 기본 제공 옵션이 이미 있습니다 .
바이런 파일러

2
흥미롭게도 이와 같은 암호 저장 함정 에 대한 이 연구 논문 에 따르면 Stack Overflow를 사용하는 개발자는 덜 안전한 코드를 생성하는 경향이 있습니다. 왜 그런지 궁금해?


하나는 단순히 인코딩 / 디코딩을 구현하는 것이 아닙니다
luckyging3r

답변:


70

당신이 유일한 으로부터 물건을 모호하게됩니다 간단한 난독 찾고 매우 캐주얼 관찰자, 당신은 타사 라이브러리를 사용하고자되지 않습니다. Vigenere 암호와 같은 것을 추천합니다. 단순한 고대 암호 중 가장 강력한 암호 중 하나입니다.

Vigenère 암호

빠르고 쉽게 구현할 수 있습니다. 다음과 같은 것 :

import base64

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

디코딩은 키를 빼는 것을 제외하면 거의 동일합니다.

인코딩하는 문자열이 짧거나 사용 된 암호의 길이를 추측하기 어려운 경우 깨지기가 훨씬 더 어렵습니다.

암호화 된 것을 찾고 있다면 PyCrypto가 아마도 최선의 방법 일 것입니다. 그러나 이전 답변은 몇 가지 세부 사항을 간과하고 있습니다. PyCrypto의 ECB 모드에서는 메시지 길이가 16 자의 배수 여야합니다. 그래서 패드를해야합니다. 또한 URL 매개 변수로 사용하려면base64.urlsafe_b64_encode() 하려면 표준 대신을 하십시오. 이것은 base64 알파벳의 일부 문자를 URL 안전 문자로 대체합니다 (이름에서 알 수 있듯이).

그러나 이것을 사용하기 전에이 매우 얇은 난독 화 층이 귀하의 요구에 충분 하다는 것을 절대적으로 확신해야합니다 . 내가 링크 한 Wikipedia 기사는 암호 해독에 대한 자세한 지침을 제공하므로 적당한 정도의 결정을 가진 사람은 누구나 쉽게 해독 할 수 있습니다.


6
smehmood의 스크립트를 수정하고 디코딩 기능을 추가했습니다. gist.github.com/ilogik/6f9431e4588015ecb194
Adrian Mester 2013-07-23

3
주의! smehmood의 코드와 Adrian Mester의 수정은 모두 하위 ASCII 범위의 문자가있는 문자열에서만 작동합니다! % 연산자, 유니 코드 입력 등의 우선 순위를 참조하십시오. 작업 코드에 대한 qneill의 답변 참조
le_m

2
@smehmood 다음과 같은 오류가 발생합니다'str' object cannot be interpreted as an integer
Rohit Khatri

3
"for i in xrange (string)"은 "for i in xrange (len (string))"로 변경해야 할 수 있습니다.
user3113626

2
Python 2 및 3 용 인코더 및 디코더 : gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1a
iman

71

보안이 아닌 모호함을 원한다고 명시 적으로 말했듯이, 우리는 당신이 제안한 것의 약점에 대해 당신을 질책하지 않을 것입니다 :)

따라서 PyCrypto를 사용합니다.

import base64
from Crypto.Cipher import AES

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

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

누군가 데이터베이스와 코드베이스를 확보하면 암호화 된 데이터를 디코딩 할 수 있습니다. secret_key안전을 지키세요 !


3
AES 암호화에는 길이가 16의 배수 인 블록이 필요하기 때문에 msg_text가 길이가 16 바이트의 배수가 아니면 이것이 작동하지 않을 것이라고 생각합니다. 임의의 길이의 msg_text에 대한 작업 구현은 길이가 16의 배수가되도록 문자열에 패딩을 추가해야합니다.
tohster

6
패딩이있는 예 : paste.ubuntu.com/11024555 임의의 암호 및 메시지 길이로 작동합니다.
iman

3
@Ethan no,이 특정 encrypt함수는 상태 저장 dlitz.net/software/pycrypto/api/current/… 이므로 다시 사용하지 마십시오.

1
@Will +1 정보와 링크에 감사드립니다. 미래에 매우 값 비싼 버그 수정으로부터 저를 구했습니다.
Ethan

3
re- "확실히 강력한 시스템에서 ECB를 사용하지 마십시오.": 나는 단지 내 재미를 위해 이것을 실험하고 있습니다. 그러나 귀하의 코드에서 위의 주석을 보았습니다. 최소한의 보안 / 암호화 / 정보-이론 배경을 가진 사람들에게 왜 "절대 사용하지 않습니까?" 다른 질문이 필요하거나 링크가있을 수 있습니다.
트레버 보이드 스미스

69

파이썬에는 내장 된 암호화 체계가 없습니다. 또한 암호화 된 데이터 저장소를 중요하게 생각해야합니다. 한 개발자가 안전하지 않다고 이해하는 사소한 암호화 체계와 경험이 적은 개발자는 장난감 체계를 안전한 체계로 오인 할 수 있습니다. 암호화하는 경우 올바르게 암호화하십시오.

그러나 적절한 암호화 체계를 구현하기 위해 많은 작업을 할 필요는 없습니다. 우선 , 암호화 휠을 재발 명하지 말고 신뢰할 수있는 암호화 라이브러리를 사용하여이를 처리하십시오. Python 3의 경우 신뢰할 수있는 라이브러리는cryptography .

또한 암호화 및 암호 해독을 바이트에 적용하는 것이 좋습니다 . 먼저 문자 메시지를 바이트로 인코딩합니다. stringvalue.encode()UTF8로 인코딩하고 다음을 사용하여 쉽게 되돌릴 수 있습니다.bytesvalue.decode() .

마지막으로 암호화 및 복호화시 암호가 아닌 에 대해 이야기 합니다. 키는 사람이 기억할 수 없어야합니다. 암호는 사람이 읽을 수 있고 기억할 수있는 경우가 많지만 비밀 위치에 저장하지만 컴퓨터에서 읽을 수있는 것입니다. 당신 은 할있습니다약간의주의를 기울여 암호에서 키를 추출 .

그러나 웹 애플리케이션 또는 클러스터에서 실행되는 프로세스를 사람의주의없이 계속 실행하려면 키를 사용하려고합니다. 암호는 최종 사용자 만 특정 정보에 액세스해야하는 경우에 사용됩니다. 그럼에도 불구하고 일반적으로 암호로 응용 프로그램을 보호 한 다음 사용자 계정에 연결된 키를 사용하여 암호화 된 정보를 교환합니다.

대칭 키 암호화

Fernet – AES CBC + HMAC, 적극 권장

cryptography라이브러리가 포함 Fernet 조리법 , 암호화를 사용하기위한 모범 사례 조리법을. Fernet은 개방형 표준입니다. 광범위한 프로그래밍 언어로 즉시 구현 이며 메시지 변조를 방지하기 위해 버전 정보, 타임 스탬프 및 HMAC 서명과 함께 AES CBC 암호화를 패키지화합니다.

Fernet 암호화하고 해독 메시지를 매우 쉽게 그것을하게 하고 안전하게 보호 할 수 있습니다. 비밀로 데이터를 암호화하는 데 이상적인 방법입니다.

Fernet.generate_key()보안 키를 생성하는 데 사용 하는 것이 좋습니다 . 암호도 사용할 수 있지만 (다음 섹션), 전체 32 바이트 비밀 키 (암호화에 16 바이트, 서명에 16 바이트)가 생각할 수있는 대부분의 암호보다 더 안전합니다.

Fernet이 생성하는 키는 bytesURL 및 파일 안전 base64 문자 가있는 객체이므로 인쇄 할 수 있습니다.

from cryptography.fernet import Fernet

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

암호화 또는 암호 해독 메시지하려면 만들 Fernet()지정된 키를 가진 인스턴스를, 및 호출 Fernet.encrypt()하거나 Fernet.decrypt(), 암호화에 일반 텍스트 메시지와 암호화 된 토큰 모두는 bytes객체.

encrypt()decrypt()기능과 같을 것이다 :

from cryptography.fernet import Fernet

def encrypt(message: bytes, key: bytes) -> bytes:
    return Fernet(key).encrypt(message)

def decrypt(token: bytes, key: bytes) -> bytes:
    return Fernet(key).decrypt(token)

데모:

>>> key = Fernet.generate_key()
>>> print(key.decode())
GZWKEhHGNopxRdOHS4H4IyKhLQ8lwnyU7vRLrM3sebY=
>>> message = 'John Doe'
>>> encrypt(message.encode(), key)
'gAAAAABciT3pFbbSihD_HZBZ8kqfAj94UhknamBuirZWKivWOukgKQ03qE2mcuvpuwCSuZ-X_Xkud0uWQLZ5e-aOwLC0Ccnepg=='
>>> token = _
>>> decrypt(token, key).decode()
'John Doe'

암호가있는 Fernet – 암호에서 파생 된 키로 보안이 다소 약화됩니다.

강력한 키 파생 방법사용하는 경우 비밀 키 대신 비밀번호를 사용할 수 있습니다 . 그런 다음 메시지에 salt 및 HMAC 반복 횟수를 포함해야하므로 암호화 된 값은 먼저 salt, count 및 Fernet 토큰을 분리하지 않고는 더 이상 Fernet과 호환되지 않습니다.

import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

backend = default_backend()
iterations = 100_000

def _derive_key(password: bytes, salt: bytes, iterations: int = iterations) -> bytes:
    """Derive a secret key from a given password and salt"""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(), length=32, salt=salt,
        iterations=iterations, backend=backend)
    return b64e(kdf.derive(password))

def password_encrypt(message: bytes, password: str, iterations: int = iterations) -> bytes:
    salt = secrets.token_bytes(16)
    key = _derive_key(password.encode(), salt, iterations)
    return b64e(
        b'%b%b%b' % (
            salt,
            iterations.to_bytes(4, 'big'),
            b64d(Fernet(key).encrypt(message)),
        )
    )

def password_decrypt(token: bytes, password: str) -> bytes:
    decoded = b64d(token)
    salt, iter, token = decoded[:16], decoded[16:20], b64e(decoded[20:])
    iterations = int.from_bytes(iter, 'big')
    key = _derive_key(password.encode(), salt, iterations)
    return Fernet(key).decrypt(token)

데모:

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

출력에 솔트를 포함하면 임의의 솔트 값을 사용할 수 있으므로 암호 재사용이나 메시지 반복에 관계없이 암호화 된 출력이 완전히 무작위로 보장됩니다. 반복 횟수를 포함하면 오래된 메시지를 해독하는 기능을 잃지 않고 시간이 지남에 따라 CPU 성능 향상을 조정할 수 있습니다.

비슷한 크기의 풀에서 적절하게 임의의 암호를 생성한다면 암호만으로도 Fernet 32 ​​바이트 임의 키만큼 안전 할 수 있습니다. 32 바이트는 256 ^ 32 개의 키를 제공하므로 74 자 (대문자 26 개, 하위 26 개, 10 자리 및 12 개의 가능한 기호)의 알파벳을 사용하는 경우 암호는 최소 math.ceil(math.log(256 ** 32, 74))== 42 자 여야합니다 . 그러나 잘 선택된 더 많은 수의 HMAC 반복 은 공격자가 무차별 대입하는 데 훨씬 더 많은 비용이 들기 때문에 엔트로피 부족을 다소 완화 할 수 있습니다.

더 짧지 만 여전히 합리적으로 안전한 암호를 선택한다고해서이 체계가 무력화되는 것은 아니며 무차별 대입 공격자가 검색해야하는 가능한 값의 수를 줄입니다. 보안 요구 사항에 대해 충분히 강력한 암호 를 선택하십시오. .

대안

모호함

대안은 암호화하지 않는 것 입니다. 보안 수준이 낮은 암호 나 집에서 만든 구현을 사용하려는 유혹을받지 마십시오. 이러한 접근 방식에는 보안이 없지만, 미래에 코드를 유지 관리하는 작업을 맡은 경험이없는 개발자에게 보안이 전혀없는 것보다 더 나쁜 보안의 환상을 줄 수 있습니다.

모호함 만 있으면 데이터를 base64로 지정하십시오. URL 안전 요구 사항의 경우 base64.urlsafe_b64encode()기능 이 좋습니다. 여기에서 비밀번호를 사용하지 말고 인코딩 만하면됩니다. 기껏해야 압축을 추가하십시오 (예 zlib:).

import zlib
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

def obscure(data: bytes) -> bytes:
    return b64e(zlib.compress(data, 9))

def unobscure(obscured: bytes) -> bytes:
    return zlib.decompress(b64d(obscured))

이 회전 b'Hello world!'b'eNrzSM3JyVcozy_KSVEEAB0JBF4='.

무결성 만

신뢰할 수없는 클라이언트로 전송되고 다시 수신 된 후 데이터가 변경되지 않도록 신뢰할 수 있는지 확인하는 방법 만 있으면 데이터 에 서명하고이를 위해 hmac라이브러리 를 SHA1과 함께 사용할 수 있습니다 (여전히 HMAC 서명에 대해 안전한 것으로 간주 됨 ) 이상 :

import hmac
import hashlib

def sign(data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
    assert len(key) >= algorithm().digest_size, (
        "Key must be at least as long as the digest size of the "
        "hashing algorithm"
    )
    return hmac.new(key, data, algorithm).digest()

def verify(signature: bytes, data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
    expected = sign(data, key, algorithm)
    return hmac.compare_digest(expected, signature)

이를 사용하여 데이터에 서명 한 다음 데이터와 함께 서명을 첨부하여 클라이언트에 보냅니다. 데이터를 다시 받으면 데이터와 서명을 분할하고 확인하십시오. 기본 알고리즘을 SHA256으로 설정 했으므로 32 바이트 키가 필요합니다.

key = secrets.token_bytes(32)

다양한 형식의 직렬화 및 역 직렬화로이 모든 것을 패키징 하는 itsdangerous라이브러리 를 살펴볼 수 있습니다 .

AES-GCM 암호화를 사용하여 암호화 및 무결성 제공

Fernet은 암호화 된 데이터의 무결성을 보장하기 위해 HMAC 서명으로 AEC-CBC를 기반으로합니다. 악의적 인 공격자는 암호문이 서명되어 있기 때문에 잘못된 입력으로 서클에서 서비스를 계속 실행하기 위해 시스템에 말도 안되는 데이터를 제공 할 수 없습니다.

갈루아 / 카운터 모드의 블록 암호는 암호문을 생성하고 태그 때문에 동일한 목적을 제공하기 위해 사용될 수 있고, 동일한 목적을 제공 할 수있다. 단점은 Fernet과 달리 다른 플랫폼에서 재사용 할 수있는 사용하기 쉬운 한 가지 방법이 없다는 것입니다. AES-GCM은 또한 패딩을 사용하지 않으므로이 암호화 암호문은 입력 메시지의 길이와 일치합니다 (Fernet / AES-CBC는 메시지를 고정 길이의 블록으로 암호화하여 메시지 길이를 다소가립니다).

AES256-GCM은 일반적인 32 바이트 비밀을 키로 사용합니다.

key = secrets.token_bytes(32)

그런 다음 사용

import binascii, time
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidTag

backend = default_backend()

def aes_gcm_encrypt(message: bytes, key: bytes) -> bytes:
    current_time = int(time.time()).to_bytes(8, 'big')
    algorithm = algorithms.AES(key)
    iv = secrets.token_bytes(algorithm.block_size // 8)
    cipher = Cipher(algorithm, modes.GCM(iv), backend=backend)
    encryptor = cipher.encryptor()
    encryptor.authenticate_additional_data(current_time)
    ciphertext = encryptor.update(message) + encryptor.finalize()        
    return b64e(current_time + iv + ciphertext + encryptor.tag)

def aes_gcm_decrypt(token: bytes, key: bytes, ttl=None) -> bytes:
    algorithm = algorithms.AES(key)
    try:
        data = b64d(token)
    except (TypeError, binascii.Error):
        raise InvalidToken
    timestamp, iv, tag = data[:8], data[8:algorithm.block_size // 8 + 8], data[-16:]
    if ttl is not None:
        current_time = int(time.time())
        time_encrypted, = int.from_bytes(data[:8], 'big')
        if time_encrypted + ttl < current_time or current_time + 60 < time_encrypted:
            # too old or created well before our current time + 1 h to account for clock skew
            raise InvalidToken
    cipher = Cipher(algorithm, modes.GCM(iv, tag), backend=backend)
    decryptor = cipher.decryptor()
    decryptor.authenticate_additional_data(timestamp)
    ciphertext = data[8 + len(iv):-16]
    return decryptor.update(ciphertext) + decryptor.finalize()

Fernet이 지원하는 동일한 TTL (Time-to-Live) 사용 사례를 지원하기 위해 타임 스탬프를 포함했습니다.

이 페이지의 다른 접근 방식, Python 3

AES CFB- CBC와 비슷하지만 패딩 할 필요 없음

이것은 잘못되었지만 All Іѕ Vаиітy가 따르는 접근 방식입니다 . 이것은 cryptography버전이지만 암호문에 IV를 포함하므로 전역으로 저장해서는 안됩니다 (IV를 재사용하면 키의 보안이 약화되고 모듈 전역으로 저장하면 다시 생성됨을 의미 함). 다음 Python 호출, 모든 암호문을 해독 할 수 없도록 렌더링) :

import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

backend = default_backend()

def aes_cfb_encrypt(message, key):
    algorithm = algorithms.AES(key)
    iv = secrets.token_bytes(algorithm.block_size // 8)
    cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(message) + encryptor.finalize()
    return b64e(iv + ciphertext)

def aes_cfb_decrypt(ciphertext, key):
    iv_ciphertext = b64d(ciphertext)
    algorithm = algorithms.AES(key)
    size = algorithm.block_size // 8
    iv, encrypted = iv_ciphertext[:size], iv_ciphertext[size:]
    cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
    decryptor = cipher.decryptor()
    return decryptor.update(encrypted) + decryptor.finalize()

여기에는 HMAC 서명의 추가 아머 링이 없으며 타임 스탬프가 없습니다. 직접 추가해야합니다.

위의 내용은 기본 암호화 빌딩 블록을 잘못 결합하는 것이 얼마나 쉬운 지 보여줍니다. 모든 Іѕ Vаиітy의 IV 값을 잘못 처리하면 IV가 손실되어 데이터 유출 또는 모든 암호화 된 메시지를 읽을 수 없게 될 수 있습니다. 대신 Fernet을 사용하면 이러한 실수로부터 보호됩니다.

AES ECB – 안전하지 않음

이전에 AES ECB 암호화를 구현 했고 Python 3에서이를 계속 지원해야하는 경우에도 여전히 그렇게 할 수 cryptography있습니다. 동일한 경고가 적용되며 ECB는 실제 애플리케이션에 대해 충분히 안전하지 않습니다 . Python 3에 대한 답변을 다시 구현하여 패딩 자동 처리를 추가합니다.

from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend

backend = default_backend()

def aes_ecb_encrypt(message, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(cipher.algorithm.block_size).padder()
    padded = padder.update(msg_text.encode()) + padder.finalize()
    return b64e(encryptor.update(padded) + encryptor.finalize())

def aes_ecb_decrypt(ciphertext, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    decryptor = cipher.decryptor()
    unpadder = padding.PKCS7(cipher.algorithm.block_size).unpadder()
    padded = decryptor.update(b64d(ciphertext)) + decryptor.finalize()
    return unpadder.update(padded) + unpadder.finalize()

다시 말하지만 여기에는 HMAC 서명이 없기 때문에 ECB를 사용해서는 안됩니다. 위의 내용은 cryptography실제로 사용해서는 안되는 공통 암호화 빌딩 블록을 처리 할 수있는 것을 설명하기 위한 것입니다.


51

@smehmood의 Vigenere 암호 답변에 언급 된 "encoded_c"는 "key_c" 여야합니다.

다음은 인코딩 / 디코딩 기능입니다.

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

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

면책 조항 : 의견에서 알 수 있듯이,이 내용 을 읽고 변호사와 이야기하는 것을 꺼리지 않는 한 실제 애플리케이션에서 데이터를 보호하는 데 사용해서는 안됩니다 .

XOR 암호화의 문제점은 무엇입니까?


2
매우 유용합니다. 감사합니다. 아래에 Python 3 버전을 게시했습니다 (댓글에서보기
Ryan Barrett

1
"Schneier 's Law" : 가장 우둔한 아마추어부터 최고의 암호 학자에 이르기까지 누구나 자신이 깰 수없는 알고리즘을 만들 수 있습니다. 이것을 사용하지 마십시오. 보안에 가깝지 않습니다.
zaph 2016

3
큰! 이 버전은 악센트가있는 문자열에서도 작동하지만 @smehmood의 버전은 그렇지 않습니다. 사용 예 : encodedmsg = encode('mypassword', 'this is the message éçàèç"') print encodedmsg print decode('mypassword', encodedmsg), 잘 작동합니다.
Basj

2
다음은 이 코드를 기반으로 하는 Sublime 텍스트 플러그인 으로 CTRL + SHIFT + P 다음에 "Eeencode"또는 "Dddecode"를 사용하여 텍스트를 쉽게 인코딩 / 디코딩 할 수 있습니다.
Basj

2
@basj 예를 들어 주셔서 감사합니다
sk03

49

@qneill의 답변 에서 가져온 함수의 Python 3 버전은 다음과 같습니다 .

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

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

Python 3은 문자열 / 바이트 배열을 두 개의 다른 개념으로 분할하고이를 반영하도록 API를 업데이트했기 때문에 추가 인코딩 / 디코딩이 필요합니다.


4
감사 라이언, FWIW 당신 typo'd @qniell
qneill

3
궁금한 사람들을 위해 차이점은 .encode()).decode(). 의 반환 encode().decode()의 두 번째 줄에서 decode().
RolfBly

2
흠, 인코딩 된 코드는 실제로 고유하지 않습니다. 테스트를 실행했는데 11,22,33,44, ..., 88,99,111,222, ...의 모든 코드 키가 항상 이전과 동일한 코드를 가지고 있습니다. 그러나 나는 그것을 감사
Hzzkygcs

1
@Ryan Barrett, 디코딩시 암호의 정확성을 확인할 수 있습니까? 키를 알고있는 인코딩 된 문자열을 보낸다고 가정 해 봅시다. 그가 오타로 키를 입력하면 어떻게 될까요? 디코딩은 여전히 ​​그에게 "디코딩 된"문자열을 제공하지만 올바른 문자열이 아닙니다. 어떻게 알 수 있습니까?
Heinz

26

면책 조항 : 주석에서 언급했듯이 실제 애플리케이션에서 데이터를 보호하기 위해 사용해서는 안됩니다 .

XOR 암호화의 문제점은 무엇입니까?

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

https://github.com/hellman/xortool


앞서 언급했듯이 PyCrypto 라이브러리에는 암호 모음이 포함되어 있습니다. XOR "암호화"를 사용하여 직접 수행하고 싶지 않은 경우 더러운 작업을 수행 할 수 있습니다.

from Crypto.Cipher import XOR
import base64

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

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

암호는 일반 텍스트를 채울 필요없이 다음과 같이 작동합니다.

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

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

신용 https://stackoverflow.com/a/2490376/241294에 대한 은 base64 인코딩 / 디코딩 기능에 대한 것입니다 (저는 파이썬 초보자입니다).


참고 : Crypto 모듈은 Crypto가 아닌 설치된 pycrptop에 의해 python3에 설치됩니다. sudo pip3 install pycrypto.
Nikhil VJ

2
참고 : pycrypto는 내 끝에서 herokuapp에 설치하지 못했습니다. 이 게시물을 찾았습니다. .. pycrypto 패키지가 pycryptodome insteal이라는 다른 패키지로 대체되었으며 XOR 메소드가 더 이상 사용되지 않는다고 말하는 것
Nikhil VJ

2
이 방법을 사용하지 마십시오 . 문서 에서이 '암호'에 대한 설명을 참고하십시오. XOR 장난감 암호, XOR은 가장 간단한 스트림 암호 중 하나입니다. 암호화 및 암호 해독은 키를 오염시켜 만든 키 스트림으로 데이터를 XOR-ing하여 수행됩니다. 실제 응용 프로그램에 사용하지 마십시오! .
Martijn Pieters

@MartijnPieters 당신이 맞아요. 제 편집으로 그 점이 분명해 졌기를 바랍니다.
poida

12

다음은 AES (PyCrypto) 및 base64를 사용한 URL Safe 암호화 및 복호화 구현입니다.

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

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

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

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

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

https://bugs.python.org/issue4329 ( TypeError: character mapping must return integer, None or unicode) 와 같은 문제가 발생 str(cipher)하면 다음과 같이 디코딩 하는 동안 사용 하십시오 .

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

테스트:

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

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

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

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

Windows x64 + Python 3.6 + PyCryptodome의 버그 (pycrypto가 더 이상 사용되지 않음) : TypeError: Object type <class 'str'> cannot be passed to C code.
Basj

@Basj aww 죄송합니다 .. 나는 창문을 사용하지 않아서 고칠 수 없습니다.
모든 Іѕ Vаиітy

IV를 생성하고 모듈 수준에서 저장하지 마십시오. 반환 된 암호문 메시지에 IV 를 포함 해야합니다 ! 이제 두 가지 문제가 발생했습니다. Python 프로세스를 다시 시작하면 새 IV가 제공되어 이전에 암호화 된 메시지를 해독 할 수 없게되며 그 동안 IV를 여러 메시지에 다시 사용하여 보안을 효과적으로 ECB 수준으로 낮 춥니 다.
Martijn Pieters

1
@ AllІѕVаиітy로 해결 b'...', 향후 참조를 위해 답변을 편집했습니다!
Basj '1911.07.08

8

python3에서 인코딩 / 디코딩 기능 작업 (qneill의 답변에서 거의 수정되지 않음) :

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

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

8

훌륭한 답변을 주셔서 감사합니다. 추가 할 원본은 없지만 유용한 Python 기능을 사용하여 qneill의 답변을 점진적으로 다시 작성했습니다. 그들이 코드를 단순화하고 명확하게한다는 데 동의하길 바랍니다.

import base64


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


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

enumerate()-목록의 항목을 색인과 쌍으로 연결

문자열의 문자를 반복

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


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

목록 이해력을 사용하여 목록 작성

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


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

종종 Python에서는 목록 색인이 전혀 필요하지 않습니다. zip 및 cycle을 사용하여 루프 인덱스 변수를 완전히 제거하십시오.

from itertools import cycle


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


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

그리고 몇 가지 테스트 ...

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

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

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

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

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

아주 좋은 @Nick, 비단뱀의 좋은 진행, 테스트도 부팅됩니다. 적절한 신용을 제공하기 위해 Smehmood의 원래 답변 stackoverflow.com/a/2490718/468252 에서 버그를 수정했습니다 .
qneill

4

안전을 원한다면 암호 학적으로 건전한 Fernet을 사용할 수 있습니다. 별도로 저장하지 않으려면 정적 "소금"을 사용할 수 있습니다. 사전 및 무지개 공격 방지 만 잃게됩니다. 길거나 짧은 암호를 선택할 수 있기 때문에 선택했습니다. AES에서는 쉽지 않습니다.

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64

#set password
password = "mysecretpassword"
#set message
message = "secretmessage"

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt="staticsalt", iterations=100000, backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)

#encrypt
encrypted = f.encrypt(message)
print encrypted

#decrypt
decrypted = f.decrypt(encrypted)
print decrypted

너무 복잡하면 누군가 simplecrypt를 제안했습니다.

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

솔트를 생성하고 암호화 결과에 포함하기 만하면 반복되는 비밀번호와 메시지가 여전히 무작위로 출력됩니다. 알고리즘의 미래 보장을 위해 반복 값도 포함하지만 여전히 다른 반복 횟수를 사용하여 메시지를 해독 할 수 있습니다.
Martijn Pieters

3

여기에 온 사람 (그리고 바운티 어)은 다른 답변이 제공하지 않는 설정이 많지 않은 원 라이너를 찾는 것처럼 보였습니다. 그래서 나는 base64를 앞당기 고 있습니다.

이제 이것은 기본적인 난독 화일 뿐이며 ** NO WAY OK FOR SECURITY ** 라는 점을 명심 하십시오. 그러나 다음은 몇 가지 한 줄입니다.

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data, key):
    return urlsafe_b64encode(bytes(key+data, 'utf-8'))

def decode(enc, key):
    return urlsafe_b64decode(enc)[len(key):].decode('utf-8')

print(encode('hi', 'there')) # b'dGhlcmVoaQ=='
print(decode(encode('hi', 'there'), 'there')) # 'hi'

참고할 몇 가지 사항 :

  • I / O에 따라 더 많거나 적은 바이트 대 문자열 인코딩 / 디코딩을 직접 처리하고 싶을 것입니다. 조사 bytes()bytes::decode()
  • base64는 사용 된 문자 유형으로 쉽게 인식 할 수 있으며 종종 =문자로 끝납니다 . 저와 같은 사람들은 웹 사이트에서 볼 때 자바 스크립트 콘솔에서이를 해독합니다. btoa(string)(js) 만큼 쉽습니다.
  • 순서는 키 + 데이터입니다. b64에서와 같이 끝에 나타나는 문자는 시작 부분에있는 문자에 따라 다릅니다 (바이트 오프셋 때문입니다. Wikipedia 에 몇 가지 멋진 설명이 있습니다). 이 시나리오에서 인코딩 된 문자열의 시작 부분은 해당 키로 인코딩 된 모든 항목에 대해 동일합니다. 장점은 데이터가 더 난독 화된다는 것입니다. 다른 방법으로 수행하면 키에 관계없이 데이터 부분이 모든 사람에게 정확히 동일하게됩니다.

이제 원하는 것이 어떤 종류의 키도 필요하지 않고 약간의 난독 화만 필요하다면 어떤 종류의 키도없이 base64 만 사용할 수 있습니다.

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data):
    return urlsafe_b64encode(bytes(data, 'utf-8'))

def decode(enc):
    return urlsafe_b64decode(enc).decode()

print(encode('hi')) # b'aGk='
print(decode(encode('hi'))) # 'hi'

2
예, 보안에 신경 쓰지 않는다면 base64가 암호화하는 것보다 낫습니다.
Martijn Pieters

한 줄로 표현되지 않은 다른 답변에 대해서는 질문의 요점이 아닙니다. 그들은 호출 할 두 가지 기능을 요청합니다. 그리고 Fernet(key).encrypt(message)base64 호출과 같은 하나의 표현식입니다.
Martijn Pieters

그리고 모두 제거 해야 key합니다. 개발자의로드주의를 지불하지 않고 복사 스택 오버플로에서 붙여 예정과 것이다 비밀의 열쇠를 가정합니다. 포함해야한다면 최소한 사용하지 말고 어쨌든 사용한다면 예외를 경고하거나 발생 시키십시오. 복사 및 붙여 넣기 문화의 어리 석음과 정상적인 기능을 제공해야하는 책임을 과소 평가하지 마십시오.
Martijn Pieters

3

4 가지 해결책을 제시하겠습니다.

1) cryptography라이브러리 와 함께 Fernet 암호화 사용

다음은 cryptography평소와 같이 설치할 수 있는 패키지를 사용하는 솔루션 입니다 pip install cryptography.

import base64
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

def cipherFernet(password):
    key = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=b'abcd', iterations=1000, backend=default_backend()).derive(password)
    return Fernet(base64.urlsafe_b64encode(key))

def encrypt1(plaintext, password):
    return cipherFernet(password).encrypt(plaintext)

def decrypt1(ciphertext, password):
    return cipherFernet(password).decrypt(ciphertext)

# Example:

print(encrypt1(b'John Doe', b'mypass'))  
# b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg=='
print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'mypass')) 
# b'John Doe'
try:  # test with a wrong password
    print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'wrongpass')) 
except InvalidToken:
    print('Wrong password')

자신의 솔트, 반복 횟수 등으로 조정할 수 있습니다.이 코드는 @HCLivess의 답변과 그리 멀지 않지만 목표는 바로 사용할 수 encrypt있고 decrypt기능을 갖추는 것입니다 . 출처 : https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet .

참고 : .NET과 같은 바이트 대신 문자열을 원하는 경우 .encode().decode()모든 곳에서 사용하십시오 .'John Doe'b'John Doe'


2) Crypto라이브러리를 사용한 간단한 AES 암호화

이것은 Python 3에서 작동합니다.

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

def cipherAES(password, iv):
    key = SHA256.new(password).digest()
    return AES.new(key, AES.MODE_CFB, iv)

def encrypt2(plaintext, password):
    iv = Random.new().read(AES.block_size)
    return base64.b64encode(iv + cipherAES(password, iv).encrypt(plaintext))

def decrypt2(ciphertext, password):
    d = base64.b64decode(ciphertext)
    iv, ciphertext = d[:AES.block_size], d[AES.block_size:]
    return cipherAES(password, iv).decrypt(ciphertext)

# Example:    

print(encrypt2(b'John Doe', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'wrongpass'))  # wrong password: no error, but garbled output

참고 : 제거 할 수 있습니다 base64.b64encode.b64decode텍스트 읽을 수있는 출력을 원하지 않는 경우 및 / 또는 어쨌든 바이너리 파일로 디스크에 암호문을 저장하려는 경우.


3) 더 나은 암호 키 유도 기능을 사용하는 AES 및 Crypto라이브러리를 사용하여 "잘못된 암호 입력"여부를 테스트하는 기능

2) AES "CFB 모드"를 사용하는 솔루션은 괜찮지 만 두 가지 단점이 SHA256(password)있습니다. 룩업 테이블로 쉽게 무차별 대입 할 수 있다는 사실 과 잘못된 암호가 입력되었는지 테스트 할 방법이 없다는 사실입니다. 이 문제는 AES : 잘못된 암호가 입력되었음을 감지하는 방법에 설명 된대로 "GCM 모드"에서 AES를 사용하여 해결됩니다 . 그리고 "당신이 입력 한 암호가 잘못"확보 말을이 방법인가? :

import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES

def cipherAES_GCM(pwd, nonce):
    key = Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100000)
    return Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_GCM, nonce=nonce, mac_len=16)

def encrypt3(plaintext, password):
    nonce = Crypto.Random.new().read(16)
    return nonce + b''.join(cipherAES_GCM(password, nonce).encrypt_and_digest(plaintext))  # you case base64.b64encode it if needed

def decrypt3(ciphertext, password):
    nonce, ciphertext, tag = ciphertext[:16], ciphertext[16:len(ciphertext)-16], ciphertext[-16:]
    return cipherAES_GCM(password, nonce).decrypt_and_verify(ciphertext, tag)

# Example:

print(encrypt3(b'John Doe', b'mypass'))
print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'mypass'))
try:
    print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'wrongpass'))
except ValueError:
    print("Wrong password")

4) RC4 사용 (라이브러리 필요 없음)

https://github.com/bozhu/RC4-Python/blob/master/rc4.py 에서 수정되었습니다 .

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        yield S[(S[i] + S[j]) % 256]

def encryptRC4(plaintext, key, hexformat=False):
    key, plaintext = bytearray(key), bytearray(plaintext)  # necessary for py2, not for py3
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    keystream = PRGA(S)
    return b''.join(b"%02X" % (c ^ next(keystream)) for c in plaintext) if hexformat else bytearray(c ^ next(keystream) for c in plaintext)

print(encryptRC4(b'John Doe', b'mypass'))                           # b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a'
print(encryptRC4(b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a', b'mypass'))   # b'John Doe'

(최신 편집 이후로 오래되었지만 향후 참조를 위해 보관 됨) : Windows + Python 3.6 + pycrypto( pip install pycryptoWindows 에서는 할 수 없음 ) 또는 pycryptodome( 이 포크에서 지원 from Crypto.Cipher import XOR하지 않기 때문에 실패한 모든 답변을 사용하는 데 문제 XOR가 있습니다 pycrypto. 사용하는 솔루션 ... AES도)와 함께 실패했습니다 TypeError: Object type <class 'str'> cannot be passed to C code. 또한 라이브러리 simple-crypt에는 pycrypto종속성이 있으므로 옵션이 아닙니다.


1
솔트 및 반복 횟수를 하드 코딩하고 싶지 않습니다. 임의의 솔트를 생성하고 암호화에서 반복 횟수를 구성 할 수 있도록하고, 해당 정보를 암호화 결과에 포함하고, 해독시 값을 추출하여 사용합니다. 솔트는 주어진 메시지에서 재사용 된 비밀번호의 사소한 인식으로부터 사용자를 보호하며, 반복 횟수는 알고리즘의 미래를 보장합니다.
Martijn Pieters

예, 물론 @MartijnPieters, 그러나 목표는 OP가 요청한 간단한 목적을 위해 일반 텍스트 + 암호의 두 가지 매개 변수 를 사용하여 간단한 코드를 만드는 것 입니다. 물론 더 복잡한 시나리오 (예 : 데이터베이스)의 경우 이러한 모든 추가 매개 변수를 사용합니다.
Basj

추가 매개 변수가 필요하지 않습니다! 불투명 한 반환 값에 인코딩 된 정보를 포함합니다 password_encrypt().
Martijn Pieters

@MartijnPieters 참으로 좋은 솔루션입니다.
Basj

2

작동하지만 암호 길이는 정확히 8. 이것은 간단하며 pyDes 가 필요합니다 .

from pyDes import *

def encode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.encrypt(data)
    return d

def decode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.decrypt(data)
    return d

x = encode('John Doe', 'mypass12')
y = decode(x,'mypass12')

print x
print y

산출:

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

고정 IV를 사용하지 마십시오! IV를 무작위 화하고 대신 암호문과 함께 포함하십시오. 그렇지 않으면 ECB 모드를 사용할 수도 있습니다. 반복되는 일반 텍스트 메시지는 인식하기가 어렵습니다.
Martijn Pieters

또한 pyDes 프로젝트가 죽은 것처럼 보입니다. 홈페이지는 사라졌고 PyPI의 마지막 릴리스는 이제 9 년이되었습니다.
Martijn Pieters

2

원본 메시지의 CRC 체크섬을 포함하는 @qneill 코드의 다른 구현으로, 검사에 실패하면 예외가 발생합니다.

import hashlib
import struct
import zlib

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

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

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


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

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

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

    return dec

2

AES를 사용하여 비밀번호로 문자열을 암호화 할 수 있습니다. 하지만 사람들이 쉽게 추측 할 수 없도록 충분히 강력한 암호를 선택하는 것이 좋습니다 (미안하지만 어쩔 수 없습니다. 저는 보안 문제가되고 싶습니다).

AES는 좋은 키 크기로 강력하지만 PyCrypto에서도 사용하기 쉽습니다.


3
고마워 앨런. 그러나 명확히하기 위해 암호 자체를 암호화하지 않습니다. 위의 예에서는 소스 코드에서 사용하는 간단한 암호 인 "mypass"암호에 따라 "John Doe"문자열을 암호화합니다. 사용자 암호는 관련되지 않으며 다른 매우 민감한 정보도 없습니다. 나는 이것을 명확히하기 위해 내 질문을 편집했습니다.
RexE

1
AES는 올바르게 사용하면 좋습니다. 그러나 잘못 사용하는 것은 쉽습니다. 여기에 안전하지 않은 블록 암호 모드를 사용하는 대답이 하나 이상 있고 IV 값을 처리하는 두 가지 대답이 있습니다. Fernet과 같이 잘 정의 된 레시피로 좋은 라이브러리를 사용하는 것이 좋습니다!
Martijn Pieters

사실 그것은 매우 기민한 관찰입니다. 나는 IV를 한 번 더듬었다.
Alan

0

외부 라이브러리는 비밀 키 암호화 알고리즘을 제공합니다.

예를 들어 CypherPyCrypto모듈 은 다양한 암호화 알고리즘을 제공합니다.

  • 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

MeTooCryptoOpenSSLPython 용 래퍼이며 (다른 기능 중에서) 강력한 범용 암호화 라이브러리를 제공합니다. 대칭 암호 (예 : AES)가 포함됩니다.


0

보안 암호화를 원하는 경우 :

파이썬 2의 경우 keyczar http://www.keyczar.org/ 를 사용해야합니다 .

파이썬 3의 경우 keyczar를 사용할 수있을 때까지 simple-crypt http://pypi.python.org/pypi/simple-crypt를 작성했습니다 .

둘 다 여기에있는 대부분의 다른 답변보다 더 안전하게 만드는 키 강화를 사용합니다. 사용하기 쉽기 때문에 보안이 중요하지 않은 경우에도 사용하고 싶을 수 있습니다.


로부터 Keyczar 저장소 : 중요 사항 : Keyczar가되지 않습니다. Keyczar 개발자는 Tink를 권장 하지만 Python 버전의 Tink는 없습니다.
Martijn Pieters

0

따라서 미션 크리티컬 한 인코딩없으므로 난독 화 를 위해 암호화하기 만하면 됩니다.

Caeser의 암호를 제시하겠습니다

여기에 이미지 설명 입력

Caesar의 암호 또는 Caesar 시프트는 가장 간단하고 널리 알려진 암호화 기술 중 하나입니다. 일반 텍스트의 각 문자가 알파벳 아래로 고정 된 수의 위치에있는 문자로 대체되는 대체 암호 유형입니다. 예를 들어 왼쪽 시프트가 3이면 D는 A로, E는 B가되는 식입니다.

참조 용 샘플 코드 :

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

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

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

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

        return result 

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

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

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

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

        return result 

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

장점 : 요구 사항을 충족하고 간단하며 인코딩 작업을 수행합니다.

단점 : 간단한 무차별 대입 알고리즘으로 크랙 될 수 있습니다 (누구나 모든 추가 결과를 시도 할 가능성이 거의 없음).


0

참조를 위해 디코딩 및 인코딩으로 코드를 하나 더 추가

import base64

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

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

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

if __name__ == '__main__':
    main()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.