【问题标题】:Is anything wrong with my basic user authentication approach?我的基本用户身份验证方法有什么问题吗?
【发布时间】:2012-08-25 12:25:20
【问题描述】:

在安全性和身份验证以及大多数其他客户端-服务器通信方面,我还是个新手。我想做的很简单,如果可能的话,避免引入 3rd 方框架和类来完成这项工作。 (我在 Python 中使用 Google App Engine)

用户通过移动应用程序 (iOS) 登录一次服务。登录后,用户将发出许多请求,以获取消息、朋友、状态等信息。因此,每次应用程序与服务器通信时,我们不会发送该用户的电子邮件和密码进行身份验证,而是发送一个会话 ID。到目前为止,这只是我对系统的理解。

我想出了一个非常简单的方法,对我来说似乎效果很好,但是由于缺乏经验,我可能看不到很多东西。这样做会有什么问题:

  1. 在设备上,用户输入电子邮件/密码,凭据被发送到服务器并进行验证,一旦通过验证,就会生成一个随机数。这个随机数以 IntegerProperty 的形式存储在 User 模型上,名称为 session_number。

  2. session_number 被发送到用户的设备并被保存。现在,只要用户连接到服务器进行请求,session_number 连同用户的整数 ID 号都会发送到服务器。我们得到该 userId 的 User 实体,现在我们比较 user.session_number == incoming_session_number 的值。如果他们匹配,我们很好,否则错误。

  3. 如果用户注销,我们会从数据存储中清除 session_number。

这里唯一的另一个问题是当用户从多个设备登录时如何处理?每个设备都应该存储自己的 session_number 吗?

【问题讨论】:

  • “凭据被发送到服务器并经过验证”——这是最大的部分,也是最有可能存在安全漏洞的部分。你打算如何做这件事? (通过 HTTPS 登录 Google 帐户?)除此之外,您的方案似乎还可以,尽管您可能希望每隔一段时间强制用户重新登录(以减轻任何会话劫持风险)。请注意,我也远不是安全专家 ;-)
  • 通过验证我的意思是如果incoming_password == user.password,然后验证,否则错误。
  • 如何确保凭据被加密?
  • @Cameron 密码作为 SHA256 哈希值存储在数据存储中,该值也是通过无线电波发送的内容。
  • 因此,本质上,您的密码是以明文形式发送的,因为:如果有人偷看您的数据库,他们可以准确地看到他们需要通过网络发送的登录信息。存储散列密码,然后将其作为相同的字符串发送,首先破坏了使用散列的保护。您应该使用不同的加密,例如: 从服务器发送一个随机 nonce 字符串(可能是 32 个随机字符);让客户端将其附加到其密码的哈希中,然后对结果进行哈希处理,然后将其发回;然后服务器将 nonce 附加到存储的密码以检查...

标签: security google-app-engine session authentication


【解决方案1】:

这种安全模型的一个重要方面是您如何存储密码(或者更确切地说,是如何不存储密码)。这并不像看起来那么简单,因为如果操作不当会有很多危险。

This article 涵盖了基础知识,并提供了一个很好的大纲来说明要研究的内容。它讨论了散列、彩虹表、加盐和慢散列函数。

由于您使用的是 App Engine,因此您需要研究 Python 中的加密库。 hashlibhmac 是标准库的一部分,但不幸的是 Python 的标准库没有任何慢散列函数的实现,如 bcryptpbkdf2

因此,我建议使用passlib 进行密码散列。我自己将它用于一个项目(正在进行中)。具体来说,我决定使用sha512_crypt - 你可以使用look at the code here(欢迎任何批评)。

显然,还有一个 JavaScript implementation 的 PBKDF2 由斯坦福构建。

【讨论】:

  • +1 用于 passlib 推荐,但是我建议使用 CryptContext 以便您可以自适应并随着时间的推移轻松添加更复杂的方案和选项,而无需重新编写身份验证代码或需要升级所有旧密码。见这里:packages.python.org/passlib/lib/passlib.context-tutorial.html
  • 我正在尝试消化所有这些新信息并有一些问题。首先,我在哪里为密码、客户端或服务器生成盐和哈希是否重要?我对 Objective-C 更满意,并且已经在那里设置了它,这就是我现在正在做的事情。我正在使用常量盐对用户的密码进行散列(正如我所读到的,它应该在运行时创建),并发送该散列以进行验证。但是服务器除了检查 hash1 == hash2 之外什么都不做。我还是没有抓住重点吗?
  • @BryceCutt:太好了!谢谢你的链接,我一定会看看的。
  • @mohabitar:服务器当然更方便也更安全。如果您想在 JavaScript 中对其进行哈希处理,那么浏览器可以将其关闭,所以...
  • @voithos 在您添加新方案时使用 CryptContext 作为默认设置,所有新密码都将使用新方案进行加密,但旧密码仍然有效。您可以将一些代码添加到您的登录处理程序,以检测某人的密码是使用旧方案存储的,然后重新加密并存储它,以便您可以逐步升级旧密码。随着密码破解者变得更快、更成熟,这应该有助于我们跟上。
【解决方案2】:

除了我关于重新发明轮子的评论;我看到这个理论的主要问题是它完全受到重放攻击。

