복붙노트

[PYTHON] 파이썬에서 OpenSSL AES 암호화 파일을 해독하는 방법은 무엇입니까?

PYTHON

파이썬에서 OpenSSL AES 암호화 파일을 해독하는 방법은 무엇입니까?

OpenSSL은 AES 암호화를위한 인기있는 (그러나 안전하지 않은 - 아래 참조) 명령 행 인터페이스를 제공합니다.

openssl aes-256-cbc -salt -in filename -out filename.enc

Python은 AES를 PyCrypto 패키지 형태로 지원하지만 도구 만 제공합니다. Python / PyCrypto를 사용하여 OpenSSL을 사용하여 암호화 된 파일의 암호를 해독하는 방법은 무엇입니까?

이 질문은 동일한 체계를 사용하여 파이썬에서의 암호화와 관련이있었습니다. 나는 그 부분을 제거하여 누구나 그것을 사용하지 못하도록 막았다. 오늘날의 표준에서는 안전하지 않기 때문에이 방법으로 더 이상 데이터를 암호화하지 마십시오. 다른 옵션이없는 경우, 역 호환성보다 다른 이유로 암호 해독 만 사용해야합니다. 암호화하고 싶습니까? 가능한 경우 NaCl / libsodium을 사용하십시오.

해결법

  1. ==============================

    1.파이썬의 인기를 감안할 때 처음에는이 질문에 대한 완전한 답이 없다는 점에 실망했습니다. 이 보드에서 다른 답변뿐만 아니라 다른 리소스를 읽음으로써 올바른 결과를 얻을 수있게되었습니다. 나중에 참조하고 검토 할 때 결과를 공유 할 수 있다고 생각했습니다. 나는 결코 암호 전문가가 아니다! 그러나 아래 코드는 원활하게 작동하는 것으로 보입니다.

    파이썬의 인기를 감안할 때 처음에는이 질문에 대한 완전한 답이 없다는 점에 실망했습니다. 이 보드에서 다른 답변뿐만 아니라 다른 리소스를 읽음으로써 올바른 결과를 얻을 수있게되었습니다. 나중에 참조하고 검토 할 때 결과를 공유 할 수 있다고 생각했습니다. 나는 결코 암호 전문가가 아니다! 그러나 아래 코드는 원활하게 작동하는 것으로 보입니다.

    from hashlib import md5
    from Crypto.Cipher import AES
    from Crypto import Random
    
    def derive_key_and_iv(password, salt, key_length, iv_length):
        d = d_i = ''
        while len(d) < key_length + iv_length:
            d_i = md5(d_i + password + salt).digest()
            d += d_i
        return d[:key_length], d[key_length:key_length+iv_length]
    
    def decrypt(in_file, out_file, password, key_length=32):
        bs = AES.block_size
        salt = in_file.read(bs)[len('Salted__'):]
        key, iv = derive_key_and_iv(password, salt, key_length, bs)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        next_chunk = ''
        finished = False
        while not finished:
            chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
            if len(next_chunk) == 0:
                padding_length = ord(chunk[-1])
                chunk = chunk[:-padding_length]
                finished = True
            out_file.write(chunk)
    

    용법:

    with open(in_filename, 'rb') as in_file, open(out_filename, 'wb') as out_file:
        decrypt(in_file, out_file, password)
    

    이 문제를 개선하거나 더 유연하게 확장 할 수있는 기회가 있다면 (예 : 소금없이 작동하도록 설정하거나 Python 3과의 호환성을 제공) 기분 전환하십시오.

    이 답변은 동일한 스키마를 사용하여 파이썬에서의 암호화와 관련이있었습니다. 나는 그 부분을 제거하여 누구나 그것을 사용하지 못하도록 막았다. 오늘날의 표준에서는 안전하지 않기 때문에이 방법으로 더 이상 데이터를 암호화하지 마십시오. 다른 옵션이없는 경우, 역 호환성보다 다른 이유로 암호 해독 만 사용해야합니다. 암호화하고 싶습니까? 가능한 경우 NaCl / libsodium을 사용하십시오.

  2. ==============================

    2.나는 몇 가지 수정 사항으로 코드를 다시 게시하고있다 (나는 당신의 버전을 모호하게하고 싶지 않았다). 코드가 작동하는 동안에는 패딩과 관련된 일부 오류를 감지하지 못합니다. 특히 제공된 암호 해독 키가 올바르지 않은 경우 패딩 논리가 이상하게 작동 할 수 있습니다. 변경 사항에 동의하면 솔루션을 업데이트 할 수 있습니다.

    나는 몇 가지 수정 사항으로 코드를 다시 게시하고있다 (나는 당신의 버전을 모호하게하고 싶지 않았다). 코드가 작동하는 동안에는 패딩과 관련된 일부 오류를 감지하지 못합니다. 특히 제공된 암호 해독 키가 올바르지 않은 경우 패딩 논리가 이상하게 작동 할 수 있습니다. 변경 사항에 동의하면 솔루션을 업데이트 할 수 있습니다.

    from hashlib import md5
    from Crypto.Cipher import AES
    from Crypto import Random
    
    def derive_key_and_iv(password, salt, key_length, iv_length):
        d = d_i = ''
        while len(d) < key_length + iv_length:
            d_i = md5(d_i + password + salt).digest()
            d += d_i
        return d[:key_length], d[key_length:key_length+iv_length]
    
    # This encryption mode is no longer secure by today's standards.
    # See note in original question above.
    def obsolete_encrypt(in_file, out_file, password, key_length=32):
        bs = AES.block_size
        salt = Random.new().read(bs - len('Salted__'))
        key, iv = derive_key_and_iv(password, salt, key_length, bs)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        out_file.write('Salted__' + salt)
        finished = False
        while not finished:
            chunk = in_file.read(1024 * bs)
            if len(chunk) == 0 or len(chunk) % bs != 0:
                padding_length = bs - (len(chunk) % bs)
                chunk += padding_length * chr(padding_length)
                finished = True
            out_file.write(cipher.encrypt(chunk))
    
    def decrypt(in_file, out_file, password, key_length=32):
        bs = AES.block_size
        salt = in_file.read(bs)[len('Salted__'):]
        key, iv = derive_key_and_iv(password, salt, key_length, bs)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        next_chunk = ''
        finished = False
        while not finished:
            chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
            if len(next_chunk) == 0:
                padding_length = ord(chunk[-1])
                if padding_length < 1 or padding_length > bs:
                   raise ValueError("bad decrypt pad (%d)" % padding_length)
                # all the pad-bytes must be the same
                if chunk[-padding_length:] != (padding_length * chr(padding_length)):
                   # this is similar to the bad decrypt:evp_enc.c from openssl program
                   raise ValueError("bad decrypt")
                chunk = chunk[:-padding_length]
                finished = True
            out_file.write(chunk)
    
  3. ==============================

    3.아래의 코드는 Python 3과 호환되어야하며 코드에 기록 된 작은 변경 사항이 있어야합니다. 또한 Crypto.Random 대신 os.urandom을 사용하려고합니다. 'Salted__'는 salt_header로 바뀌며 필요에 따라 맞출 수도 있고 비워 둘 수도 있습니다.

    아래의 코드는 Python 3과 호환되어야하며 코드에 기록 된 작은 변경 사항이 있어야합니다. 또한 Crypto.Random 대신 os.urandom을 사용하려고합니다. 'Salted__'는 salt_header로 바뀌며 필요에 따라 맞출 수도 있고 비워 둘 수도 있습니다.

    from os import urandom
    from hashlib import md5
    
    from Crypto.Cipher import AES
    
    def derive_key_and_iv(password, salt, key_length, iv_length):
        d = d_i = b''  # changed '' to b''
        while len(d) < key_length + iv_length:
            # changed password to str.encode(password)
            d_i = md5(d_i + str.encode(password) + salt).digest()
            d += d_i
        return d[:key_length], d[key_length:key_length+iv_length]
    
    def encrypt(in_file, out_file, password, salt_header='', key_length=32):
        # added salt_header=''
        bs = AES.block_size
        # replaced Crypt.Random with os.urandom
        salt = urandom(bs - len(salt_header))
        key, iv = derive_key_and_iv(password, salt, key_length, bs)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        # changed 'Salted__' to str.encode(salt_header)
        out_file.write(str.encode(salt_header) + salt)
        finished = False
        while not finished:
            chunk = in_file.read(1024 * bs) 
            if len(chunk) == 0 or len(chunk) % bs != 0:
                padding_length = (bs - len(chunk) % bs) or bs
                # changed right side to str.encode(...)
                chunk += str.encode(
                    padding_length * chr(padding_length))
                finished = True
            out_file.write(cipher.encrypt(chunk))
    
    def decrypt(in_file, out_file, password, salt_header='', key_length=32):
        # added salt_header=''
        bs = AES.block_size
        # changed 'Salted__' to salt_header
        salt = in_file.read(bs)[len(salt_header):]
        key, iv = derive_key_and_iv(password, salt, key_length, bs)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        next_chunk = ''
        finished = False
        while not finished:
            chunk, next_chunk = next_chunk, cipher.decrypt(
                in_file.read(1024 * bs))
            if len(next_chunk) == 0:
                padding_length = chunk[-1]  # removed ord(...) as unnecessary
                chunk = chunk[:-padding_length]
                finished = True 
            out_file.write(bytes(x for x in chunk))  # changed chunk to bytes(...)
    
  4. ==============================

    4.이것이 조금 늦다는 것을 알고 있지만 여기에 2013 년에 python pycrypto 패키지를 사용하여 openssl과 호환되는 방식으로 암호화 / 해독하는 방법에 대한 블로그를 작성했습니다. python2.7 및 python3.x에서 테스트되었습니다. 소스 코드와 테스트 스크립트는 여기에서 찾을 수 있습니다.

    이것이 조금 늦다는 것을 알고 있지만 여기에 2013 년에 python pycrypto 패키지를 사용하여 openssl과 호환되는 방식으로 암호화 / 해독하는 방법에 대한 블로그를 작성했습니다. python2.7 및 python3.x에서 테스트되었습니다. 소스 코드와 테스트 스크립트는 여기에서 찾을 수 있습니다.

    이 솔루션과 위의 우수한 솔루션의 주요 차이점 중 하나는 파이프와 파일 I / O를 구분하여 일부 응용 프로그램에서 문제를 일으킬 수 있다는 것입니다.

    해당 블로그의 주요 기능은 다음과 같습니다.

    # ================================================================
    # get_key_and_iv
    # ================================================================
    def get_key_and_iv(password, salt, klen=32, ilen=16, msgdgst='md5'):
        '''
        Derive the key and the IV from the given password and salt.
    
        This is a niftier implementation than my direct transliteration of
        the C++ code although I modified to support different digests.
    
        CITATION: http://stackoverflow.com/questions/13907841/implement-openssl-aes-encryption-in-python
    
        @param password  The password to use as the seed.
        @param salt      The salt.
        @param klen      The key length.
        @param ilen      The initialization vector length.
        @param msgdgst   The message digest algorithm to use.
        '''
        # equivalent to:
        #   from hashlib import <mdi> as mdf
        #   from hashlib import md5 as mdf
        #   from hashlib import sha512 as mdf
        mdf = getattr(__import__('hashlib', fromlist=[msgdgst]), msgdgst)
        password = password.encode('ascii', 'ignore')  # convert to ASCII
    
        try:
            maxlen = klen + ilen
            keyiv = mdf(password + salt).digest()
            tmp = [keyiv]
            while len(tmp) < maxlen:
                tmp.append( mdf(tmp[-1] + password + salt).digest() )
                keyiv += tmp[-1]  # append the last byte
            key = keyiv[:klen]
            iv = keyiv[klen:klen+ilen]
            return key, iv
        except UnicodeDecodeError:
            return None, None
    
    
    # ================================================================
    # encrypt
    # ================================================================
    def encrypt(password, plaintext, chunkit=True, msgdgst='md5'):
        '''
        Encrypt the plaintext using the password using an openssl
        compatible encryption algorithm. It is the same as creating a file
        with plaintext contents and running openssl like this:
    
        $ cat plaintext
        <plaintext>
        $ openssl enc -e -aes-256-cbc -base64 -salt \\
            -pass pass:<password> -n plaintext
    
        @param password  The password.
        @param plaintext The plaintext to encrypt.
        @param chunkit   Flag that tells encrypt to split the ciphertext
                         into 64 character (MIME encoded) lines.
                         This does not affect the decrypt operation.
        @param msgdgst   The message digest algorithm.
        '''
        salt = os.urandom(8)
        key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst)
        if key is None:
            return None
    
        # PKCS#7 padding
        padding_len = 16 - (len(plaintext) % 16)
        if isinstance(plaintext, str):
            padded_plaintext = plaintext + (chr(padding_len) * padding_len)
        else: # assume bytes
            padded_plaintext = plaintext + (bytearray([padding_len] * padding_len))
    
        # Encrypt
        cipher = AES.new(key, AES.MODE_CBC, iv)
        ciphertext = cipher.encrypt(padded_plaintext)
    
        # Make openssl compatible.
        # I first discovered this when I wrote the C++ Cipher class.
        # CITATION: http://projects.joelinoff.com/cipher-1.1/doxydocs/html/
        openssl_ciphertext = b'Salted__' + salt + ciphertext
        b64 = base64.b64encode(openssl_ciphertext)
        if not chunkit:
            return b64
    
        LINELEN = 64
        chunk = lambda s: b'\n'.join(s[i:min(i+LINELEN, len(s))]
                                    for i in range(0, len(s), LINELEN))
        return chunk(b64)
    
    
    # ================================================================
    # decrypt
    # ================================================================
    def decrypt(password, ciphertext, msgdgst='md5'):
        '''
        Decrypt the ciphertext using the password using an openssl
        compatible decryption algorithm. It is the same as creating a file
        with ciphertext contents and running openssl like this:
    
        $ cat ciphertext
        # ENCRYPTED
        <ciphertext>
        $ egrep -v '^#|^$' | \\
            openssl enc -d -aes-256-cbc -base64 -salt -pass pass:<password> -in ciphertext
        @param password   The password.
        @param ciphertext The ciphertext to decrypt.
        @param msgdgst    The message digest algorithm.
        @returns the decrypted data.
        '''
    
        # unfilter -- ignore blank lines and comments
        if isinstance(ciphertext, str):
            filtered = ''
            nl = '\n'
            re1 = r'^\s*$'
            re2 = r'^\s*#'
        else:
            filtered = b''
            nl = b'\n'
            re1 = b'^\\s*$'
            re2 = b'^\\s*#'
    
        for line in ciphertext.split(nl):
            line = line.strip()
            if re.search(re1,line) or re.search(re2, line):
                continue
            filtered += line + nl
    
        # Base64 decode
        raw = base64.b64decode(filtered)
        assert(raw[:8] == b'Salted__' )
        salt = raw[8:16]  # get the salt
    
        # Now create the key and iv.
        key, iv = get_key_and_iv(password, salt, msgdgst=msgdgst)
        if key is None:
            return None
    
        # The original ciphertext
        ciphertext = raw[16:]
    
        # Decrypt
        cipher = AES.new(key, AES.MODE_CBC, iv)
        padded_plaintext = cipher.decrypt(ciphertext)
    
        if isinstance(padded_plaintext, str):
            padding_len = ord(padded_plaintext[-1])
        else:
            padding_len = padded_plaintext[-1]
        plaintext = padded_plaintext[:-padding_len]
        return plaintext
    
  5. ==============================

    5.그러나 파일을 암호화하고 암호 해독하기 만하면됩니다.

    그러나 파일을 암호화하고 암호 해독하기 만하면됩니다.

    여기에서 복사 한 자기 대답. 아마도 이것은 더 간단하고 안전한 옵션이라고 생각합니다. 그것이 얼마나 안전한지에 대한 몇 가지 전문가 의견에 관심이 있지만.

    Python 3.6과 SimpleCrypt를 사용하여 파일을 암호화 한 다음 업로드했습니다.

    파일을 암호화하는 데 사용한 코드라고 생각합니다.

    from simplecrypt import encrypt, decrypt
    f = open('file.csv','r').read()
    ciphertext = encrypt('USERPASSWORD',f.encode('utf8')) # I am not certain of whether I used the .encode('utf8')
    e = open('file.enc','wb') # file.enc doesn't need to exist, python will create it
    e.write(ciphertext)
    e.close
    

    이것은 런타임에 암호를 해독하는 데 사용하는 코드입니다. getpass ( "password :")를 인수로 실행하므로 암호 변수를 메모리에 저장할 필요가 없습니다.

    from simplecrypt import encrypt, decrypt
    from getpass import getpass
    
    # opens the file
    f = open('file.enc','rb').read()
    
    print('Please enter the password and press the enter key \n Decryption may take some time')
    
    # Decrypts the data, requires a user-input password
    plaintext = decrypt(getpass("password: "), f).decode('utf8')
    print('Data have been Decrypted')
    

    파이썬 2.7에서는 UTF-8 인코딩 동작이 다르므로 코드가 약간 다를 수 있습니다.

  6. from https://stackoverflow.com/questions/16761458/how-to-decrypt-openssl-aes-encrypted-files-in-python by cc-by-sa and MIT license