有没有被广泛认为可信的SHA-256 JavaScript实现?

169

我正在为一个论坛编写登录功能,在将密码发送到服务器之前需要在Javascript中对其进行哈希。我有些困惑,不知道哪种SHA-256实现是可靠的。我原本以为会有一些权威的脚本是大家都用的,但我发现有很多不同的项目,它们都有自己的实现。

我知道使用别人的加密代码总是要冒风险的,除非你有资格自行审查它,而且没有普遍公认的“可信”定义,但这似乎是一个常见而且重要的问题,应该有一些共识。难道我太天真了吗?

编辑:因为评论区经常出现这个问题,所以我补充一下:是的,我们会在服务器端再次进行更严格的哈希处理。客户端哈希不是我们保存在数据库中的最终结果。客户端哈希是因为人类客户端请求它。他们并没有给出具体的原因,可能只是觉得太保险了。


10
不要跑题,但是为什么你要在客户端对密码进行哈希处理? - Steve
7
@ddyer 根本不是这样。 "不要自己造轮子" 适用于发明自己的算法,编写自己的 算法实现,在加密算法之上开发自己的协议,或几乎任何高于使用尽可能高级别的抽象。如果你认为只依赖安全核心并仅编写粘合代码就能保持安全,那么你会遇到麻烦的。 - Stephen Touset
34
如果你使用没有挑战/响应协议的哈希密码,那么哈希密码就是密码,实际上与明文传输密码没有什么区别。 - ddyer
34
@ddyer,如果不是针对我们的网站,保护用户在其他网站上使用的明文密码也具有一定的价值。这是一个简单的修复方法,也许对我们没有帮助,但如果我们在某些方面出了差错,它可以潜在地帮助用户。就像我之前说的,这是客户的要求,即使我想改变也无能为力。 - jono
8
@Anorov 我非常愿意改变我的想法 :) 但在这种情况下,我确实不理解您的观点如何适用。我们会对密码进行两次哈希:一次在客户端使用简单的SHA-256,一次在服务器端使用更复杂的算法。第一次是为了保护明文密码,以防止中间人或类似攻击;第二次则是为了防止暴力破解。即使您获取了数据库和管理员哈希值,也无法直接使用它们来验证登录。 - jono
显示剩余13条评论
10个回答

180

我在https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest上找到了这个使用内部js模块的片段:

async function sha256(message) {
    // encode as UTF-8
    const msgBuffer = new TextEncoder().encode(message);                    

    // hash the message
    const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);

    // convert ArrayBuffer to Array
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    // convert bytes to hex string                  
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
}

请注意,crypto.subtle 仅在 httpslocalhost 上可用。例如,在使用 python3 -m http.server 进行本地开发时,您需要将以下行添加到您的 /etc/hosts 文件中:0.0.0.0 localhost

重启后,您就可以打开 localhost:8000 并使用 crypto.subtle


5
太棒了,谢谢。在Node.js中是否有类似的API可用? - Con Antonakos
4
内置的 'crypto' 库应该可以胜任:https://nodejs.org/dist/latest-v11.x/docs/api/crypto.html - tytho
3
对我来说失败了(Chrome 88): TypeError: Cannot read property 'digest' of undefined。由于不明原因,crypto.subtle仅在HTTPS上下文中定义,这使其不适用于一般用途。 - Tino
@Tino 这个链接可能会有帮助吗?https://dev59.com/FVIH5IYBdhLWcg3wZtbI 请查看问题的更新。 - carloswm85
2
@Tino “通用使用” 通常意味着在我们进入21世纪后,现在是HTTPS。 - jbg

143

已过时:许多现代浏览器现在都拥有对加密操作的一流支持。请参见下面Vitaly Zdanevich的回答


斯坦福JS加密库包含了SHA-256的实现。虽然JS中的加密并不像其他实现平台那样经得起考验,但这个库至少部分由Dan Boneh开发和赞助,他是密码学领域一个知名且值得信任的人物,并且这意味着该项目受到真正懂得此领域的人的监督。该项目也得到了NSF的支持。