例如,给定一个 ID 为 99 且会话 ID/cookie 为“MONKEY-1”的用户——如果用户想要发送请求,他必须在某些情况下使用“from, 99, MONKEY-1”对其进行签名时尚。让我们假设它是这样的:

{ FROM: 99, TO: SERVER, COMMAND: GET-NEW-MAIL, SESSION: MONKEY-1 }

太好了,如果 MONKEY-1 是秘密的话。 (无论你喜欢什么格式;它也可能是一个 4KiB 的二进制线噪声块。)

然而,现在我们有一个窃听者,他看到了那个数据包经过……她现在发送了她自己的数据包。也许她很聪明,并且欺骗了真实用户 99 正在使用的源 IP 地址——即使窃听者听不到您的回复,她仍然可以向您发送数据包。也许那个看起来像这样:

 { FROM: 99, TO: SERVER, COMMAND: DELETE-ALL-MAIL, SESSION: MONKEY-1 }

有很多方法可以防止这种事情发生,它们的效果各不相同。在 SSL 中包装连接有助于使这变得极其困难(但绝不是不可能);这是很多网站都依赖的解决方案。更好的方法是使用双向通信以一种入侵者无法获取秘密数据的方式对事物进行签名,使用散列或公钥/私钥加密。例如,假设我们有这样的交换:

{ FROM: SERVER, TO: ??, COMMAND: PLEASE-LOGIN, NONCE: PIGEON }

{ FROM: BILL, TO: SERVER, COMMAND: LOGIN, AUTH: hash ( hash ( password ) . "PIGEON" ) }

...其中hash() 表示,例如,SHA-256 总和,. "PIGEON" 表示串联;

{ FROM: SERVER, TO: BILL, COMMAND: LOGIN-OK, SESSION: hash ( password ) ^ "MONKEY-1" }

... 其中^ 指的是一些操作,比如按位异或,也许。 然后,随后,Bill 发送如下请求:

 { FROM: BILL, TO: SERVER, COMMAND: GET-NEW-MAIL, NONCE: "ARMADILLO", 
   AUTH: hash ( "GET-NEW-MAIL" . #\Newline . "ARMADILLO" . #\Newline . "MONKEY-1" ) }

现在,MONKEY-1 从来没有在空旷的地方旅行过;并且,在每个请求上给出的 AUTH 密钥与所使用的动词或命令以及一个随机数相关联,该随机数应该随每个请求而变化,服务器可以轻松验证其完整性,但窃听者无法再次重播相同的消息,或更改动词并做一些不同的事情。

解释密码问题:

我有一个数据库表,它包含

User: BILL, Password: hash(DOLPHIN)

如果我在网上收到了

{ FROM: BILL, PASSWORD: hash(DOLPHIN), COMMAND: GET-ALL-MAIL }

……那么窃听者不太可能(但相当合理)知道密码是 DOLPHIN,但是,她不需要知道或关心:

 { FROM: BILL, PASSWORD: hash(DOLPHIN), COMMAND: DELETE-ALL-MAIL }

您提到了对密码进行加盐……您会怎么做?

 User: BILL, PASSWORD: hash( SALT . DOLPHIN )

除非您将 SALT 和 DOLPHIN 分开存放,否则您很难从 hash ( SALT . DOLPHIN )hash (DOLPHIN)。因此,要么用户必须向您提交hash ( SALT . DOLPHIN )(在客户端放置一个静态 SALT),要么您必须再次存储明文密码。

解决方法可能是做类似的事情

 Database: ( BILL => hash ( SALT . DOLPHIN ) )

 Server sends: ( NONCE )

 Client sends: ( BILL => hash ( NONCE . hash ( SALT . DOLPHIN ) ) )

【讨论】:

  • 注意:上面不是一个功能齐全的例子......这就是为什么我没有让它看起来太像伪代码......它错过了很多优势案例和可能的攻击媒介。我只是希望解释基本的“我信任来自客户端的魔法 cookie”模型的一些主要问题。
  • 我不明白你是如何使用随机数的。谁生成随机数?当我向服务器发送哈希 ( hash (password) . "PIGEON" ) 时,我如何验证这是否是正确的密码?我是否在客户端加密并在服务器端解密?存储用户密码的值是多少?
  • 这行到底是什么意思:{ FROM: SERVER, TO: ??, COMMAND: PLEASE-LOGIN, NONCE: PIGEON }
  • nonce 是一个随机值;可能是一个由许多随机字节组成的字符串,保证不会两次相同;这里的第一个(PIGEON)是由服务器在登录前生成的(TO:??,因为服务器不知道用户是谁)——关于h(h(pass).nonce)——服务器知道 h(pass ) (但不是通过),因此它可以从数据库中获取h(pass),附加它自己的随机数,然后对其进行哈希处理,然后将其与客户端返回的字符串进行比较。
  • 当然,nonce 永远不应该是英文单词,只是很多线路噪音(例如尝试dd if=/dev/urandom bs=512 count=1),我只是使用任意字符串来使其清晰可见。
猜你喜欢
  • 2011-02-01
  • 2015-07-22
  • 1970-01-01
  • 2018-12-20
  • 2021-05-17
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多