【问题标题】:How can I effectively store binary data in a file that's in a text format like CSV?如何有效地将二进制数据存储在 CSV 等文本格式的文件中?
【发布时间】:2021-10-25 02:55:25
【问题描述】:

我目前正在使用 Python 编写密码存储程序,尽管 C 可能会更快。过去一个小时左右,我一直在尝试找到一种将字节对象存储在 CSV 文件中的方法。我正在用自己的盐对密码进行哈希处理,然后将其存储,然后再次抓取以检查密码。当它存储在内存中时,它工作得很好。

salt = os.urandom(64)
hash = hashlib.pbkdf2_hmac(
    'sha256',
    password.encode('utf-8'),
    salt,
    1000000
)
storage = salt + hash
salt_from_store = storage[:64]
hash_from_store = storage[64:]

但是,当我尝试将其存储在 CSV 文件中时,它不必一直运行,我得到一个错误,

TypeError: write() 参数必须是 str,而不是字节

所以,我使用,将它转换为字符串,

str(storage)

写得很好。但是,当我从文件中获取它时,它仍然是一个字符串,长度从 128(字节)到 300+(字符)。它也永远不一致。我不知道编码,所以我不能那样改变它,当我打印字节时,它是一堆带有反斜杠和 X 的字符

b'\xfd\x3a'

偶尔会出现一些随机的特殊字符。我不确定是否有办法将其转换为 int,然后将其转换回来。另一个问题是我找到了一种方法,通过改变

b"\xf1\x96"

"b\xf1\x96"

它打印编码的文本,而不是它组成的字节。但是,我不知道这是否是改变它的好方法,如果是,是否有办法在没有类似的情况下做到这一点

bytes[0] = '"'
bytes[1] = 'b'

【问题讨论】:

  • 要写入字节,要么写入期望包含字节的内容,要么写入以某种方式表示字节的文本。 CSV 基本上是一种基于文本的格式。
  • 好吗?你会详细说明吗?也许举个例子?还是你会说,“你做错了,但我不在乎帮你解决它”?
  • 我写了一个答案。这些事情需要时间。也就是说,你 expected to do some research yourself。例如,将 python convert bytes to str into a search engine 之类的东西放入会很有帮助。
  • Python 对于任何受 I/O 限制的事物来说都足够快。

标签: python python-3.x encryption hash salt


【解决方案1】:

如果您想将字节保存为字符串,您可能应该将它们编码为为此制定的格式,例如base64。这是more efficient with space than directly writing hex.

尝试将任意字节直接转换为像 utf-8 这样的编码可能会导致 UnicodeDecodeError 错误。

在您的情况下,您可以执行以下操作:

import os, hashlib, base64

password = "top_secret"

salt = os.urandom(64)
hash = hashlib.pbkdf2_hmac(
    'sha256',
    password.encode('utf-8'),
    salt,
    1000000
)
storage = salt + hash

# convert to a base64 string:
s = base64.b64encode(storage).decode('utf-8')

print(s) # <-- string you can save this to a file

# after reading it back from a file convert back to bytes
the_bytes = base64.b64decode(s)

the_bytes == storage 
# True

【讨论】:

  • 谢谢!有什么好的理由使用它,而不是内置的十六进制函数?我认为它可以生成更短的字符串,因为它的基数更高。但是,还有别的吗?尽管如此,我非常感谢您的有用回答!
  • @Xarvveron 据我所知,主要问题是它更节省空间,这就是为什么它在交换格式中很常见。
  • 十六进制,根据其结构,表示每个字符 4 位(因为它只使用 16 个不同的文本字符)。它将使用两个字符(在任何正常编码中将被编码为两个字节)来表示原始数据的每个字节。 (字符串由“Unicode 代码点”组成,与“字符”不同;但在这种情况下,区别无关紧要。)Base64,顾名思义,使用 64 个不同的文本字符,因此编码 6 位每个字符(同时仍然使用在每个正常编码中被编码为单个字节的纯 ASCII 字符)。
  • 好的,谢谢。我可能会使用它,因为它很高效,而且在标准库中。
  • 感谢@KarlKnechtel……说得好。
【解决方案2】:

要写入字节,要么写入期望包含字节的内容,要么写入以某种方式表示字节的文本。 CSV 基本上是一种基于文本的格式。如果您要使用 CSV 文件,那么您将以文本模式打开它,然后向其中写入文本。

