source

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

manycodes 2023. 1. 15. 17:17
반응형

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

PyCrypto를 사용하여 메시지와 키, 그리고 메시지를 암호화/복호화하는 두 가지 기능을 구축하려고 합니다.

도움이 되는 링크를 웹에서 여러 개 찾았는데 각각에 결함이 있습니다.

codekoala의 이 제품은 os.urandom을 사용하지만 PyCrypto는 이를 권장하지 않습니다.

또한, 제가 함수에 부여하는 키는 정확한 길이가 보장되지 않습니다.어떻게 하면 그렇게 할 수 있을까요?

또한 여러 가지 모드가 있는데, 어떤 모드가 좋을까요?무엇을 사용해야 할지 모르겠습니다:/

마지막으로 링거가 정확히 뭐죠?암호화 및 복호화를 위해 다른 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:])]

하다, 하다, 하다, 하다, 하다, 하다의 두 기능이 할 수 .pad ( 시)와 - (암호화 실행 시)unpad할 때) BLOCK_SIZE의 배수가 아닐 때 (복호화를 수행할 때) 압축을 해제합니다.

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)

키는 패스워드에서도 취득할 수 있습니다.

# 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을 사용할 것을 제안하지만 이는 일반적으로 잘못된 암호화 관행으로 간주됩니다.동작 모드에 대한 자세한 내용은 위키피디아를 참조하십시오.

영감을 주었지만 제게는 효과가 없었던 다른 답변에 감사했습니다.

몇 시간 동안 작동 방식을 파악한 후 최신 PyCryptodomex 라이브러리를 사용하여 다음과 같은 구현을 생각해냈습니다(이것은 제가 프록시를 통해 Windows에서 가상 환경에서 셋업할 수 있었던 또 다른 사례입니다).휴)

실장 작업에서는 패딩, 부호화, 암호화 스텝(및 그 반대도 마찬가지)을 적어 둡니다.포장과 포장은 순서를 염두에 두고 해야 합니다.

import base64
import hashlib
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes

__key__ = hashlib.sha256(b'16-character key').digest()

def encrypt(raw):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

    raw = base64.b64encode(pad(raw).encode('utf8'))
    iv = get_random_bytes(AES.block_size)
    cipher = AES.new(key= __key__, mode= AES.MODE_CFB,iv= iv)
    return base64.b64encode(iv + cipher.encrypt(raw))

def decrypt(enc):
    unpad = lambda s: s[:-ord(s[-1:])]

    enc = base64.b64decode(enc)
    iv = enc[:AES.block_size]
    cipher = AES.new(__key__, AES.MODE_CFB, iv)
    return unpad(base64.b64decode(cipher.decrypt(enc[AES.block_size:])).decode('utf8'))

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

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

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]지정한 기간까지 보안이 유지됩니다.

이에 대한 또 다른 견해(위의 솔루션에서 도출된 것)는 다음과 같습니다만,

  • 패딩에 null을 사용합니다.
  • lamda를 사용하지 않음(팬이었던 적이 없음)
  • python 2.7 및 3.6.5로 테스트 완료

    #!/usr/bin/python2.7
    # you'll have to adjust for your setup, e.g., #!/usr/bin/python3
    
    
    import base64, re
    from Crypto.Cipher import AES
    from Crypto import Random
    from django.conf import settings
    
    class AESCipher:
        """
          Usage:
          aes = AESCipher( settings.SECRET_KEY[:16], 32)
          encryp_msg = aes.encrypt( 'ppppppppppppppppppppppppppppppppppppppppppppppppppppppp' )
          msg = aes.decrypt( encryp_msg )
          print("'{}'".format(msg))
        """
        def __init__(self, key, blk_sz):
            self.key = key
            self.blk_sz = blk_sz
    
        def encrypt( self, raw ):
            if raw is None or len(raw) == 0:
                raise NameError("No value given to encrypt")
            raw = raw + '\0' * (self.blk_sz - len(raw) % self.blk_sz)
            raw = raw.encode('utf-8')
            iv = Random.new().read( AES.block_size )
            cipher = AES.new( self.key.encode('utf-8'), AES.MODE_CBC, iv )
            return base64.b64encode( iv + cipher.encrypt( raw ) ).decode('utf-8')
    
        def decrypt( self, enc ):
            if enc is None or len(enc) == 0:
                raise NameError("No value given to decrypt")
            enc = base64.b64decode(enc)
            iv = enc[:16]
            cipher = AES.new(self.key.encode('utf-8'), AES.MODE_CBC, iv )
            return re.sub(b'\x00*$', b'', cipher.decrypt( enc[16:])).decode('utf-8')
    

