Trouble with encode + encrypt + pad using same code for python2 and python3

余生颓废 提交于 2019-12-04 03:54:35

问题


Disclaimer: I understand that the following is not suited to give "security" in a production environment. It is simply meant as "a little bit better" than using XOR or rot13 on sensitive data that is stored on my system.

I put together the following code to allow me to use AES encryption for those sensitive values. AES requires 16 byte chunks; so I need padding. And I want to save that data in text files; so I added base64 encoding:

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))

For python2; that prints:

in: abc

scrambled: asEkqlUDiqlUpW1lw09UlQ==

reversed :

For python3; I run into

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

Two questions:

  • What is wrong with my "reverse" path with python2, why doesn't it print "abc"?
  • I understand that error message using python3; but I am wondering: what is the correct, canonical way to solve this problem in a way that works for both python2 and python3?

回答1:


One problem with your code is that you are using the same cipher object for both encryption and decryption. This won't work, as the cipher objects are stateful:PyCrypto Documentation

You can create another object for decrypting, as in: crypto2 = AES.new('This is a key123', AES.MODE_CBC, 'This is an IV456'), and then use this object to decrypt.




回答2:


One problem is that the Crypto module returns byte strings in Python3.

So when you use s[-1], you actually get an integer and no longer a byte string. The portable way is to use s[-1:] which correctly gives a character in Python2 and a byte string suitable for ord in Python3:

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



回答3:


Generally, code that handles binary data properly in both Python 2 and Python 3 can get a little messy. As you discovered, when you iterate over a bytes string in Python 3 you get integers, not characters.

Thus in Python 2, this code

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

outputs

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

whereas in Python 3 it outputs

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

The clean way to handle this is to simply write separate code for the two versions. But it is possible to write code that works on both versions.

Here's a modified version of the code you posted in the question. It also handles the statefulness of AES by creating a new AES cipher object each time you encrypt or decrypt.

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 output

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'

In Python 2, the reversed output looks like

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

because we're decoding the bytes to Unicode.


I turned the pad and unpad functions into proper def functions. That makes them a little easier to read. Also, it's generally considered bad style to use lambda for named functions: lambda is supposed to be used for anonymous functions.




回答4:


More of an addendum: as result of the answers I got; and digging into AES encryption a bit deeper I figured that the Cipher AES API actually allows for "unpadded" input. I rewrote my code to:

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))

And now I receive the expected results!

The trick is that I can't reuse the AES object; so a new one needs to be created; and in addition to that AES also offers the CTR mode - and that one does padding internally!



来源:https://stackoverflow.com/questions/43274703/trouble-with-encode-encrypt-pad-using-same-code-for-python2-and-python3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!