【问题标题】:Trouble with encode + encrypt + pad using same code for python2 and python3对 python2 和 python3 使用相同的代码进行编码 + 加密 + 填充的问题
【发布时间】:2017-09-02 15:16:33
【问题描述】:

免责声明:我了解以下内容不适合在生产环境中提供“安全性”。它只是意味着比对存储在我系统上的敏感数据使用 XOR 或 rot13 “好一点”。

我将以下代码放在一起,以允许我对这些敏感值使用 AES 加密。 AES 需要 16 字节块;所以我需要填充。我想将该数据保存在文本文件中;所以我添加了base64编码:

from __future__ import print_function
from Crypto.Cipher import AES
import base64

crypto = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')

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


def scramble(data):
    return base64.b64encode(crypto.encrypt(pad(data)))


def unscramble(data):
    return unpad(crypto.decrypt(base64.b64decode(data)))

incoming = "abc"
print("in: {}".format(incoming))
scrambled = scramble(incoming)
print("scrambled: {}".format(scrambled))
andback= unscramble(scrambled)
print("reversed : {}".format(andback))

对于python2;打印:

输入:abc

加扰:asEkqlUDiqlUpW1lw09UlQ==

反转:

对于python3;我遇到了

unpad = lambda s: s[0:-ord(s[-1])]
TypeError: ord() expected string of length 1, but int found

两个问题:

  • python2 的“反向”路径有什么问题,为什么不打印“abc”?
  • 我理解使用 python3 的错误消息;但我想知道:以适用于 python2 和 python3 的方式解决这个问题的正确、规范的方法是什么?

【问题讨论】:

    标签: python encryption padding


    【解决方案1】:

    一个问题是 Crypto 模块在 Python3 中返回 byte 字符串。

    所以当你使用s[-1] 时,你实际上得到的是一个整数,而不是一个字节串。可移植的方法是使用s[-1:],它正确地给出了Python2中的一个字符和一个适合Python3中ord的字节字符串:

    unpad = lambda s: s[0:-ord(s[-1:])]
    

    【讨论】:

      【解决方案2】:

      通常,在 Python 2 和 Python 3 中正确处理二进制数据的代码可能会有点混乱。正如您所发现的,当您在 Python 3 中迭代 bytes 字符串时,您会得到整数,而不是字符。

      因此在 Python 2 中,这段代码

      print([i for i in b'ABCDE'])
      print([ord(c) for c in 'ABCDE'])
      

      输出

      ['A', 'B', 'C', 'D', 'E']
      [65, 66, 67, 68, 69]
      

      而在 Python 3 中它输出

      [65, 66, 67, 68, 69]
      [65, 66, 67, 68, 69]
      

      处理这个问题的简洁方法是简单地为两个版本编写单独的代码。但是是可以编写在两个版本上都可以使用的代码。

      这是您在问题中发布的代码的修改版本。它还通过在您每次加密或解密时创建一个新的 AES 密码对象来处理 AES 的状态性。

      from __future__ import print_function
      from Crypto.Cipher import AES
      import base64
      
      BS = 16
      
      def pad(s):
          padsize = BS - len(s) % BS
          return (s + padsize * chr(padsize)).encode('utf-8')
      
      def unpad(s):
          s = s.decode('utf-8')
          offset = ord(s[-1])
          return s[:-offset]
      
      def scramble(data, key, iv):
          crypto = AES.new(key, AES.MODE_CBC, iv)
          raw = crypto.encrypt(pad(data))
          return base64.b64encode(raw)
      
      def unscramble(data, key, iv):
          crypto = AES.new(key, AES.MODE_CBC, iv)
          raw = crypto.decrypt(base64.b64decode(data))
          return unpad(raw)
      
      key = b'This is a key123'
      iv = b'This is an IV456'
      
      incoming = "abc def ghi jkl mno"
      print("in: {0!r}".format(incoming))
      
      scrambled1 = scramble(incoming, key, iv)
      print("scrambled: {0!r}".format(scrambled1))
      
      incoming = "pqr stu vwx yz0 123"
      print("in: {0!r}".format(incoming))
      
      scrambled2 = scramble(incoming, key, iv)
      print("scrambled: {0!r}".format(scrambled2))
      
      andback = unscramble(scrambled2, key, iv)
      print("reversed : {0!r}".format(andback))
      
      andback = unscramble(scrambled1, key, iv)
      print("reversed : {0!r}".format(andback))
      

      Python 3 输出

      in: 'abc def ghi jkl mno'
      scrambled: b'C2jA5/WngDo55J7TG3uiArEO7hhyTPld/A3v52t+ANc='
      in: 'pqr stu vwx yz0 123'
      scrambled: b'FsFAKA2SbhCTimURy0W8+tM4iqLhNlK3OZrRuuYpMpY='
      reversed : 'pqr stu vwx yz0 123'
      reversed : 'abc def ghi jkl mno'
      

      在 Python 2 中,反向输出看起来像

      reversed : u'pqr stu vwx yz0 123'
      reversed : u'abc def ghi jkl mno'
      

      因为我们将字节解码为 Unicode。


      我将pad 和unpad 函数转换为正确的def 函数。这使它们更容易阅读。此外,将lambda 用于命名函数通常被认为是不好的风格:lambda 应该用于匿名函数。

      【讨论】:

        【解决方案3】:

        您的代码的一个问题是您使用相同的密码对象进行加密和解密。这不起作用,因为密码对象是有状态的:PyCrypto Documentation

        您可以创建另一个解密对象,如下所示: crypto2 = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456'),然后用这个对象解密。

        【讨论】:

          【解决方案4】:

          更多附录:作为我得到的答案的结果;并更深入地研究 AES 加密,我认为 Cipher AES API 实际上允许“未填充”输入。我将代码重写为:

          from __future__ import print_function
          from Crypto.Cipher import AES
          from Crypto.Util import Counter
          
          from base64 import b64decode, b64encode
          
          def scramble(data):
              crypto = AES.new('This is a key123', AES.MODE_CTR, 'This is an IV456', counter=Counter.new(128))
              return b64encode(crypto.encrypt(data))
          
          
          def unscramble(data):
              crypto = AES.new('This is a key123', AES.MODE_CTR, 'This is an IV456', counter=Counter.new(128))
              return crypto.decrypt(b64decode(data))
          
          
          incoming = "123456801DEF"
          print("in: {}".format(incoming))
          scrambled = scramble(incoming)
          print("scrambled: {}".format(scrambled))
          andback = unscramble(scrambled)
          print("reversed : {}".format(andback))
          

          现在我收到了预期的结果!

          诀窍是我不能重用 AES 对象;所以需要创建一个新的;除此之外,AES 还提供 CTR 模式 - 并且在内部进行填充!

          【讨论】:

            猜你喜欢
            • 2018-11-18
            • 1970-01-01
            • 2013-07-13
            • 2020-09-14
            • 2014-05-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多