我应该使用Base64或Unicode来存储哈希和盐?

时间:2014-11-17 21:56:45

标签: encryption unicode

我从未在网络应用程序的安全方面工作,因为我刚刚大学毕业。现在,我正在寻找一份工作并在一些网站上工作,以保持我的技能敏锐并获得新的技能。我正在研究的一个网站几乎是从创建它的人那里复制而来的original MEAN stack,但试图理解它,并尽我所能做更好的事情。

计算哈希&盐,创作者使用PBKDF2。我对听到支持或反对PBKDF2的论据不感兴趣,因为这不是这个问题的内容。他们似乎在这里使用了缓冲区,我理解这是node中的常见做法。我感兴趣的是他们使用base64进行缓冲区编码的原因,而不是简单地使用UTF-8,这是缓冲区对象的一个​​选项。现在大多数计算机都可以处理Unicode中的许多字符(如果不是全部的话),但是创建者可以选择在Unicode的子集中编码密码,而不会将自己限制为base64的65个字符。

通过“编码为UTF-8base64”之间的选择,我的意思是将从密码计算的哈希的二进制转换为给定的编码。 node.js指定了将二进制数据编码为Buffer对象的几种方法。从Buffer类的文档页面:

Pure JavaScript is Unicode friendly but not nice to binary data. When dealing with TCP
streams or the file system, it's necessary to handle octet streams. Node has several
strategies for manipulating, creating, and consuming octet streams.

Raw data is stored in instances of the Buffer class. A Buffer is similar to an array
of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer
cannot be resized.

根据我的理解,Buffer类的功能是获取一些二进制数据并计算每个8(通常)位的值。然后,它将每组位转换为与您指定的编码中的值对应的字符。例如,如果二进制数据为00101100(8位),并且您指定UTF-8作为编码,则输出将为,(逗号)。这是任何查看缓冲区输出的人在使用文本编辑器(例如vim)查看它时会看到的内容,以及计算机在“读取”它们时“看到”的内容。 Buffer类有几种可用的编码,例如UTF-8base64binary

我认为他们觉得,虽然在哈希中存储任何可以想象的UTF-8个字符,但他们不得不这样做,不会使大多数现代计算机与其千兆字节的RAM和太字节的空间相交,实际上显示所有这些字符,因为他们可能想要在日志中等等,会吓到用户,他们必须看看奇怪的中文,希腊文,保加利亚等字符,以及控制字符,比如Ctrl按钮或Backspace按钮甚至是哔哔声。除非他们是有经验的用户测试PBKDF2本身,否则他们永远不会真正理解他们中的任何一个,但程序员的首要任务是不给他的任何用户心脏病发作。使用base64会增加大约三分之一的开销,这几天几乎不值得注意,并且减少了字符集,这对降低安全性没有任何作用。毕竟,计算机完全用二进制编写。正如我之前所说,他们可以选择不同的Unicode子集,但base64已经是标准的,这使得它更容易并减少了程序员的工作。

我是否正确了解此存储库的创建者选择在base64中编码其密码而不是所有Unicode的原因?坚持使用他们的示例是更好,还是应该使用Unicode或更大的子集?

4 个答案:

答案 0 :(得分:54)

哈希值是 bytes 的序列。这是二进制信息。它是一系列字符。

UTF-8是一种用于将字符序列转换为字节序列的编码。将哈希值“存储为UTF-8”是没有意义的,因为它已经是一个字节序列,而不是一个字符序列。

不幸的是,许多人已经习惯于将一个字节视为伪装中的某种角色;它是C编程语言的基础,仍然会感染一些相当现代和广泛的框架,如Python。然而,只有混乱和悲伤躺在那条道路上。通常的症状是人们对可怕的“字符零”表示哀嚎和抱怨 - 意思是,值为0的字节(一个字节的完美值),变成一个字符,变成特殊的用作C系列语言中字符串结尾指示符的字符。这种混淆甚至可能导致漏洞(对于比较函数,零意味着早于预期的终止)。

一旦你理解二进制是二进制的,问题就变成了:我们如何处理和存储我们的哈希值?特别是在JavaScript中,一种已知在处理二进制值时特别差的语言。解决方案是一种编码,它将字节转换为字符,而不仅仅是任何字符,而是一个非常小的良好行为字符子集。这称为Base64。 Base64是一种通用方案,用于将字节编码为不包含有问题字符的字符串(没有零,只有ASCII可打印字符,不包括所有控制字符和其他一些如引号)。

不使用Base64意味着假设JavaScript可以管理任意字节序列,就好像它只是“普通字符”一样,而这根本不是真的。

答案 1 :(得分:30)

存在与Base64而不是Unicode存储的基本安全相关原因:哈希可能包含字节值" 0",被许多编程语言用作字符串结束标记。

如果将哈希存储为Unicode,您,另一个程序员或您使用的某些库代码可能会将其视为字符串而不是字节集合,并使用strcmp()或类似的字符串比较函数进行比较。如果你的哈希包含字节值" 0",你有效地将你的哈希截断到" 0"之前的部分,使攻击变得更容易。

Base64编码避免了这个问题:字节值" 0"不能以散列的编码形式出现,因此如果您使用memcmp()(正确的方式)或strcmp()(错误的方式)比较编码的哈希值并不重要。

这不仅仅是一个理论上的问题:使用strcmp()检查数字签名的代码有多种情况,大大削弱了安全性。

答案 2 :(得分:13)

这是一个简单的答案,因为有大量字节序列不是格式良好的UTF-8字符串。最常见的是一个连续字节(0x80-0xbf),它不在多字节序列(0xc0-0xf7)中的前导字节之前;字节0xf8-0xff也无效。

所以这些字节序列不是有效的UTF-8字符串:

  • 0x80的

  • 0x40 0xa0

  • 0xff的

  • 0xFE的

  • 0xFA回应

如果要将任意数据编码为字符串,请使用允许它的方案。 Base64就是其中一种方案。


一个额外的观点:你可能会想到,好吧,我并不关心他们是否是格式正确的UTF-8字符串,我永远不会将这些数据用作string,我只想把这个字节序列存储起来以备日后使用。

问题在于,如果您为期望UTF-8字符串的应用程序提供任意字节序列,并且格式不正确,则应用程序没有义务使用此字节序列。它可能会因为错误而拒绝它,它可能会截断字符串,它可能会尝试"修复"它。

所以不要尝试将任意字节序列存储为UTF-8字符串。

答案 3 :(得分:3)

Base64更好,但考虑使用websafe base64字母表进行传输。 Base64可能与查询字符串语法冲突。

您可能考虑使用的另一个选项是使用十六进制。它更长但很少与任何语法冲突。