从根本上说,硬盘驱动器上的每个文件都由字节组成。这意味着,当您打开 CSV 文件时,您将选择(或使用默认)文本编码方案。因此,您的 bytes 对象在写入时必须转换两次(转换为文本,然后转换为文件中的底层字节 - 例如您可以使用十六进制编辑器进行验证),读取时再转换两次。这就是处理混合数据的现实。值得庆幸的是,一半的工作是自动为您处理的(通过open 调用,或像csv.Reader 这样的包装器)。

所以,我使用str(storage)将其转换为字符串

这实际上并不是您最感兴趣的某种意义上的转换。这是要求对象的可打印、人类可读的表示(还有repr,这需要更面向技术的表示。对于strbytes 对象,这就是括起来的引号的来源,以及其他调整。当你print 某事时,它的str 被使用。当你评估在 REPL 中,您会看到结果的 repr - 除了当结果为 None 时,它根本不显示任何内容)。专门处理bytesstr对象,Python有encodingdecoding的概念,它使用显式.encode(str->@987654337 @) 和 .decode (bytes->str) 方法。这些是您可以在文档中轻松查找的主题(或以前的 Stack Overflow 问题,或一般在 Internet 上)。

当我打印字节时,它是一堆带有反斜杠和 X 的字符

是的,这是 Python 用来告诉您 bytes 对象中存在哪些数据的形式。你这里说的基本和“打印列表的时候是一堆列表元素,用方括号括起来的逗号”,或者“打印整数的时候是一堆数字符号”是一样的。

但是,当我从文件中获取它时,它仍然是一个字符串,长度从 128(字节)到 300+(字符)。

所以再次解码。当然,您确实需要正确编码。您从文件中获得的所有内容都将是一个字符串,因为您是以文本模式打开文件,因为 CSV 是一种文本格式。 (顺便说一句,您正在为此使用csv 标准库模块,对吧?)

它也永远不会一致。我不知道编码

所以告诉它使用哪种编码;如果您需要使用一致数量的文本,请选择一种始终将一个字节映射到一个 Unicode 代码点的编码(例如latin-1,也称为iso-8859-1)。但我怀疑您实际上并不关心文本的长度(如果有的话,您会关心文件中使用的字节数)。

我找到了一种方法,通过改变

您只能对文字数据执行此操作。不要用这些术语来思考。 b语言语法的一部分。它是不是数据。

【讨论】:

  • “我不知道编码”的意思是它是由 hashlib.pbkdf2_hmac() 自动生成的,它没有告诉我它是什么。我现在意识到“转换”不是正确的词,但我也尝试了 str.decode() 和 str(byte, 'utf-8'),但都没有帮助。有哪些类似于 csv 和 JSON 的文件格式适用于字节数据?
  • 您还可以选择以其他方式“编码”,例如将每个字节转换为十六进制字符串表示形式,或者转换为base64,或者——如果你真的想浪费空间——转换为@ 987654346@ 和 1 符号(二进制字符串表示)。 .encode.decode 方法以及您可以与它们一起使用的编码名称是基于字节以某种方式表示文本的假设。
  • 谢谢你的解释!!
  • 对不起,我一开始有点错误。 pbkdf2_hmac 专门给你一个原始的bytes 对象。你“不知道编码”,因为它没有被编码并且不代表文本;这只是原始数据。要创建字符串,无论如何都需要使用能够处理任何可能输入字节序列的编码对其进行解码。其中最简单的是latin-1,它将每个单独的字节按顺序映射到前 256 个 Unicode 代码点。或者,是的,您可以使用非文本解释方案。这对于可读性来说可能是一个更好的主意,因此您无需担心 CSV 的转义方案。
  • 啊,好吧。一开始我很困惑。感谢您回来解释为什么我不知道编码。
【解决方案3】:

你可以使用十六进制。让我们获取一些数据:

>>> import os
>>> b = os.urandom(10)
>>> b
b'\xc5\xe2{\xdf\xd2\x13\xa7\x0b\xef\x07'

作为可以写入 CSV 的十六进制字符串:

>>> b.hex()
'c5e27bdfd213a70bef07'

回到字节:

>>> bytes.fromhex(b.hex())
b'\xc5\xe2{\xdf\xd2\x13\xa7\x0b\xef\x07'

【讨论】:

  • 好的,谢谢!!这非常有帮助,现在它可以工作了!如果我有足够的分数来投票给你的答案,我会的。但是,我确实将其标记为已批准的答案,我非常感激。在我来寻求帮助之前,我可能应该花更多时间阅读文档。感谢您抽出宝贵时间给出如此简洁明了的答案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-06
  • 1970-01-01
  • 2020-05-25
相关资源
最近更新 更多