认证方法
我正在写一个服务器-客户端应用程序,用来接收用户消息并发布这些消息。
我在考虑认证的方法。
- 非对称加密,可能会用到RSA。
- 哈希(盐+密码+'消息'+'用户ID'),使用SHA256。
- HMAC,使用SHA256。这个方法似乎比第二种更安全,也涉及到对密码和消息数据的哈希处理。
- 用静态密码对消息进行对称加密,双方都存储这个密码,可能会用到AES。
其实不需要加密,因为消息最终会在线发布。所以我更倾向于使用HMAC或公钥基础设施(PKI)的方法。
我用Java发送请求(包括要实现的任何认证方法): www.mysite.com/foo?userid=12345&msg=hello&token=abc1234
服务器端用Python来接收请求,并确保请求来自有效用户。
我面临的问题是如何整合这两边的代码,确保双方能理解对方的认证令牌。这对我来说是个大问题。同时,我也考虑到两种语言中可用的库。
或者我可以选择把服务器端的代码重写成Java。(为什么不用Python?因为客户端应用需要用Java写)
你觉得怎么样?
补充说明:我不会把这个当作一个网页应用。这个应用不提供任何网页。大部分操作和网页无关。只是客户端和服务器之间的通信通过互联网进行。我认为通过HTTP发送POST/GET请求是一个简单但不安全的方法。如果你有其他建议,请随时告诉我。
SSL是一种安全的加密方式,但它并不能防止攻击者冒充用户向服务器发送消息。任何人都可以发起HTTPS连接。而且通信内容不需要加密,因为服务器最终会在线发布这些内容。我需要的是一种验证消息和发送者身份的方法。
目前我倾向于使用HMAC算法。RSA会更安全,但开发起来也更麻烦。看看接下来会怎样吧。
谢谢。
4 个回答
让我给你的提议加几条随意的想法和评论。
- 关于RSA:因为你主要关注的是身份验证,我想你可能想用RSA签名。这意味着每个用户都需要自己的私钥。对我来说,这听起来有点过于复杂,尤其是当用户和服务器已经共享一些共同的秘密(比如密码)时。
- 使用SHA256是个不错的选择。我有点不明白你为什么要加盐,因为盐通常是用来防止攻击者预先计算字典中的哈希值。简单来说,字典攻击在这里是个问题。攻击者可能会尝试用字典里的所有密码来哈希消息。问题是他必须为每个用户重复这个攻击,不能重用之前计算的值。让这种攻击稍微难一点的方法叫做“密钥强化”,比如重复哈希结果n次,这样就迫使攻击者在进行字典攻击时需要做更多的工作。
- 我完全同意HMAC比单纯的SHA-256要好。毕竟HMAC是为了身份验证而设计的。我不太明白你为什么想在计算HMAC之前先哈希消息,HMAC本身就会为你做这个。
- 加密并不总是能提供身份验证。特别是在攻击者能够获取明文的情况下,就像你提到的那样。他可能会利用加密模式的特性来操控密文,并准确知道最终的明文会是什么样子(例如,CBC模式允许这种操控)。你之前提到的方案(HMAC)更可取。
最后,如果你能在用户和服务器之间建立SSL连接,正如其他人所建议的那样:这将是一个更安全的解决方案,因为连上面提到的字典攻击都不可能发生。
首先,有一个选择可以考虑,就是走“企业解决方案”这条路,购买标准的公钥基础设施(PKI)服务:从Veri$ign等公司买一个证书,然后使用普通的HTTPS(或者至少是底层协议TLS)。这样,从客户端发送数据就会变得相对简单,而且你选择的任何编程语言都会有标准库来处理这些数据。我其实觉得HTTPS/TLS和整个证书体系对于那些你知道自己在和哪个服务器沟通、也知道想用什么加密系统的应用来说,复杂得有点浪费空间。但是,现成的库可以让你快速上手,所以无论如何考虑一下这个方案都是值得的。
原则上,你并不需要PKI颁发的证书就能使用HTTPS/TLS。但实际上,花几美元买个证书可能比说服标准库接受自签名证书要简单得多。(一般来说,使用HTTPS库可能会让你把原本需要两天解决的“编程我的加密系统”的问题,变成两天的“为什么这个黑箱不接受我的证书”的问题——这也有点看你想解决什么问题……)
即使你不使用HTTPS/TLS,你也可能想看看相关的规范,以了解它是如何应对某些安全威胁的(以及那些威胁是什么)。
好吧,接下来我来回应你的几点:
- 首先要明确的是,使用RSA或其他非对称加密时,你并不需要证书或PKI。PKI试图解决的是客户端需要和一个“神秘”服务器沟通的问题,这个服务器在说“这是我的公钥”时,客户端无法信任。如果你在写一个专用客户端,并且提前知道你要和哪个服务器沟通,那么你可以直接把公钥和客户端一起分发。客户端知道自己在和正确的服务器沟通,因为如果不是,那服务器就无法理解用那个公钥加密的数据。
- 使用“原始”哈希时,你需要小心的是它们容易受到扩展攻击。所以如果你发送“a|b|c”以及该数据的哈希码,攻击者可以算出“a|b|c|d”的哈希码(在你的例子中(2),他们可以把“userid”替换成“useridX”,并根据你提供的“a|b|c”的哈希码算出一个新的哈希码)。HMAC方案可以解决这个问题。
- 无论如何,如果你选择使用“原始”加密工具,还是要使用标准库,因为它们会考虑到一些安全问题。例如,使用RSA时,你需要小心使用填充(可以看看我写的关于Java中的RSA加密的信息——这可能对你有帮助)。而在生成任何加密密钥时,你需要注意“随机数据的来源”。
- 你不一定要把AES加密密钥基于密码(如果你这样做,建议了解一下基于密码的加密方案:密码通常熵值很低,所以你需要采取额外措施来保护自己)。但我建议采用更标准的方法,用密码来验证连接,并在认证过程中协商一个随机的AES密钥。
- 确保在你的方案中解决重放攻击的问题(一般来说,客户端和服务器在通信开始时应该互相发送一个随机的“随机数”,而认证和未来的通信将依赖于这个随机数)。
你可以直接用标准的SSL套接字来保护连接,先用密码验证用户,然后再发送要发布的消息吗?如果客户端不多,你甚至可以使用自签名证书,把它放在客户端应用的KeyStore里,这样就不需要从Verisign或者其他地方购买证书了。
如果你在服务器上存储用户的密码哈希(我觉得应该这样做,不然你怎么验证用户的密码呢),那么你可以先在客户端生成这个哈希(假设你是把用户名和密码组合在一起进行哈希),然后把这个哈希附加到消息上,再对结果进行哈希。最后,你把用户ID、哈希和消息一起发送到服务器。
在服务器端,你会收到一个包含用户ID的请求,你从服务器上获取用户的密码(服务器只存储用户名和密码的哈希),然后把这个密码附加到消息上,再次进行哈希,并与请求中的哈希进行比较。如果匹配,用户就可以发布消息了。
哦,还有,你应该使用HTTP POST,而不是把数据放在URL里。服务器不应该接受在URL中发送的发布请求数据。