如何处理 Python 和 PostgreSQL 之间的二进制数据?
How to handle binary data between Python and PostgreSQL?
我目前正在构建一个密码管理器项目,以更好地了解 PostgreSQL,同时学习如何处理 Python 和数据库之间的数据。
下面是我的密码管理器的一些逻辑。
- 如果您是新用户,程序会提示您等待设置主密码,以访问您的密码库。设置后,主密码将使用 hashlib Python 模块加密,如下面的代码片段所示。 key 和 salt 用于加密密码,两者都以二进制值存储在数据库中。使用 psycopg2 Python 模块将这些值发送到数据库。
- 如果您是现有用户,该程序会提示您等待已设置的主密码,以便您可以使用密码库进行访问。该程序将从数据库中获取 key 和 salt,使用 salt 创建一个 new key并查看 key 和 new 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 中的二进制数据不同。我们可以在密钥中看到;
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 column 和 salt 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
编辑的行相同。你需要一些东西来连接它们,比如用户名。
我目前正在构建一个密码管理器项目,以更好地了解 PostgreSQL,同时学习如何处理 Python 和数据库之间的数据。
下面是我的密码管理器的一些逻辑。
- 如果您是新用户,程序会提示您等待设置主密码,以访问您的密码库。设置后,主密码将使用 hashlib Python 模块加密,如下面的代码片段所示。 key 和 salt 用于加密密码,两者都以二进制值存储在数据库中。使用 psycopg2 Python 模块将这些值发送到数据库。
- 如果您是现有用户,该程序会提示您等待已设置的主密码,以便您可以使用密码库进行访问。该程序将从数据库中获取 key 和 salt,使用 salt 创建一个 new key并查看 key 和 new 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 中的二进制数据不同。我们可以在密钥中看到;
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 column 和 salt 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
编辑的行相同。你需要一些东西来连接它们,比如用户名。