如何处理 Python 和 PostgreSQL 之间的二进制数据?

How to handle binary data between Python and PostgreSQL?

我目前正在构建一个密码管理器项目,以更好地了解 PostgreSQL,同时学习如何处理 Python 和数据库之间的数据。


下面是我的密码管理器的一些逻辑。


但是,我遇到了以下问题:存储在数据库中的二进制数据(key和salt)与Python中的二进制数据(key和salt)不一样。你会在下面找到我的代码:

import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
import sys
import os
import hashlib

# This connects to PSQL
connection = psycopg2.connect(user="postgres",
                              password="abc",
                              host="localhost",
                              port="5432",
                              dbname="passwordmanager")

connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
cur = connection.cursor()

salt = b""
key = b""
new_key = b""
stored_salt = b""


def welcome_n():  # This welcomes a new user without a master password set
    user_input = input("Welcome! You need to set a master password"
                       " before you can start storing passwords - continue?\n    y/n: ")
    valid = False
    while not valid:
        if user_input == "y":
            password_input = input("Please choose your password:\n")
            global salt
            global key

            salt = os.urandom(32)
            key = hashlib.pbkdf2_hmac(
                "sha256",
                password_input.encode("utf-8"),
                salt,
                100000
            )
            # PSQL commands to store salt and key
            psycopg2.Binary(key)
            psycopg2.Binary(salt)
            cur.execute("INSERT INTO master (key, salt) VALUES (%s, %s)",
                        (key, salt))
            valid = True
            print(key)  # b'\xfe\xfdO{\xd6?\xa1\x8d(\xbb\xb3r\x8a\xbc\xd6&t\x11[\x06\x110`\xb3\xfa\x91\xee\xc7x\x14\xddR'
            key = b""

        elif user_input == "n":
            print("Quiting program.")
            sys.exit()
        else:
            user_input = input("Invalid input. Please choose either 'y' or 'n'.\n    y/n: ")


def welcome_o():  # This welcomes an existing user with a master password set already
    password_to_check = input("Welcome! Enter your password to access the password safe:\n")

    # Encrypting the password given
    global salt
    global key
    global new_key
    global stored_salt

    cur.execute("SELECT key FROM master;")
    key = cur.fetchone()  # The key variable now has the fetched binary key from database
    cur.execute("SELECT salt FROM master;")
    stored_salt = cur.fetchone()  # The stored_salt variable now has the fetched binary salt from database

    new_key = hashlib.pbkdf2_hmac("sha256", password_to_check.encode("utf-8"), b"stored_salt", 10000)
    # A new key has been generated using the fetched salt

    if new_key == key:
        print("Password is correct")
    else:
        print("Incorrect Password")

    # Checking password similarities
    print(key)  # (<memory at 0x03B96388>,)
    print(new_key)  # b"k\r\xd8\xcfU\x05x\xfc'9\xaaC\x1fp~*9av6k^\xeeec\xef\xc5\xe3\xf1^\x883"


welcome_n()
welcome_o()

正如我前面提到的,存储在数据库中的二进制数据与 Python 中的二进制数据不同。我们可以在密钥中看到;


Python 二进制 = b'\xfe\xfdO{\xd6?\xa1\x8d(\xbb\xb3r\x8a\xbc\xd6&t\x11[\x06\x110`\xb3\xfa\x91\xee\xc7x\x14\xddR'

PSQL 二进制文件 = \xfefd4f7bd63fa18d28bbb3728abcd62674115b06113060b3fa91eec77814dd52


我遇到的另一个问题是,当我尝试在屏幕上打印密钥时,我得到 (<0x03B96388 处的内存>,) 如上所示,而新密钥显示为 b"k\r\xd8\xcfU\x05x\xfc'9\xaaC\x1fp~*9av6k^\xeeec\xef\xc5\xe3\xf1^\x883" .到目前为止,我的猜测是我没有将数据正确输入数据库并且没有正确提取数据,这意味着二进制数据发生了变形。我还想提一下,保存主密码二进制数据的 table 的 key columnsalt column 数据类型设置为字节茶。 您可以在下面找到 table 架构:

Column |  Type  | Collation | Nullable |               Default              | Storage | Stats target | Description
-------+--------+-----------+----------+------------------------------------+---------+--------------+-------------
id     | bigint |           | not null | nextval('master_id_seq'::regclass) |  plain  |              |
key    | bytea  |           | not null |                                    | extended|              |
salt   | bytea  |           | not null |                                    | extended|              |

非常感谢任何形式的反馈,我是编程新手,我正在寻求改进和学习新事物!

当您读取 bytea 时,您将得到一个 memoryview 对象。这个需要转换成bytes.

key = bytes(key)

有关更多信息,请参阅

但是您的代码还有其他问题。鉴于您正在为 Python 和 SQL 苦苦挣扎,我建议您做一个不涉及二进制数据的更简单的练习。


            psycopg2.Binary(key)
            psycopg2.Binary(salt)
            cur.execute("INSERT INTO master (key, salt) VALUES (%s, %s)",
                        (key, salt))

您在这里对 psycopg2.Binary 的调用没有任何作用。

psycopg2.Binary(key) 不会改变 key。它 return 是密钥的 psycopg2.extensions.Binary 表示。您正在进行转换,将它们丢弃,然后插入原始未转换的值。

相反,您需要插入 psycopg2.Binary 中的 return 值。

            cur.execute("INSERT INTO master (key, salt) VALUES (%s, %s)",
                        (psycopg2.Binary(key), psycopg2.Binary(salt)))

但事实证明你根本不需要它。您可以传入键和值,psycopg2 会计算出来。

            cur.execute("INSERT INTO master (key, salt) VALUES (%s, %s)",
                        (key, salt))

请参阅 psycopg2 文档中的 Binary Adaptation


fetchone() 不是 return 一列,而是 return 一个 作为一个元组。 key = cur.fetchone() returns 一个以键为第一项的元组。 key == new_key 永远行不通。

取而代之的是获取该行并将其分开。这是正确的,而且比对同一行进行两次查询更有效。

cur.execute("SELECT key, salt FROM master;")
(key, stored_salt) = cur.fetchone()

您对 hashlib.pbkdf2_hmac 的第二次调用不正确。它使用不同的迭代次数,并且使用文字字符串 "stored_salt" 作为盐,而不是 stored_salt 变量的内容。

这是正确的调用。

    new_key = hashlib.pbkdf2_hmac(
        "sha256",
        password_to_check.encode("utf-8"),
        stored_salt,
        100000
    )

为避免这种情况,请创建一个用于计算键的函数。

def make_key(password, salt):
    return hashlib.pbkdf2_hmac(
        "sha256",
        password.encode("utf-8"),
        salt,
        100000
    )

您的变量不需要是全局的。 可能不会破坏任何东西,但这是一种不好的做法。


因为您的 select 查询缺少 where 子句,所以无法保证您 select 的行与您之前 insert 编辑的行相同。你需要一些东西来连接它们,比如用户名。