如何通过 HTTP 安全地发送密码?
Posted
技术标签:
【中文标题】如何通过 HTTP 安全地发送密码?【英文标题】:How to send password securely over HTTP? 【发布时间】:2010-12-07 15:52:47 【问题描述】:如果用户在登录屏幕上提交带有用户名和密码的表单,密码将以纯文本形式发送(即使使用 POST,如果我错了,请纠正我)。
保护用户及其密码免受第三方窃听通信数据的正确方法是什么?
我知道 HTTPS 是解决问题的方法,但有没有办法使用标准 HTTP 协议(POST 请求)至少确保一定程度的安全性? (也许以某种方式使用 javascript)
我所关心的是一个页面 - 这是一个 php 生成的登录页面,它当然是作为 html 文件在 HTTP GET 请求中发送给用户的。服务器和客户端之间没有建立(@Jeremy Powel)连接,所以我无法创建这样的握手协议。我希望整个过程对用户透明——他想提交密码,而不是处理密码学。
【问题讨论】:
如果没有客户端使用密码学,您可能无法完成此操作,但用户不必看到这样的过程。他只需输入他的密码,您的 PHP 生成的代码(例如 javascript)就会为您处理这一切。 您描述的问题是发明HTTPS的原因。如果您向客户端发送秘密以加密密码,则窃听者将能够嗅探它并在回程中解密密码。 所以您的建议中的 S 可能只是密码(或以任何方式组合的用户名+密码),因为这是用户拥有的唯一“秘密”。我对么?所以解决方案如下: - 服务器为 HTML 页面提供一个隐藏的表单字段 R - 用户输入密码,在发送密码之前,javascript 计算 H(R,S) 并将其发送到服务器,甚至可能通过使用 AJAX - 服务器计算 H(R,S) 并将其与接收到的进行比较,并向 ajax 请求发送响应是否通过身份验证 - javascript 将浏览器重定向到所需的网页 @jeremy powell - 虽然您所描述的是常见做法,但它也容易受到中间人的攻击,他们可以从标头中嗅出 cookie 并通过重用 cookie 来冒充用户。除非您使用 HTTPS,否则很难防范中间人攻击 对于以后遇到这个问题的人:登录后,您还需要保护会话cookie。 (所以:使用 HTTPS 真的要容易得多。) 【参考方案1】:安全身份验证是一个广泛的话题。简而言之,正如@jeremy-powell 提到的,总是倾向于通过 HTTPS 而不是 HTTP 发送凭据。它将消除很多与安全相关的问题。
如今,TSL/SSL 证书相当便宜。事实上,如果您根本不想花钱,有一个免费的 letsencrypt.org - 自动证书颁发机构。
您可以更进一步,使用 caddyserver.com 在后台调用letsencrypt。
现在,一旦我们摆脱了 HTTPS 的阻碍......
您不应通过 POST 有效负载或 GET 参数发送登录名和密码。改用 Authorization 头(基本访问认证方案),其构造如下:
用户名和密码组合成一个字符串,用 a 分隔 冒号,例如:用户名:密码 生成的字符串使用编码 Base64 的 RFC2045-MIME 变体,但不限于 76 字符/行。 授权方法和空格即“基本”是 放在编码字符串之前。来源:Wikipedia: Authorization header
这可能看起来有点复杂,但事实并非如此。 有很多很好的库可以为您提供开箱即用的功能。
您应该使用授权标头有几个很好的理由
-
这是一个标准
很简单(在你学会如何使用它们之后)
它将允许您在 URL 级别登录,如下所示:
https://user:password@your.domain.com/login
(例如 Chrome 会自动将其转换为 Authorization
标头)
重要提示:
正如 @zaph 在下面的评论中指出的那样,将敏感信息作为 GET 查询发送并不是一个好主意,因为它很可能最终会出现在服务器日志中。Authorization
标头值传统上是 base64 编码的用户名/密码。 Base64 不是加密。原始值可以通过使用简单 base64 解码的路径上攻击获得。
【讨论】:
将凭据(密码)作为 GET 参数发送的问题在于,用户/密码对可能最终会出现在服务器日志中,这不是一个好主意。最好在 POST 中发送凭据。 啊……一点也不。您看到的屏幕截图已修改以说明浏览器中发生的情况。您点击进入浏览器的那一刻将转换您的 url,创建一个Authorization
标头。试一试。日志会提醒干净。当然,如果您从服务器拨打电话(如果这是您担心的情况),您当然应该以编程方式生成标头。
您无法记录您看不到的内容。来自浏览器的username:password@url
转换为:url
+ Authorization
请求标头。至于 GET 查询......就像我说的那样,使用授权标头。更好。
您没有解决问题的重点:保护敏感数据免受中间人窃听。重点不是寻找替代和/或更标准化的方式来传递凭据,而是通过不安全的渠道保护它们。
应该强调的是,该解决方案仅在您已经使用 TLS/SSL 加密连接时才有效。 base64 不是加密。【参考方案2】:
您可以使用质询响应方案。假设客户端和服务器都知道一个秘密 S。那么服务器可以通过以下方式确定客户端知道密码(不泄露):
-
服务器向客户端发送一个随机数 R。
客户端将 H(R,S) 发送回服务器(其中 H 是加密哈希函数,如 SHA-256)
服务器计算 H(R,S) 并将其与客户端的响应进行比较。如果它们匹配,则服务器知道客户端知道密码。
编辑:
这里有一个问题是 R 的新鲜度和 HTTP 是无状态的。这可以通过让服务器创建一个秘密(称为 Q)来处理,只有服务器知道。然后协议是这样的:
-
服务器生成随机数R,然后发送给客户端H(R,Q)(客户端无法伪造)。
客户端发送 R、H(R,Q),计算 H(R,S) 并将其全部发送回服务器(其中 H 是加密哈希函数,如 SHA-256)
服务器计算 H(R,S) 并将其与客户端的响应进行比较。然后它需要 R 并(再次)计算 H(R,Q)。如果客户端的 H(R,Q) 和 H(R,S) 版本与服务器的重新计算匹配,则服务器认为客户端已通过身份验证。
请注意,由于客户端无法伪造 H(R,Q),因此 H(R,Q) 充当 cookie(因此可以实际实现为 cookie)。
另一个编辑:
之前对协议的编辑是不正确的,因为任何观察过 H(R,Q) 的人似乎都能够使用正确的哈希重播它。服务器必须记住哪些 R 不再新鲜。我正在CW'ing这个答案,所以你们可以编辑这个并找出一些好的东西。
【讨论】:
+1 - 您需要使用 javascript(或 Flash/Silverlight/等)计算客户端的响应 不会阻止中间人或冒充攻击。例如。通过无线网络。看起来这只会给人一种虚假的安全感,IMO。 这可以防止被动攻击,但中间人仍然可以攻击。 同样需要服务器知道原始密码。 不要重新发明加密货币! (生产中)【参考方案3】:在这里使用 https 听起来是最好的选择(现在证书并不那么贵)。但是,如果需要 http,您可以使用一些加密 - 在服务器端对其进行加密并在用户浏览器中进行解密(单独发送密钥)。
我们在实现safevia.net 时使用了这一点 - 加密是在客户端(发送方/接收方)端完成的,因此用户数据在网络或服务器层均不可用。
【讨论】:
【参考方案4】:如果您的虚拟主机允许,或者您需要处理敏感数据,请使用 HTTPS,句号。 (这通常是法律要求的 afaik)。
否则,如果您想通过 HTTP 执行某些操作。我会做这样的事情。
-
服务器将其公钥嵌入登录页面。
客户端填充登录表单并点击提交。
AJAX 请求从服务器获取当前时间戳。
客户端脚本连接凭据、时间戳和盐(从模拟数据散列,例如鼠标移动、按键事件),使用公钥对其进行加密。
提交生成的哈希。
服务器解密哈希
检查时间戳是否足够新(仅允许短暂的 5-10 秒窗口)。如果时间戳太旧,则拒绝登录。
将哈希存储 20 秒。在此时间间隔内拒绝登录的相同哈希。
对用户进行身份验证。
因此,这样密码受到保护,并且无法重放相同的身份验证哈希。
关于会话令牌的安全性。这有点难。但是,重用被盗的会话令牌可能会更加困难。
-
服务器设置了一个额外的会话 cookie,其中包含一个随机字符串。
浏览器在下次请求时发回这个 cookie。
服务器检查 cookie 中的值,如果不同则销毁会话,否则一切正常。
服务器使用不同的文本再次设置 cookie。
因此,如果会话令牌被盗,并且其他人发送了请求,那么在原始用户的下一个请求中,会话将被销毁。因此,如果用户积极浏览网站,经常点击链接,那么小偷就不会拿走被盗的令牌。可以通过要求对敏感操作(如帐户删除)进行另一次身份验证来加强此方案。
编辑:请注意,如果攻击者使用不同的公钥设置自己的页面并代理对服务器的请求,这并不能防止 MITM 攻击。 为防止这种情况,必须将公钥固定在浏览器的本地存储或应用程序中,以检测此类技巧。
关于实现:RSA 可能是最知名的算法,但对于长密钥来说它相当慢。我不知道 PHP 或 Javascript 实现的速度有多快。但可能有更快的算法。
【讨论】:
这种情况下密码真的有保护吗?有人不能嗅出发送的内容并使用公钥对其进行解密,然后在他们以后使用它时更新时间戳吗?我错过了什么吗? @michaellindahl 非对称加密意味着只有永远不会离开服务器的私钥才能用于解密。公钥只能用于加密。 浏览器和服务器之间的计算机可以更改登录页面上的公钥。 感谢您分享您的方法,我发现一切都很有趣。发送凭证时添加的时间戳是一个不错的选择!关于使用值为随机字符串的额外会话 cookie 的一个问题:就像仅用于下一个请求的令牌?【参考方案5】:您可以将 ssl 用于您的主机,有免费的 ssl 项目,例如letsencrypt https://letsencrypt.org/
【讨论】:
检查有问题的第三句。 (另外,请务必阅读其他答案,以确保您没有添加不那么详细的重复内容。)【参考方案6】:您可以使用SRP 在不安全的通道上使用安全密码。这样做的好处是,即使攻击者嗅探到流量或破坏服务器,他们也无法在不同的服务器上使用密码。 https://github.com/alax/jsrp 是一个 JavaScript 库,支持在浏览器或服务器端(通过节点)通过 HTTP 安全密码。
【讨论】:
【参考方案7】:我会使用带有 AJAX 或多个表单提交的服务器端和客户端 Diffie-Hellman 密钥交换系统(我推荐前者),尽管我在互联网上没有看到任何好的实现。请记住,一个 JS 库总是可能被 MITM 破坏或更改。本地存储可以在一定程度上帮助解决这个问题。
【讨论】:
【参考方案8】:HTTPS 之所以如此强大,是因为它使用了非对称加密。这种类型的密码学不仅允许您创建加密隧道,而且您可以验证您正在与正确的人交谈,而不是黑客。
这是使用非对称密码 RSA(PGP 使用)进行通信的 Java 源代码: http://www.hushmail.com/services/downloads/
【讨论】:
【参考方案9】:将 HTTP 与 SSL 结合使用将使您的生活更轻松,您可以高枕无忧。非常聪明的人(至少比我聪明!)多年来一直在仔细研究这种保密通信方法。
【讨论】:
...和 “但我必须为 SSL 证书付费!!” 不是有效的投诉,因为这些天你可以花 30 美元买到它们。你的数据真的不值 30 美元来保护吗? 如果您订阅的主机不支持添加SSL证书怎么办? @Calmarius - 然后你转移到一个真正的虚拟主机 @BornToCode 这在技术上意味着您需要拥有一个专用 IP 并且您需要拥有服务器硬件(或至少一个 VPS)才能使用 HTTPS。共享的虚拟主机不能使用 HTTPS,除非整个服务器都受到主机所有者证书的保护。 共享虚拟主机当然可以使用 https,使用 en.wikipedia.org/wiki/Server_Name_Indication以上是关于如何通过 HTTP 安全地发送密码?的主要内容,如果未能解决你的问题,请参考以下文章