如何让 py-scrypt 的 "simple password verifier" 示例函数起作用?

How to get py-scrypt's "simple password verifier" example functions to work?

我正在使用 py-scrypt 提供的 example script 构建一个 简单密码验证器 。下面是我的测试脚本。

测试脚本:

#!/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

或没有错误消息但 return 错误

password_ok? : False

当我注释 return scrypt.encrypt(os.urandom(datalength), password, maxtime=maxtime) 以删除随机秘密消息生成器并取消注释 return scrypt.encrypt(a_secret_message, password, maxtime=maxtime) 以使用非随机秘密消息时,函数 verify2_password 起作用。

问题:如何让随机秘密消息元素起作用?是什么导致它失败?

UnicodeDecodeError 异常的解释

原因 1

我想我理解为什么 Scrypt 发布 UnicodeDecodeError。引用 Python's UnicodeDecodeError :

The UnicodeDecodeError normally happens when decoding an str string from a certain coding. Since codings map only a limited number of str strings to unicode characters, an illegal sequence of str characters will cause the coding-specific decode() to fail.

同样在 Python's Unicode HOWTO 部分 Python 的 Unicode 支持 --> 字符串类型 ,它写道

In addition, one can create a string using the decode() method of bytes. This method takes an encoding argument, such as UTF-8, and optionally an errors argument

The errors argument specifies the response when the input string can’t be converted according to the encoding’s rules. Legal values for this argument are 'strict' (raise a UnicodeDecodeError exception), 'replace' (use U+FFFD, REPLACEMENT CHARACTER), 'ignore' (just leave the character out of the Unicode result), or 'backslashreplace' (inserts a \xNN escape sequence).

简而言之,只要 Python 的 .decode() 方法无法将 str 字符串映射到 unicode 字符,并且当它使用 strict 参数时,.decode() 方法将 return 一个 UnicodeDecodeError 异常。

我试图在py-scrypt/scrypt/scrypt.py.decrypt()方法中找到.decode()方法。最初,我找不到它。对于 Python3,.decrypt() 方法 return 语句是: return str(out_bytes, encoding)

然而,进一步检查 Python 对 str class 的解释,我发现解释说:

if object is a bytes (or bytearray) object, then str(bytes, encoding, errors) is equivalent to bytes.decode(encoding, errors).

这意味着如果不在 str(bytes, encoding) 中定义 error 参数,此 str class 默认为 returning bytes.decode(encoding, errors='strict') 和 return每当无法将 str 字符串映射到 unicode 字符时,都会编辑 UnicodeDecodeError 异常。

原因 2

在 "simple password verifier" 示例中,Scrypt.encrypt() 的 input 参数被定义为 os.urandom(datalength),其中 return 编辑了一个 <class 'bytes'> .当此 <class 'bytes'> 被加密并随后由 Scrypt.decrypt() 解密时,returned 解密值也必须是 <class 'bytes'> 。根据 .decrypt() method 的 doc_string,对于 Python3,此方法将 return 一个 str 实例,如果使用编码进行编码。如果 encoding=None,它将 return 一个字节实例。由于 Script.decrypt() 在函数 verify2_password() 中默认为 encoding='utf-8'Script.decrypt() 尝试 return 一个 <class str> 导致了 UnicodeDecodeError

py-scrypt 中给出的 "simple password verifier" 示例脚本的解决方案:

  1. verify_password() 函数应包含参数 encoding=None .
  2. 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)