다른 사람의 이익을 위해 @Cyril과 @Marcus의 답을 조합하여 복호화를 실시했습니다.이것은 HTTP 요구를 경유하여 암호화되어 있는 것을 전제로 하고 있습니다.따옴표로 둘러싸인 텍스트로 base64가 부호화되었습니다.

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

Crypto ★★★★★★★★★★★★★★★★★」PyCryptodomex…하다

import base64
import hashlib
from Cryptodome.Cipher import AES as domeAES
from Cryptodome.Random import get_random_bytes
from Crypto import Random
from Crypto.Cipher import AES as cryptoAES

BLOCK_SIZE = AES.block_size

key = "my_secret_key".encode()
__key__ = hashlib.sha256(key).digest()
print(__key__)

def encrypt(raw):
    BS = cryptoAES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
    raw = base64.b64encode(pad(raw).encode('utf8'))
    iv = get_random_bytes(cryptoAES.block_size)
    cipher = cryptoAES.new(key= __key__, mode= cryptoAES.MODE_CFB,iv= iv)
    a= base64.b64encode(iv + cipher.encrypt(raw))
    IV = Random.new().read(BLOCK_SIZE)
    aes = domeAES.new(__key__, domeAES.MODE_CFB, IV)
    b = base64.b64encode(IV + aes.encrypt(a))
    return b

def decrypt(enc):
    passphrase = __key__
    encrypted = base64.b64decode(enc)
    IV = encrypted[:BLOCK_SIZE]
    aes = domeAES.new(passphrase, domeAES.MODE_CFB, IV)
    enc = aes.decrypt(encrypted[BLOCK_SIZE:])
    unpad = lambda s: s[:-ord(s[-1:])]
    enc = base64.b64decode(enc)
    iv = enc[:cryptoAES.block_size]
    cipher = cryptoAES.new(__key__, cryptoAES.MODE_CFB, iv)
    b=  unpad(base64.b64decode(cipher.decrypt(enc[cryptoAES.block_size:])).decode('utf8'))
    return b

encrypted_data =encrypt("Hi Steven!!!!!")
print(encrypted_data)
print("=======")
decrypted_data = decrypt(encrypted_data)
print(decrypted_data)

조금 늦었지만 이게 도움이 될 것 같아요.PKCS#7 패딩과 같은 사용 스킴에 대해서는 아무도 언급하지 않았습니다.대신 이전 함수를 사용하여 패딩(암호화를 수행할 때) 및 패딩 해제(복호화를 수행할 때)할 수 있습니다.소스코드 전체를 아래에 제공하겠습니다.

import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
import pkcs7
class Encryption:

    def __init__(self):
        pass

    def Encrypt(self, PlainText, SecurePassword):
        pw_encode = SecurePassword.encode('utf-8')
        text_encode = PlainText.encode('utf-8')

        key = hashlib.sha256(pw_encode).digest()
        iv = Random.new().read(AES.block_size)

        cipher = AES.new(key, AES.MODE_CBC, iv)
        pad_text = pkcs7.encode(text_encode)
        msg = iv + cipher.encrypt(pad_text)

        EncodeMsg = base64.b64encode(msg)
        return EncodeMsg

    def Decrypt(self, Encrypted, SecurePassword):
        decodbase64 = base64.b64decode(Encrypted.decode("utf-8"))
        pw_encode = SecurePassword.decode('utf-8')

        iv = decodbase64[:AES.block_size]
        key = hashlib.sha256(pw_encode).digest()

        cipher = AES.new(key, AES.MODE_CBC, iv)
        msg = cipher.decrypt(decodbase64[AES.block_size:])
        pad_text = pkcs7.decode(msg)

        decryptedString = pad_text.decode('utf-8')
        return decryptedString

import StringIO
import binascii


def decode(text, k=16):
    nl = len(text)
    val = int(binascii.hexlify(text[-1]), 16)
    if val > k:
        raise ValueError('Input is not padded or padding is corrupt')

    l = nl - val
    return text[:l]


def encode(text, k=16):
    l = len(text)
    output = StringIO.StringIO()
    val = k - (l % k)
    for _ in xrange(val):
        output.write('%02x' % val)
    return text + binascii.unhexlify(output.getvalue())

https://stackoverflow.com/a/21928790/11402877

호환 utf-8 부호화

def _pad(self, s):
    s = s.encode()
    res = s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs).encode()
    return res
from Crypto import Random
from Crypto.Cipher import AES
import base64

BLOCK_SIZE=16
def trans(key):
     return md5.new(key).digest()

def encrypt(message, passphrase):
    passphrase = trans(passphrase)
    IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return base64.b64encode(IV + aes.encrypt(message))

