我正在使用py-scrypt
提供的示例脚本来构建一个简单的密码验证器。下面是我的测试脚本。
测试脚本:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import scrypt
import os
def hash2_password(a_secret_message, password, maxtime=0.5, datalength=64):
#return scrypt.encrypt(a_secret_message, password, maxtime=maxtime)
return scrypt.encrypt(os.urandom(datalength), password, maxtime=maxtime)
def verify2_password(data, password, maxtime=0.5):
try:
secret_message = scrypt.decrypt(data, password, maxtime)
print('nDecrypted secret message:', secret_message)
return True
except scrypt.error:
return False
password2 = 'Baymax'
secret_message2 = "Go Go"
data2 = hash2_password(secret_message2, password2, maxtime=0.1, datalength=64)
print('nEncrypted secret message2:')
print(data2)
password_ok = verify2_password(data2, password2, maxtime=0.1)
print('npassword_ok? :', password_ok)
问题:我经常收到错误消息,例如:
Traceback (most recent call last):
File "~/example_scrypt_v1.py", line 56, in <module>
password_ok = verify2_password(data2, password2, maxtime=0.1)
File "~/example_scrypt_v1.py", line 43, in verify2_password
secret_message = scrypt.decrypt(data, password, maxtime)
File "~/.local/lib/python3.5/site-packages/scrypt/scrypt.py", line 188, in decrypt
return str(out_bytes, encoding)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xca in position 0: invalid continuation byte
其中最后一行变化为例如:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xaf in position 3: invalid start byte
或
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xee in position 1: invalid continuation byte
或无错误消息,但返回 False
password_ok? : False
当我评论return scrypt.encrypt(os.urandom(datalength), password, maxtime=maxtime)
删除随机秘密消息生成器并取消注释return scrypt.encrypt(a_secret_message, password, maxtime=maxtime)
使用非随机秘密消息时,该功能verify2_password
工作。
问:如何让随机秘密消息元素工作? 是什么导致了它的失败?
UnicodeDecodeError Exception 的解释
原因1
我想我明白为什么 Scrypt 要发布UnicodeDecodeError
.引用 Python 的 UnicodeDecodeError :
UnicodeDecodeError 通常在解码 str 字符串时发生 从某种编码。由于编码映射的str数量有限 字符串到 Unicode 字符,STR 字符的非法序列 将导致特定于编码的解码 () 失败。
同样在 Python 的 UnicodeHOWTO 部分Python 的 Unicode 支持 --> 字符串类型,它写道
此外,可以使用 decode() 方法创建一个字符串 字节。此方法采用编码参数,例如 UTF-8,并且 可选错误参数
errors 参数指定输入字符串无法时的响应 根据编码的规则进行转换。此法律价值 参数是"严格的"(引发UnicodeDecodeError异常), "替换"(使用 U+FFFD,替换字符),"忽略"(离开 Unicode 结果中的字符),或"反斜杠替换" (插入 \xNN 转义序列)。
简而言之,每当 Python 的.decode()
方法无法将str
字符串映射到 unicode 字符时,并且当它使用strict
参数时,.decode()
方法将返回UnicodeDecodeError
异常。
我试图在py-scrypt/scrypt/scrypt.py
的 .decrypt() 方法中找到.decode()
方法。最初,我找不到它。对于 Python3,.decrypt()
方法 return 语句是: 返回 str(out_bytes, 编码)
然而,进一步检查 Python 对 str 类的解释,我发现解释是这样的:
如果对象是字节(或字节数组)对象,则 str(字节, 编码, errors)等效于bytes.decode(encoding,errors)。
这意味着没有在str(bytes, encoding)
中定义error
参数,这个str类默认返回bytes.decode(encoding, errors='strict')
,并在str
字符串映射到Unicode字符时返回UnicodeDecodeError
异常。
原因2
在"简单密码验证器"示例中,Scrypt.encrypt() 的input
参数被定义为返回<class 'bytes'>
的os.urandom(datalength)
。 当这个<class 'bytes'>
被加密,随后被Scrypt.decrypt()
解密时,返回的解密值也必须是一个<class 'bytes'>
。根据.decrypt()
方法的doc_string,对于 Python3,如果使用编码编码,此方法将返回一个 str 实例。如果encoding=None
,它将返回一个字节实例。由于Script.decrypt()
默认在函数verify2_password()
中encoding='utf-8'
,Script.decrypt()
尝试返回<class str>
会导致UnicodeDecodeError
。
py-scrypt中给出的"简单密码验证器"示例脚本的解决方案:
verify_password()
函数应包含参数encoding=None
。scrypt.decrypt()
应包含参数encoding=encoding
。
修订的示例脚本:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import scrypt
import os
def encrypt_password(password, maxtime=0.5, datalength=64):
passphrase = os.urandom(datalength)
print('npassphrase = ', passphrase, type(passphrase))
return scrypt.encrypt(passphrase, password, maxtime=maxtime)
def verify_password(encrpyted_passphrase, password, maxtime=0.5, encoding=None):
try:
passphrase = scrypt.decrypt(encrpyted_passphrase, password, maxtime,
encoding=encoding)
print('npassphrase = ', passphrase, type(passphrase))
return True
except scrypt.error:
return False
password = 'Baymax'
encrypted_passphrase = encrypt_password(password, maxtime=0.5, datalength=64)
print('nEncrypted PassPhrase:')
print(encrypted_passphrase)
password_ok = verify_password(encrypted_passphrase, password, maxtime=0.5)
print('npassword_ok? :', password_ok)