[python] PyCrypto AES 256을 사용한 암호화 및 복호화

PyCrypto를 사용하여 메시지와 키라는 두 가지 매개 변수를 허용하고 메시지를 암호화 / 해독하는 두 가지 기능을 작성하려고합니다.

웹에서 도움이되는 몇 가지 링크를 찾았지만 각 링크에 결함이 있습니다.

codekoala에서 이것은 os.urandom을 사용하는데, 이는 PyCrypto에 의해 권장되지 않습니다.

또한 함수에 제공하는 키가 정확한 길이를 보장하지는 않습니다. 그렇게하려면 어떻게해야합니까?

또한 몇 가지 모드가 있는데 어떤 모드가 권장됩니까? 나는 무엇을 사용 해야할지 모르겠다 : /

마지막으로 IV는 정확히 무엇입니까? 암호화 및 암호 해독을 위해 다른 IV를 제공 할 수 있습니까? 그렇지 않으면 다른 결과가 나옵니까?

편집 : 코드 부분이 안전하지 않아 제거되었습니다.



답변

다음은 내 구현이며 일부 수정 사항으로 작동하며 키 및 비밀 문구의 정렬을 32 바이트 및 iv ~ 16 바이트로 향상시킵니다.

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

class AESCipher(object):

    def __init__(self, key): 
        self.bs = AES.block_size
        self.key = hashlib.sha256(key.encode()).digest()

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s)-1:])]


답변

입력 길이가 BLOCK_SIZE의 배수가 아닌 경우- pad암호화 (패딩시) 및 unpad언 패딩 (해독시)의 두 가지 기능이 필요할 수 있습니다 .

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[:-ord(s[len(s)-1:])]

열쇠의 길이를 묻는 것입니까? 키를 직접 사용하지 않고 md5sum을 사용할 수 있습니다.

더구나, PyCrypto 사용 경험이 거의 없다면, IV는 입력이 동일 할 때 암호화의 출력을 혼합하는 데 사용되므로 IV는 임의의 문자열로 선택되어 암호화 출력의 일부로 사용됩니다. 메시지를 해독하는 데 사용하십시오.

그리고 여기 내 구현이 있습니다. 그것이 당신에게 도움이되기를 바랍니다.

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

class AESCipher:
    def __init__( self, key ):
        self.key = key

    def encrypt( self, raw ):
        raw = pad(raw)
        iv = Random.new().read( AES.block_size )
        cipher = AES.new( self.key, AES.MODE_CBC, iv )
        return base64.b64encode( iv + cipher.encrypt( raw ) ) 

    def decrypt( self, enc ):
        enc = base64.b64decode(enc)
        iv = enc[:16]
        cipher = AES.new(self.key, AES.MODE_CBC, iv )
        return unpad(cipher.decrypt( enc[16:] ))


답변

“모드”에 대한 질문을하겠습니다. AES256은 일종의 블록 암호 입니다. 블록 이라고 하는 32 바이트 와 16 바이트 문자열 을 입력으로 받아서 블록 을 출력합니다. 암호화하기 위해 작동 모드 에서 AES 를 사용합니다. 위의 솔루션은 CBC를 사용하는 것이 좋습니다. 다른 하나는 CTR이며 사용하기가 다소 쉽습니다.

from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random

# AES supports multiple key sizes: 16 (AES128), 24 (AES192), or 32 (AES256).
key_bytes = 32

# Takes as input a 32-byte key and an arbitrary-length plaintext and returns a
# pair (iv, ciphtertext). "iv" stands for initialization vector.
def encrypt(key, plaintext):
    assert len(key) == key_bytes

    # Choose a random, 16-byte IV.
    iv = Random.new().read(AES.block_size)

    # Convert the IV to a Python integer.
    iv_int = int(binascii.hexlify(iv), 16) 

    # Create a new Counter object with IV = iv_int.
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)

    # Create AES-CTR cipher.
    aes = AES.new(key, AES.MODE_CTR, counter=ctr)

    # Encrypt and return IV and ciphertext.
    ciphertext = aes.encrypt(plaintext)
    return (iv, ciphertext)

# Takes as input a 32-byte key, a 16-byte IV, and a ciphertext, and outputs the
# corresponding plaintext.
def decrypt(key, iv, ciphertext):
    assert len(key) == key_bytes

    # Initialize counter for decryption. iv should be the same as the output of
    # encrypt().
    iv_int = int(iv.encode('hex'), 16) 
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)

    # Create AES-CTR cipher.
    aes = AES.new(key, AES.MODE_CTR, counter=ctr)

    # Decrypt and return the plaintext.
    plaintext = aes.decrypt(ciphertext)
    return plaintext

(iv, ciphertext) = encrypt(key, 'hella')
print decrypt(key, iv, ciphertext)

이를 종종 AES-CTR이라고합니다. PyCrypto와 함께 AES-CBC를 사용할 때는주의를 기울여야 합니다. 그 이유는 주어진 다른 솔루션으로 예시 된 것처럼 패딩 체계 를 지정해야하기 때문입니다 . 당신이하지 않은 경우 일반적으로, 매우 패딩주의, 거기에 공격 을 완전히 암호화 휴식!

