Python2/3 的 base64 对不可见字符编码结果不同

2018-12-12 18:42:21 +08:00
 whoami9894

两个完全相同的.py,分别是 2 和 3:


# py2
import base64

c = base64.b64decode('U/osUbnY8nSrWz4WPwKSwWPzKq9tOIQ9eCWnN5E+')
plain = '{"name":"guest","admin":false}'
res = ''
for i in range(len(c)):
    res += chr(ord(c[i]) ^ ord(plain[i]))
need = '{"name":"guest","admin":true}'
payload = ''
for i in range(len(need)):
    print ord(need[i]) ^ ord(res[i])
    payload += chr(ord(need[i]) ^ ord(res[i]))
print payload, len(payload)
payload = base64.b64encode(payload)
print payload



# py3
import base64

c = base64.b64decode('U/osUbnY8nSrWz4WPwKSwWPzKq9tOIQ9eCWnN5E+')
plain = '{"name":"guest","admin":false}'
res = ""
for i in range(len(c)):
    res += chr(c[i] ^ ord(plain[i]))
need = '{"name":"guest","admin":true}'
payload = ""
for i in range(len(need)):
    print(ord(need[i]) ^ ord(res[i]))
    payload += chr(ord(need[i]) ^ ord(res[i]))
print(payload, len(payload))
payload = base64.b64encode(payload.encode("utf-8"))
print(payload)

输出结果,逐字节打印 ascii 码,payload 变量是完全相同的,但编码结果不同

# py2
83
250
44
81
185
216
242
116
171
91
62
22
63
2
146
193
99
243
42
175
109
56
132
61
106
54
190
33
137
S?Q 关玔 c?痬
U/osUbnY8nSrWz4WPwKSwWPzKq9tOIQ9aja+IYk=


# py3
83
250
44
81
185
216
242
116
171
91
62
22
63
2
146
193
99
243
42
175
109
56
132
61
106
54
190
33
137
Sú,Q¹Øòt«[>■?☻’=j6¾!‰
b'U8O6LFHCucOYw7J0wqtbPhY/AsKSw4Fjw7Mqwq9tOMKEPWo2wr4hwok='

将两个 base64 字串放到 Python3 中解码:

>>> b64decode("U/osUbnY8nSrWz4WPwKSwWPzKq9tOIQ9aja+IYk=")
b'S\xfa,Q\xb9\xd8\xf2t\xab[>\x16?\x02\x92\xc1c\xf3*\xafm8\x84=j6\xbe!\x89'

>>> b64decode("U8O6LFHCucOYw7J0wqtbPhY/AsKSw4Fjw7Mqwq9tOMKEPWo2wr4hwok=")
b'S\xc3\xba,Q\xc2\xb9\xc3\x98\xc3\xb2t\xc2\xab[>\x16?\x02\xc2\x92\xc3\x81c\xc3\xb3*\xc2\xafm8\xc2\x84=j6\xc2\xbe!\xc2\x89'

对于这种情况应该如何避免

3190 次点击
所在节点    Python
7 条回复
chenstack
2018-12-12 18:54:14 +08:00
python3 的那部分倒数第二行
payload = base64.b64encode(payload.encode("utf-8"))
改成
payload = base64.b64encode(payload.encode("latin-1"))
结果就和 python2 的一致了
jingniao
2018-12-12 18:56:53 +08:00
默认编码?
whoami9894
2018-12-12 19:32:37 +08:00
@chenstack
确实!是因为 Python2 默认 Latin-1 的缘故吧

这是一道 PHP 生成流密码题目,我查了一下因为 PHP 默认也是 Latin-1,所以就解释通了,感谢
atuocn
2018-12-12 19:37:32 +08:00
utf8 编码只会在全部字符都在 ascii 码范围内,才和字节码能对上。
字符串也不能认为用 latin-1 编码就会变成 ascii 字节码。你试试

'中文'.decode('latin-1')

如果是新开发,要同时支持 py2, py3,建议统一用 utf8。如果是 py2 升级 py3,要保持旧接口的兼容性,改不了。那用楼主的主要语言环境的语言编码比如 gb2312,或者取系统的编码.
CharAct3
2018-12-12 20:04:06 +08:00
@whoami9894
不是的,Python2 中所谓的字符串其实就是 bytes。

在两段代码中 base64 编码 payload 的时候,传入的 bytes 是不一样的,所以结果不同。

举个例子,虽然在 Python2 和 Python3 中 '\xef' 看起来是一样的,但是在 Python2 中这就是一个 0xef 的 byte,而在 Python3 中则代表 U+00ef 这个 Unicode 字符,使用 utf-8 编码后就是 b'\xc3\xaf' 这个 bytes。
可以在 Python3 中试一试:
'\xef' == '\u00ef'

想把 U+0080 到 U+00ff 的 Unicode 字符编码为对应的 0x80 到 0xff 的 byte,就要使用 latin-1 编码,这个不受语言的影响。
whoami9894
2018-12-12 20:13:36 +08:00
@atuocn
@CharAct3

学到了,感谢!
hacker
2018-12-13 03:23:16 +08:00
建议不要再用 Python 2.x

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/516988

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX