python2 和 python3 使用相同代码进行编码 + 加密 + 填充时出现问题
Trouble with encode + encrypt + pad using same code for python2 and python3
免责声明:我了解以下内容不适合在生产环境中提供“安全”。它只是意味着比对存储在我系统上的敏感数据使用 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;打印:
in: abc
scrambled: asEkqlUDiqlUpW1lw09UlQ==
reversed :
对于python3;我运行变成了
unpad = lambda s: s[0:-ord(s[-1])]
TypeError: ord() expected string of length 1, but int found
两个问题:
- 我使用 python2 的“反向”路径有什么问题,为什么不打印“abc”?
- 我使用 python3 理解该错误消息;但我想知道:以适用于 python2 和 python3 的方式解决此问题的正确、规范的方法是什么?
您的代码存在一个问题,即您使用同一个密码对象进行加密和解密。这是行不通的,因为密码对象是有状态的:PyCrypto Documentation
您可以创建另一个用于解密的对象,如:
crypto2 = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')
,然后用这个对象解密。
一个问题是 Crypto 模块 returns byte strings in Python3.
所以当你使用s[-1]
时,你实际上得到的是一个整数,不再是一个字节串。可移植的方法是使用 s[-1:]
正确给出 Python2 中的字符和适合 Python3 中的 ord
的字节字符串:
unpad = lambda s: s[0:-ord(s[-1:])]
更多的附录:作为我得到的答案的结果;并深入研究 AES 加密,我发现 Cipher AES API 实际上允许 "unpadded" 输入。我将代码重写为:
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 模式 - 并且在内部进行填充!
通常,在 Python 2 和 Python 3 中正确处理二进制数据的代码可能会有点混乱。正如您所发现的,当您遍历 Python 3 中的 bytes
字符串时,您得到的是整数,而不是字符。
因此在Python2中,这段代码
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'
在Python2中,反向输出看起来像
reversed : u'pqr stu vwx yz0 123'
reversed : u'abc def ghi jkl mno'
因为我们正在将字节解码为 Unicode。
我将 pad
和 unpad
函数转换为正确的 def
函数。这使它们更容易阅读。此外,通常认为对命名函数使用 lambda
是不好的风格:lambda
应该用于匿名函数。
免责声明:我了解以下内容不适合在生产环境中提供“安全”。它只是意味着比对存储在我系统上的敏感数据使用 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;打印:
in: abc
scrambled: asEkqlUDiqlUpW1lw09UlQ==
reversed :
对于python3;我运行变成了
unpad = lambda s: s[0:-ord(s[-1])]
TypeError: ord() expected string of length 1, but int found
两个问题:
- 我使用 python2 的“反向”路径有什么问题,为什么不打印“abc”?
- 我使用 python3 理解该错误消息;但我想知道:以适用于 python2 和 python3 的方式解决此问题的正确、规范的方法是什么?
您的代码存在一个问题,即您使用同一个密码对象进行加密和解密。这是行不通的,因为密码对象是有状态的:PyCrypto Documentation
您可以创建另一个用于解密的对象,如:
crypto2 = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456')
,然后用这个对象解密。
一个问题是 Crypto 模块 returns byte strings in Python3.
所以当你使用s[-1]
时,你实际上得到的是一个整数,不再是一个字节串。可移植的方法是使用 s[-1:]
正确给出 Python2 中的字符和适合 Python3 中的 ord
的字节字符串:
unpad = lambda s: s[0:-ord(s[-1:])]
更多的附录:作为我得到的答案的结果;并深入研究 AES 加密,我发现 Cipher AES API 实际上允许 "unpadded" 输入。我将代码重写为:
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 模式 - 并且在内部进行填充!
通常,在 Python 2 和 Python 3 中正确处理二进制数据的代码可能会有点混乱。正如您所发现的,当您遍历 Python 3 中的 bytes
字符串时,您得到的是整数,而不是字符。
因此在Python2中,这段代码
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'
在Python2中,反向输出看起来像
reversed : u'pqr stu vwx yz0 123'
reversed : u'abc def ghi jkl mno'
因为我们正在将字节解码为 Unicode。
我将 pad
和 unpad
函数转换为正确的 def
函数。这使它们更容易阅读。此外,通常认为对命名函数使用 lambda
是不好的风格:lambda
应该用于匿名函数。