使用 PyCrypto AES 进行 Python 加密

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14179784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 10:41:03  来源:igfitidea点击:

Python Encrypting with PyCrypto AES

pythonpycrypto

提问by if __name__ is None

I just found pycrypto today, and I've been working on my AES encryption class. Unfortunately it only half-works. self.h.md5 outputs md5 hash in hex format, and is 32byte. This is the output. It seems to decrypt the message, but it puts random characters after decryption, in this case \n\n\n... I think I have a problem with block size of self.data, anyone know how to fix this?

我今天刚找到 pycrypto,我一直在研究我的 AES 加密类。不幸的是,它只起作用了一半。self.h.md5 以十六进制格式输出 md5 哈希,并且是 32 字节。这是输出。它似乎解密了消息,但它在解密后放置了随机字符,在这种情况下 \n\n\n... 我想我的 self.data 块大小有问题,有人知道如何解决这个问题吗?

Jans-MacBook-Pro:test2 jan$ ../../bin/python3 data.py b'RLfGmn5jf5WTJphnmW0hXG7IaIYcCRpjaTTqwXR6yiJCUytnDib+GQYlFORm+jIctest 1 2 3 4 5 endtest\n\n\n\n\n\n\n\n\n\n'

Jans-MacBook-Pro:test2 jan$ ../../bin/python3 data.py b'RLfGmn5jf5WTJphnmW0hXG7IaIYcCRpjaTTqwXR6yiJCUytnDib+GQYlFORm+jIctest 1 2 3 4 5\n\n\n\n\n \n\n'

from Crypto.Cipher import AES
from base64 import b64encode, b64decode
from os import urandom

class Encryption():
    def __init__(self):
        self.h = Hash()

    def values(self, data, key):
        self.data = data
        self.key = key
        self.mode = AES.MODE_CBC
        self.iv = urandom(16)
        if not self.key:
            self.key = Cfg_Encrypt_Key
        self.key = self.h.md5(self.key, True)

    def encrypt(self, data, key):
        self.values(data, key)
        return b64encode(self.iv + AES.new(self.key, self.mode, self.iv).encrypt(self.data))

    def decrypt(self, data, key):
        self.values(data, key)
        self.iv = b64decode(self.data)[:16]
        return AES.new(self.key, self.mode, self.iv).decrypt(b64decode(self.data)[16:])

采纳答案by SquareRootOfTwentyThree

To be honest, the characters "\n\n\n\n\n\n\n\n\n\n"don't look that random to me. ;-)

老实说,字符“\n\n\n\n\n\n\n\n\n\n”在我看来并不那么随意。;-)

You are using AES in CBC mode. That requires length of plaintext and ciphertext to be always a multiple of 16 bytes. With the code you show, you should actually see an exception being raised when datapassed to encrypt()does not fulfill such condition. It looks like you added enough new line characters ('\n'to whatever the input is until the plaintext happened to be aligned.

您在 CBC 模式下使用 AES。这要求明文和密文的长度始终是 16 字节的倍数。使用您显示的代码,您实际上应该看到在data传递给encrypt()不满足此类条件时引发的异常。看起来您向任何输入添加了足够的换行符 ( '\n',直到纯文本碰巧对齐。

Apart from that, there are two common ways to solve the alignment issue:

除此之外,有两种常见的方法可以解决对齐问题:

  1. Switch from CBC (AES.MODE_CBC) to CFB (AES.MODE_CFB). With the default segment_sizeused by PyCrypto, you will not have any restriction on plaintext and ciphertext lengths.

  2. Keep CBC and use a padding scheme like PKCS#7, that is:

    • before encrypting a plaintext of Xbytes, append to the back as many bytes you need to to reach the next 16 byte boundary. All padding bytes have the same value: the number of bytes that you are adding:

      length = 16 - (len(data) % 16)
      data += bytes([length])*length
      

      That's Python 3 style. In Python 2, you would have:

      length = 16 - (len(data) % 16)
      data += chr(length)*length
      
    • after decrypting, remove from the back of the plaintext as many bytes as indicated by padding:

      data = data[:-data[-1]]
      
  1. 从 CBC ( AES.MODE_CBC)切换到 CFB ( AES.MODE_CFB)。segment_size使用 PyCrypto使用的默认值,您将不会对明文和密文长度有任何限制。

  2. 保留 CBC 并使用像 PKCS#7 这样的填充方案,即:

    • 在加密X字节的明文之前,将达到下一个 16 字节边界所需的字节数附加到后面。所有填充字节都具有相同的值:您要添加的字节数:

      length = 16 - (len(data) % 16)
      data += bytes([length])*length
      

      那是 Python 3 风格。在 Python 2 中,您将拥有:

      length = 16 - (len(data) % 16)
      data += chr(length)*length
      
    • 解密后,从明文后面删除填充指示的字节数:

      data = data[:-data[-1]]
      

Even though I understand in your case it is just a class exercise, I would like to point out that it is insecure to send data without any form of authentication (e.g. a MAC).

尽管我理解在您的情况下这只是课堂练习,但我想指出的是,在没有任何形式的身份验证(例如 MAC)的情况下发送数据是不安全的。

回答by Armin Rigo

AES.new().encrypt()and .decrypt()take as both input and output strings whose length is a multiple of 16. You have to fix it in one way or another. For example you can store the real length at the start, and use that to truncate the decrypted string.

AES.new().encrypt()并将其.decrypt()作为输入和输出字符串,其长度是 16 的倍数。您必须以一种或另一种方式修复它。例如,您可以在开始时存储实际长度,并使用它来截断解密的字符串。

Note also that while it's the only restriction for AES, other modules (notably in Crypto.PublicKey) have additional restrictions that comes from their mathematical implementation and that shouldn't (in my opinion) be visible to the end user, but are. For example Crypto.PublicKey.ElGamalwill encrypt any short string, but if it starts with null characters, they are lost upon decryption.

另请注意,虽然它是 AES 的唯一限制,但其他模块(特别是在 中Crypto.PublicKey)具有来自其数学实现的额外限制,并且(在我看来)最终用户不应该看到,但是。例如Crypto.PublicKey.ElGamal将加密任何短字符串,但如果它以空字符开头,则它们在解密时丢失。

回答by Abhisheietk

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

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 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)
    #print in_file
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    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))
    in_file.close()
    out_file.close()

def decrypt(in_file, out_file, password, key_length=32):
    bs = AES.block_size

    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    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)    
    in_file.close()
    out_file.close()

def encode(in_file, out_file):
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    data = in_file.read()
    out_file.write(base64.b64encode(data))    
    in_file.close()
    out_file.close()

def decode(in_file, out_file):
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    data = in_file.read()
    out_file.write(base64.b64decode(data))    
    in_file.close()
    out_file.close()

回答by throws_exceptions_at_you

You can use a fix character as long as you remember the length of your initial payload, so you don't "throw" useful end bytes away. Try this:

只要您记住初始有效负载的长度,您就可以使用固定字符,这样您就不会“抛出”有用的结束字节。尝试这个:

import base64

from Crypto.Cipher import AES

def encrypt(payload, salt, key):
    return AES.new(key, AES.MODE_CBC, salt).encrypt(r_pad(payload))


def decrypt(payload, salt, key, length):
    return AES.new(key, AES.MODE_CBC, salt).decrypt(payload)[:length]


def r_pad(payload, block_size=16):
    length = block_size - (len(payload) % block_size)
    return payload + chr(length) * length


print(decrypt(encrypt("some cyphertext", "b" * 16, "b" * 16), "b" * 16, "b" * 16, len("some cyphertext")))