【问题标题】:How to handle binary data between Python and PostgreSQL?如何处理 Python 和 PostgreSQL 之间的二进制数据?
【发布时间】:2020-10-26 00:03:17
【问题描述】:

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


这里有一点关于我的密码管理器的逻辑。

  • 如果您是新用户,程序将提示您等待设置主密码,以访问您的密码库。设置后,主密码将使用 hashlib Python 模块加密,如下面的代码 sn-p 所示。 keysalt 用于加密密码,两者都以二进制值的形式存储在数据库中。这些值使用 psycopg2 Python 模块发送到数据库。
  • 如果您是现有用户,程序将提示您等待已设置的主密码,以便您可以使用密码库进行访问。程序将从数据库中获取 keysalt,使用 salt 创建一个 new key 并查看 key 和 新键 值匹配。如果是,则授予访问权限,否则拒绝访问保管库。

但是,我遇到了以下问题:存储在数据库中的二进制数据(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 中的二进制数据不同。我们可以在 key 中看到;


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


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

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

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

【问题讨论】:

  • 你能告诉我们相关表的架构吗?
  • 您正在运行 pbkdf2_hmac 不同的迭代。它们应该共享一个常量。
  • 好的,很好,你正在使用bytea。如果您可以edit 回答您的问题。避免截图,粘贴文本。
  • 我已经更新了答案。通过这些修复,代码现在可以工作了。在掌握 Python 和 SQL 的基础知识后,考虑处理二进制数据。

标签: python database postgresql binary-data


【解决方案1】:

当您读取bytea 时,您将返回一个memoryview 对象。这需要转换为bytes

key = bytes(key)

请参阅How to read and insert bytea columns using psycopg2? 了解更多信息。

但是您的代码还有其他问题。鉴于您正在努力使用 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。它返回密钥的psycopg2.extensions.Binary 表示。您正在进行转换,将它们丢弃,然后插入未转换的原始值。

相反,您需要插入来自psycopg2.Binary 的返回值。

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

但事实证明你根本不需要它。你可以传入 key 和 value,psycopg2 会弄清楚。

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

请参阅 psycopg2 文档中的 Binary Adaptation


fetchone() 不返回一列,它返回一个 row 作为元组。 key = cur.fetchone() 返回一个以键为第一项的元组。 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 编辑的行相同。你需要一些东西来连接它们,比如用户名。

【讨论】:

  • 我曾考虑在没有二进制的情况下对这个密码库进行编程以检测其他可行的选项,但我肯定会更深入地研究二进制的主题。非常感谢一切!
猜你喜欢
  • 2022-10-26
  • 1970-01-01
  • 2011-03-04
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 2017-05-10
  • 2011-10-06
  • 1970-01-01
相关资源
最近更新 更多