代码之家  ›  专栏  ›  技术社区  ›  xster

使用aes+ctr的加密问题

  •  10
  • xster  · 技术社区  · 16 年前

    我正在编写一段代码来使用对称加密对文本进行加密。但结果不对…

    from Crypto.Cipher import AES
    import os
    
    crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter = lambda : os.urandom(16))
    encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa")
    print crypto.decrypt(encrypted)
    

    这里,解密的文本与原始文本不同。

    我对密码学的了解不多,请耐心等待。我知道ctr模式每次都需要一个“counter”函数来提供一个随机计数器,但是当我的密钥是32字节,并且它坚持我的消息也是16字节的倍数时,为什么需要它是16字节呢?这正常吗?

    我猜它不会返回到原始消息,因为计数器在加密和解密之间发生了变化。但是,不管怎样,理论上它是如何工作的呢?我做错什么了?无论如何,我不得不求助于欧洲央行,直到我弄明白:(

    5 回复  |  直到 7 年前
        1
  •  12
  •   Gilles 'SO- stop being evil'    9 年前

    这个 counter 必须在解密时返回与加密时相同的值,正如您所直觉的那样,因此,一个( 根本不安全 )方法是:

    >>> secret = os.urandom(16)
    >>> crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret)
    >>> encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa")
    >>> print crypto.decrypt(encrypted)
    aaaaaaaaaaaaaaaa
    

    CTR是 密码,所以让你吃惊的“一次16次”约束是很自然的。

    当然,一个所谓的“计数器”返回 相同的 每次调用的值 is grossly insecure . 做得更好不需要太多,例如……

    import array
    
    class Secret(object):
      def __init__(self, secret=None):
        if secret is None: secret = os.urandom(16)
        self.secret = secret
        self.reset()
      def counter(self):
        for i, c in enumerate(self.current):
          self.current[i] = c + 1
          if self.current: break
        return self.current.tostring()
      def reset(self):
        self.current = array.array('B', self.secret)
    
    secret = Secret()
    crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=secret.counter)
    encrypted = crypto.encrypt(16*'a' + 16*'b' + 16*'c')
    secret.reset()
    print crypto.decrypt(encrypted)
    
        2
  •  4
  •   Gilles 'SO- stop being evil'    7 年前

    AES是一种 block cipher :这是一种算法(更准确地说,是一对算法),它获取一个密钥和一个消息块,并对该块进行加密或解密。无论密钥大小如何,块的大小始终为16个字节。

    CTR是 mode of operation . 它是一对建立在块密码基础上的算法,生成流密码,可以加密和解密任意长度的消息。

    ctr的工作原理是将连续的消息块与计数器连续值的加密相结合。计数器的大小必须是一个块,以便它是块密码的有效输入。

    • 从功能上讲,计数器的连续值是什么并不重要,只要加密和解密端使用相同的序列。通常,计数器被视为一个256位的数字,并为每个连续的块递增,随机选择一个初始值。因此,通常情况下,递增方法被烘焙到代码中,但解密端需要知道初始值是什么,因此加密端在加密消息的开头发送或存储初始计数器值。
    • 为了安全,必须 不要用给定的键重复相同的计数器值 . 所以对于一次性使用的钥匙,可以从 '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' . 但是,如果多次使用该键,则第二条消息不允许重用第一条消息使用的任何计数器值,最简单的方法是随机生成初始计数器值(使用2^128空间,碰撞的可能性可以忽略不计)。

    通过让调用者选择一个计数器函数,Pypcrypto库为您提供了足够的绳索来吊死自己。你应该使用 Crypto.Util.Counter 不仅是为了更好的性能,正如文档中所说的那样,还因为构建安全的东西比自己可能想到的更容易。即使如此,也要注意使用一个随机的初始值,这不是默认值。

    import binascii
    import os
    from Crypto.Cipher import AES
    from Crypto.Util import Counter
    def int_of_string(s):
        return int(binascii.hexlify(s), 16)
    def encrypt_message(key, plaintext):
        iv = os.urandom(16)
        ctr = Counter.new(128, initial_value=int_of_string(iv))
        aes = AES.new(key, AES.MODE_CTR, counter=ctr)
        return iv + aes.encrypt(plaintext)
    def decrypt_message(key, ciphertext):
        iv = ciphertext[:16]
        ctr = Counter.new(128, initial_value=int_of_string(iv))
        aes = AES.new(key, AES.MODE_CTR, counter=ctr)
        return aes.decrypt(ciphertext[16:])
    
        3
  •  2
  •   Gilles 'SO- stop being evil'    9 年前

    当我的密钥是32字节时,为什么需要16字节

    它必须与密码块大小相同。ctr模式只加密计数器,并用加密的计数器块XORS明文。

    笔记:

    1. 计数器值必须是唯一的——如果您使用相同的计数器值在同一个密钥下加密两个不同的明文,那么您只需放弃密钥。
    2. 就像静脉注射一样,计数器也不是秘密的——只要把它和密文一起发送就行了。如果你试图保守秘密而使代码变得更复杂,你很可能会自暴自弃。
    3. 计数器值不必是不可预测的——从零开始,为每个块添加一个值是完全正确的。但请注意,如果加密多条消息,则需要跟踪已使用的计数器值,即,需要跟踪已使用该密钥加密的块数(并且不能在程序的不同实例或不同计算机上使用相同的密钥)。
    4. 纯文本可以是任意长度——ctr模式将块密码转换为流密码。

    标准免责声明: 加密很难。如果你不明白自己在做什么, 弄错了。

    我只想跨会话存储一些密码。

    使用ScRyPT。 加密包括 encrypt decrypt 它将aes-ctr与密码派生的密钥一起使用。

    $ pip install scrypt
    
    $ python
    >>> import scrypt
    >>> import getpass
    >>> pw = getpass.getpass("enter password:")
    enter password:
    >>> encrypted = scrypt.encrypt("Guido is a space alien.",pw)
    >>> out = scrypt.decrypt(encrypted,pw)
    >>> out
    'Guido is a space alien.'
    
        4
  •  1
  •   Slartibartfast    16 年前

    初始化向量(“计数器”)需要在加密和解密之间保持不变,就像密钥一样。它的使用使您可以对同一文本进行一百万次编码,并每次获得不同的密文(防止某些已知的明文攻击和模式匹配/攻击)。解密时仍需使用与加密时相同的IV。通常,当您开始解密流时,您将IV初始化为与开始加密该流时相同的值。

    http://en.wikipedia.org/wiki/Initialization_vector 有关初始化向量的信息。

    请注意,os.urandom(16)不是“确定性的”,这是对计数器函数的要求。我建议您使用递增函数,因为这就是ctr模式的设计方法。初始计数器值应该是随机的,但连续的值应该完全可以从初始值(确定性)预测出来。初始值甚至可以帮你处理(我不知道细节)

    关于密钥、IV和输入大小,听起来您选择的密码的块大小为16字节。你所描述的一切都符合这一点,对我来说似乎很正常。

        5
  •  1
  •   Halberdier    12 年前

    我可能会迟到,我可能忽略了之前的答案,但我没有找到一个明确的声明,说明如何(至少是imho)根据密码包来完成这项工作。

    crypto.util.counter包提供了可调用的状态计数器,这非常有用,但至少对于我来说,不正确地使用它们是很容易的。

    您必须创建一个计数器,例如 ctr = Counter.new('parameters here') . 每当计数器模式的密码对象调用计数器来加密消息时,它就会递增。这对于良好的加密实践是必需的,否则,有关相等块的信息可能会从密文中泄漏。

    现在您不能对同一个密码对象调用解密函数,因为它将再次调用同一个计数器,同时该计数器已递增,可能几次。您需要做的是用用相同参数初始化的不同计数器创建一个新的密码对象。这样,解密就可以正常工作,从完成加密的同一点开始计数器。

    工作示例如下:

    # Import modules
    from Crypto.Cipher import AES
    from Crypto import Random
    from Crypto.Util import Counter
    
    
    # Pad for short keys
    pad = '# constant pad for short keys ##'
    
    # Generate a random initialization vector, to be used by both encryptor and decryptor
    # This may be sent in clear in a real communication
    
    random_generator = Random.new()
    IV = random_generator.read(8)
    
    
    # Encryption steps
    
    # Ask user for input and pad or truncate to a 32 bytes (256 bits) key
    prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: '
    user_keye = raw_input(prompt)
    keye = (user_keye + pad)[:32]
    
    # Create counter for encryptor
    ctr_e = Counter.new(64, prefix=IV)
    
    # Create encryptor, ask for plaintext to encrypt, then encrypt and print ciphertext
    encryptor = AES.new(keye, AES.MODE_CTR, counter=ctr_e)
    plaintext = raw_input('Enter message to cipher: ')
    ciphertext = encryptor.encrypt(plaintext)
    print ciphertext
    print
    
    
    # Decryption steps
    
    # Ask user for key: it must be equal to that used for encryption
    prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: '
    user_keyd = raw_input(prompt)
    keyd = (user_keyd + pad)[:32]
    
    # Create counter for decryptor: it is equal to the encryptor, but restarts from the beginning
    
    ctr_d = Counter.new(64, prefix=IV)
    
    # Create decryptor, then decrypt and print decoded text
    decryptor = AES.new(keyd, AES.MODE_CTR, counter=ctr_d)
    decoded_text = decryptor.decrypt(ciphertext)
    print decoded_text