然而,值得指出的是...
...如果在提交前在客户端哈希密码,则散列是密码,原始密码变得无关紧要。攻击者只需要拦截哈希即可冒充用户,如果该哈希未经修改地存储在服务器上,则表明服务器以纯文本形式存储了真实密码(哈希)

因此,由于您决定添加自己的改进到之前信任的方案中,所以您的安全性现在变得更糟糕了


44
但是如果在服务器上再次进行哈希,这种做法是完全合法的。 - Nick Brunt
15
如果您在服务器上进行哈希处理,则不会保存传输的密码,但与仅传输密码哈希相比,并没有增加任何安全性,因为在任一情况下,您传输的都是真实密码。请注意,此翻译可能有多种表达方式,但意思不变。 - tylerl
64
没错,但这并不比明文发送可能在互联网上其他地方使用过的密码更糟糕。 - Nick Brunt
18
我同意 @NickBrunt 的观点。最好让攻击者读取一个随机哈希字符串,它可能只适用于这个特定的应用程序,而不是原始密码,因为原始密码可能在多个地方使用。 - Aebsubis
13
我需要使用客户端哈希,因为我不希望服务器看到用户的明文密码。这并不是为了增加我所提供服务的安全性,而是为了保护用户。我不仅保存用户的哈希值与一个常量盐(每个用户都有自己的常量盐,而非全局),而且每次登录时还会将其重新进行哈希,使用一个随机的会话盐对网络嗅探提供了一定的额外安全保障。如果服务器被攻破,那么就无法保护了,但这对任何不是真正点对点的东西都是适用的。 - Hatagashira
显示剩余14条评论

40

有兴趣的人,这是使用sjcl创建SHA-256散列的代码:

import sjcl from 'sjcl'

const myString = 'Hello'
const myBitArray = sjcl.hash.sha256.hash(myString)
const myHash = sjcl.codec.hex.fromBits(myBitArray)

9
对于那些希望使用该解决方案的人来说,这应该纳入被接受的答案中。(甚至他们自己的文档中也没有类似这样的片段。) - MagicLegend
2
这是最好的答案。我曾尝试过加密解决方案,但对我无效。 - Augusto Gonzalez

21

1
我已经成功通过 npm install node-forge 安装了它,但是现在,如何使用这个库来从字符串 foo 创建一个哈希值? - user

15

不,无法使用浏览器JavaScript提高密码安全性。我强烈建议您阅读这篇文章。在您的情况下,最大的问题是“鸡蛋问题”:

使用JavaScript加密时的“鸡蛋问题”是什么?

如果您不信任网络传输密码,或者更糟糕的是,不相信服务器不会保留用户的机密信息,那么您就不能信任它们传递安全代码。在引入加密之前嗅探密码或阅读日记的同一攻击者只是在您这样做后劫持加密代码。

[...]

为什么不能使用TLS/SSL传递Javascript加密代码?

你可以这样做。虽然比听起来困难得多,但是你可以使用SSL安全地将Javascript加密传输到浏览器。问题是,一旦通过SSL建立了安全通道,你就不再需要Javascript加密;你已经拥有了“真正”的加密。

这导致了以下情况:

在Javascript中运行加密代码的问题在于,实际上任何与加密相关的函数都可能被用于构建托管页面的任何内容静默地覆盖。加密安全可能会在过程早期(通过生成虚假随机数或篡改算法使用的常量和参数)或过程后期(通过将密钥材料带回攻击者)被撤销,或者在最有可能的情况下完全绕过加密。
没有可靠的方法让任何JavaScript代码验证其执行环境。JavaScript加密代码不能询问“我是否真的正在处理随机数生成器,还是处理攻击者提供的某种仿真?”而且它肯定不能断言“除了我,作者,批准的方式外,没有人被允许以任何方式处理这个加密秘密”。这些是其他使用加密的环境通常提供的两个属性,在JavaScript中是不可能的。
基本上问题就是这样:
您的客户不信任您的服务器,因此他们想添加额外的安全代码。
该安全代码由您的服务器(他们不信任的那些)提供。
或者换句话说,
  • 你的客户不信任SSL,因此他们希望你使用额外的安全代码。
  • 该安全代码是通过SSL传递的。

