我在RESTful API项目中使用身份验证方法,我非常喜欢将HMAC-SHA256签名生成为身份验证方法。
客户端正在创建签名,只需几个简单的步骤:
# example client-side code
sig = hmac.new(bytes('SUPER_SECRET_KEY', 'utf-8'), b'', sha256)
sig.update(request_path)
sig.update(request_body)
# ...other variables needed for generating the signature...
signature = sig.hexdigest()
并将其添加到请求标题及其“用户名”(例如Authorization: THE_USER_NAME:abcd1234xyz890
)。
在服务器端,我试图以相同的方式重新创建它:
# example server-side code
def do_check(request):
# get user name from request header
username = request.headers['Authorization'].split(':')[0]
# some method to retrieve the "secret key" from database
user = db.User().filter(username=username).one()
# use user's "secret key" to generate the signature
sig = hmac.new(bytes(user.key, 'utf8'), b'', sha256)
sig.update(bytes(request.path, 'utf-8'))
sig.update(request.data)
# ...other variables needed for generating the signature...
return sig.hexdigest()
# compare the returned signature with the one client sent us...
只要我将用户的密钥存储在我的数据库中作为纯文本,所有这一切都可以正常工作:
| username | key |
------------------------------------
| THE_USER_NAME | SUPER_SECRET_KEY |
我们都知道这是绝对不可接受,因此我尝试使用SUPER_SECRET_KEY
简单地散列bcrypt
并存储散列字符串:
| username | key |
--------------------------------------------------------------------------------
| THE_USER_NAME | $2b$12$UOIKEBFBedbcYFhbXBclJOZIEgSGaFmeZYhQtaE4l6WobFW1qvIf6 |
我现在面临的问题是客户端使用未散列版本的“密钥”来生成我无法在服务器端执行的签名,因为我不再使用纯文本了。
类似方法的一个示例是generating HMAC signature in Amazon Web Services(也是simplified explanation of the same process),它不需要任何额外的登录或身份验证,也不提供密钥的任何令牌或“替换”/秘密组合。我真的怀疑AWS是将秘密存储在数据库中的纯文本中吗?(?)
如何在数据库中使用散列版本的“密钥”在服务器端重新创建HMAC签名,同时不强制客户端更改其签名生成方法(例如,避免安装bcrypt
甚至一点都没有秘密?
答案 0 :(得分:1)
密码哈希不使用共享密钥。散列秘密的行为应该破坏实际价值,同时保留验证密码的能力。您无法合理地期望从哈希中恢复密码。
Hmac身份验证和验证使用共享密钥。双方都必须知道这个秘密。
因此,密码散列与hmac根本不同,您不能简单地散列hmac密钥。哈希将不允许您回到实际密钥。
[澄清后删除不相关的部分]
所以你必须在某个地方有某种秘密,但它不需要在数据库中。可以使用对称密码(使用不在数据库中的不同密钥)在数据库中加密实际的hmac共享密钥。因此,服务器读取加密的hmac密钥,对其进行解密并使用它。
重要的是你必须以某种方式加密它,你可以解密,并排除散列。