def decrypt(encrypted, passphrase):
    passphrase = trans(passphrase)
    encrypted = base64.b64decode(encrypted)
    IV = encrypted[:BLOCK_SIZE]
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return aes.decrypt(encrypted[BLOCK_SIZE:])

AES256과 utf8mb4를 사용한 라틴어 및 특수문자(중국어) 암호화 및 복호화:

필요한 분들을 위해encrypt그리고.decrypt이 작업을 수행하기 위해 @MIKEE 코드를 변경한 것입니다.

** UTF8 만으로는 이런 유형의 인코딩을 처리할 수 없습니다**

import base64, re
from Crypto.Cipher import AES
from Crypto import Random
from django.conf import settings

import codecs

# make utf8mb4 recognizable.
codecs.register(lambda name: codecs.lookup('utf8') if name == 'utf8mb4' else None)


class AESCipher:

    def __init__(self, key, blk_sz):
        self.key = key
        self.blk_sz = blk_sz

    def encrypt( self, raw ):
        # raw is the main value
        if raw is None or len(raw) == 0:
            raise NameError("No value given to encrypt")
        raw = raw + '\0' * (self.blk_sz - len(raw) % self.blk_sz)
        raw = raw.encode('utf8mb4')
        # Initialization vector to avoid same encrypt for same strings.
        iv = Random.new().read( AES.block_size )
        cipher = AES.new( self.key.encode('utf8mb4'), AES.MODE_CFB, iv )
        return base64.b64encode( iv + cipher.encrypt( raw ) ).decode('utf8mb4')

    def decrypt( self, enc ):
        # enc is the encrypted value
        if enc is None or len(enc) == 0:
            raise NameError("No value given to decrypt")
        enc = base64.b64decode(enc)
        iv = enc[:16]
        # AES.MODE_CFB that allows bigger length or latin values
        cipher = AES.new(self.key.encode('utf8mb4'), AES.MODE_CFB, iv )
        return re.sub(b'\x00*$', b'', cipher.decrypt( enc[16:])).decode('utf8mb4')

사용방법:

>>> from django.conf import settings
>>> from aesencryption import AESCipher
>>> 
>>> aes = AESCipher(settings.SECRET_KEY[:16], 32)
>>> 
>>> value = aes.encrypt('漢字')
>>> 
>>> value
'hnuRwBjwAHDp5X0DmMF3lWzbjR0r81WlW9MRrWukgQwTL0ZI88oQaWvMfBM+W87w9JtSTw=='
>>> dec_value = aes.decrypt(value)
>>> dec_value
'漢字'
>>>

다음과 같은 라틴 문자도 마찬가지입니다.ã, á, à, â, ã, ç기타.

주의사항

데이터베이스에 라틴 값을 저장하려면 이러한 유형의 데이터를 허용하도록 설정해야 합니다.따라서 db가 다음과 같이 설정되어 있는 경우utf-8이러한 유형의 데이터를 받아들이지 않습니다.거기서도 갈아탈 필요가 있어요.

피크립토는 낡고 망가졌어요
요즘은 암호학이 더 잘 지원되고 있다.
여기 또 다른 구현이 있습니다.
바이트가 반환되므로 base64를 사용하여 전송할 문자열로 변환해야 합니다.

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

_BLOCK_SIZE = 16

class AesStringCipher:
    def __init__(self, key): 
        self._key = hashlib.sha256(key.encode()).digest()

    def encrypt_str(self, raw:str) -> bytes:
        iv = os.urandom(_BLOCK_SIZE)
        cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv), default_backend())
        encryptor = cipher.encryptor()
        raw = _pad(raw)
        return iv + encryptor.update(raw.encode('utf-8')) + encryptor.finalize()

    def decrypt_str(self, enc:bytes) -> str:
        iv = enc[:_BLOCK_SIZE]
        enc = enc[_BLOCK_SIZE:]
        cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv), default_backend())
        decryptor = cipher.decryptor()
        raw = decryptor.update(enc) + decryptor.finalize()
        raw = raw.decode('utf-8')
        return _unpad(raw)

def _pad(s:str) -> str:
    padding = (_BLOCK_SIZE - (len(s) % _BLOCK_SIZE))
    return s + padding * chr(padding)

def _unpad(s:str) -> str:
    return s[:-ord(s[len(s)-1:])]


if __name__ == '__main__':
    cipher = AesStringCipher('my secret password')

    secret_msg = 'this is a super secret msg ...'
    enc_msg = cipher.encrypt_str(secret_msg)
    dec_msg = cipher.decrypt_str(enc_msg)

    assert secret_msg == dec_msg

새로운 django-mirage-field 패키지를 사용할 수 있습니다.

언급URL : https://stackoverflow.com/questions/12524994/encrypt-decrypt-using-pycrypto-aes-256

반응형