이제 키는 임의의 32 바이트 문자열 이어야 합니다 . 암호로 충분 하지 않습니다 . 일반적으로 키는 다음과 같이 생성됩니다.

# Nominal way to generate a fresh key. This calls the system's random number
# generator (RNG).
key1 = Random.new().read(key_bytes)

키 도 password에서 파생 될 수 있습니다 .

# It's also possible to derive a key from a password, but it's important that
# the password have high entropy, meaning difficult to predict.
password = "This is a rather weak password."

# For added # security, we add a "salt", which increases the entropy.
#
# In this example, we use the same RNG to produce the salt that we used to
# produce key1.
salt_bytes = 8 
salt = Random.new().read(salt_bytes)

# Stands for "Password-based key derivation function 2"
key2 = PBKDF2(password, salt, key_bytes)

위의 일부 솔루션은 키를 파생시키기 위해 SHA256을 사용하는 것이 좋지만 일반적으로 잘못된 암호화 방식 으로 간주됩니다 . 작동 모드에 대한 자세한 내용은 Wikipedia 를 확인하십시오 .


답변

urlsafe_b64encode 및 urlsafe_b64decode를 사용하려는 사람은 다음과 같습니다 (유니 코드 문제와 시간을 보낸 후)

BS = 16
key = hashlib.md5(settings.SECRET_KEY).hexdigest()[:BS]
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, raw):
        raw = pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.urlsafe_b64encode(iv + cipher.encrypt(raw)) 

    def decrypt(self, enc):
        enc = base64.urlsafe_b64decode(enc.encode('utf-8'))
        iv = enc[:BS]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return unpad(cipher.decrypt(enc[BS:]))


답변

당신은 암호 해시 함수 (사용하여 임의의 암호에서 암호를 얻을 수 없습니다 파이썬의 내장 hashSHA-1, SHA-256 등). 파이썬은 표준 라이브러리에서 두 가지를 모두 지원합니다.

import hashlib

hashlib.sha1("this is my awesome password").digest() # => a 20 byte string
hashlib.sha256("another awesome password").digest() # => a 32 byte string

당신은 사용하여 암호화 해시 값을자를 수 [:16]또는 [:24]그것을 당신이 지정한 길이로 보안을 유지합니다.


답변

영감을 얻었지만 효과가 없었던 다른 답변에 감사드립니다.

어떻게 작동하는지 알아 내려고 시간을 보내고 난 후에, 나는 최신와 아래의 구현 해낸 PyCryptodomex의 라이브러리 (그것은 내가 VIRTUALENV .. 휴에, Windows에서 프록시 뒤에를 설정하는 관리 방법 또 다른 이야기이다)

에 작업이 구현시 패딩, 인코딩, 암호화 단계 (및 그 반대로)를 기록해야합니다. 순서를 염두에두고 포장을 풀고 포장을 풀어야합니다.

수입 base64
수입 해시
Cryptodome.Cipher import AES에서
Cryptodome.Random 가져 오기 get_random_bytes에서

__key__ = hashlib.sha256 (b'16 자 키 ') .digest ()

데프 암호화 (원시) :
    BS = AES.block_size
    패드 = λs : s + (BS-len (s) % BS) * chr (BS-len (s) % BS)

    raw = base64.b64encode (패드 (raw) .encode ( 'utf8'))
    iv = get_random_bytes (AES.block_size)
    암호 = AES.new (키 = __key__, 모드 = AES.MODE_CFB, iv = iv)
    base64.b64encode (iv + cipher.encrypt (raw))를 반환

데프 해독 (enc) :
    unpad = 람다 s : s [:-ord (s [-1 :])]

    enc = base64.b64decode (enc)
    iv = enc [: AES.block_size]
    암호 = AES.new (__ key__, AES.MODE_CFB, iv)
    unpad (base64.b64decode (cipher.decrypt (enc [AES.block_size :])). decode ( 'utf8') 반환


답변

다른 사람들을 위해 @Cyril과 @Marcus의 답변을 결합하여 얻은 해독 구현이 있습니다. 이것은 암호화 된 텍스트가 인용되고 base64로 인코딩 된 HTTP 요청을 통해 들어오는 것으로 가정합니다.

import base64
import urllib2
from Crypto.Cipher import AES


def decrypt(quotedEncodedEncrypted):
    key = 'SecretKey'

    encodedEncrypted = urllib2.unquote(quotedEncodedEncrypted)

    cipher = AES.new(key)
    decrypted = cipher.decrypt(base64.b64decode(encodedEncrypted))[:16]

    for i in range(1, len(base64.b64decode(encodedEncrypted))/16):
        cipher = AES.new(key, AES.MODE_CBC, base64.b64decode(encodedEncrypted)[(i-1)*16:i*16])
        decrypted += cipher.decrypt(base64.b64decode(encodedEncrypted)[i*16:])[:16]

    return decrypted.strip()