如何有效地将二进制数据存储在 CSV 等文本格式的文件中?

How can I effectively store binary data in a file that's in a text format like CSV?

我目前正在 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() argument must be str, not bytes

所以,我使用

将其转换为字符串
str(storage)

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

b'\xfd\x3a'

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

b"\xf1\x96"

"b\xf1\x96"

它打印编码文本,而不是它所组成的字节。但是,我不知道这是否是一种改变它的好方法,如果是,是否有一种方法可以在没有类似

的情况下做到这一点
bytes[0] = '"'
bytes[1] = 'b'

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

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

So, I converted it to a string using str(storage)

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

when I print the bytes, it's a bunch of characters with backslashes and X's

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

But then, when I get it from the file, it's still a string, and the length goes from 128 (bytes) to 300+ (chars).

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

It's also never consistent. I don't know the encoding

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

I've found a way to do it, by changing

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

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

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

如果你想将字节保存为字符串,你应该将它们编码成一种格式,比如 base64. This is 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