注意:另外,SHA-256不适合这种情况,因为它很容易被暴力破解非盐非迭代密码。如果你还是决定这样做,可以寻找bcrypt, scryptPBKDF2的实现。


2
这是不正确的。可以通过在客户端进行哈希处理来提高密码安全性。同样,只要在服务器端使用类似PBKDF2的东西,SHA-256就不会不适用。第三,虽然matasano文章包含有用的见解,但结论是错误的,因为我们现在有基本上改变了先有鸡还是先有蛋问题的浏览器应用程序。 - user239558
2
你说得对,PBKDF2 应该在客户端使用。反对客户端 JavaScript 加密的怒火假设初始页面和后续请求具有相同的安全属性。这显然不适用于浏览器应用程序,其中初始页面是单独安装并且是静态的。对于任何具有缓存永久语义的页面也是如此。在这些情况下,TOFU 与安装客户端程序时完全相同。在更复杂的设置中,静态和动态页面的提供也可能是分开的。 - user239558
5
避免系统管理员访问用户密码是合理的,因此在客户端进行哈希处理可以防止数据库管理员或其他IT专业人员查看用户密码并尝试重复使用它来访问其他服务/系统,因为许多用户会重复使用他们的密码。 - Yamada
3
@Xenland 鸡和蛋问题与你发送的请求无关。问题在于你的客户端使用从不安全连接下载的JavaScript代码来进行安全工作。安全地将JavaScript发送到Web浏览器的唯一方法是通过TLS提供它,一旦这样做,你就不需要再做其他任何事情,因为你的连接已经是安全的了。如果你使用的是攻击者可以替换的JavaScript代码,那么你的协议是什么并不重要。 - Brendan Long
1
你应当在客户端对密码进行哈希的原因并不是为了让 HTTP 请求在互联网中传输更加安全(TLS 已经做到了这一点)。相反,其目的在于避免明文密码出现在服务器中间件等地方的日志文件中。当服务器端日志文件中存在明文密码时,那些可以访问这些日志文件的人就有可能恶意使用这些信息。大多数人在许多网站上都使用相同的密码,因此如果日志文件显示“myuser@acme.com”的密码为“Tiger123”,那么这些信息就可以被用于攻击该用户可能使用的其他网站。 - deltamind106
显示剩余11条评论

13

我发现这个实现非常易于使用。同时还有一份慷慨的类BSD许可证:

jsSHA:https://github.com/Caligatio/jsSHA

我需要一种快速获取SHA-256哈希值十六进制字符串表示的方法。只需要三行代码:

var sha256 = new jsSHA('SHA-256', 'TEXT');
sha256.update(some_string_variable_to_hash);
var hash = sha256.getHash("HEX");

3

2
除了tylerl提到的Stanford lib之外,我发现jsrsasign非常有用(Github repo在这里:https://github.com/kjur/jsrsasign)。我不知道它到底有多可靠,但我已经使用过它的SHA256、Base64、RSA、x509等API,效果非常好。事实上,它也包含了Stanford lib。
如果你只想使用SHA256,jsrsasign可能会过于复杂。但是如果你在相关领域有其他需求,我认为它是一个很好的选择。

3
使用https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API更安全、更快速。 - Vitaly Zdanevich
3
同意,但公平起见,我在2015年回答了这个问题,当时Mozilla的Web加密API规范是在2017年1月发布的。 - Faraway

2

js-sha256 是一个你可以使用的 npm 包,与只能在安全连接(localhost/https)上工作的流行的 crypto.subtle 不同,它可以在任何情况下工作。当然,拥有安全连接仍然是最好的选择。我一直在使用 crypto.subtle,因为我在本地主机上运行我的 Web 应用程序,所以它一直工作得很好,但是当我尝试在服务器上运行时,它立即失败了。我不得不暂时切换到 js-sha256 npm 包,直到可以配置安全连接。


2
只是忘记了如何安装 npm i js-sha256;,以下是使用示例:const sha256 = require('js-sha256'); hashed = sha256(yourtext); - danilo
我正在寻找 C# 中替代 System.Security.Cryptography.SHA256Managed 的方案,这是我找到的最接近的选择。 - Gangula

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接