我正在尝试实现签名URL,以实现对静态文件的短期访问。想法是:
- 生成带有过期时间戳的URL(例如
https://example.com/file.png?download=false&expires=1586852158
( - 使用HMACSHA256和共享密钥对其进行签名,并将签名附加在URL的末尾(例如
https://example.com/file.png?download=false&expires=1586852158&signature=6635ea14baeeaaffe71333cf6c7fa1f0af9f6cd1a17abb4e75ca275dec5906d1
当我在服务器上收到请求时,我取出signature
参数,并验证用HMACSHA256和相同共享密钥签名的URL的其余部分是否会产生相同的签名。
实现如下:
public static class URLSigner
{
private static string GetSignatureForUri(string uri, byte[] key)
{
var hmac = new HMACSHA256(key);
var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(uri));
var hexSignature = BitConverter.ToString(signature).Replace("-", string.Empty).ToLowerInvariant();
return hexSignature;
}
public static string SignUri(string uri, byte[] key)
{
var hexSignature = GetSignatureForUri(uri, key);
return QueryHelpers.AddQueryString(uri, new Dictionary<string, string> { { "signature", hexSignature }});
}
public static bool VerifyUri(string uri, byte[] key)
{
var signatureRegex = "[\?&]signature=([a-z0-9]+)$";
var signatureMatch = Regex.Match(uri, signatureRegex);
if (!signatureMatch.Success || signatureMatch.Groups.Count != 2)
return false;
var parsedSignature = signatureMatch.Groups[1].Value;
var originalUri = Regex.Replace(uri, signatureRegex, "");
var hexSignature = GetSignatureForUri(originalUri, key);
return hexSignature == parsedSignature;
}
}
它是这样使用的:
var signedUri = URLSigner.SignUri("https://example.com/file.png?download=false", secretKey);
var isVerified = URLSigner.VerifyUri(signedUri, secretKey);
这种已签名URL的实现是否合理安全?
您的实现似乎缺少对过期时间的验证,因此任何一个密钥当前都可以无限期地工作。
除此之外,我认为这种方法总体上没有任何问题。不过,您可能希望在时间戳之外添加一个密钥,以便以某种方式识别用户或请求。
这是一篇关于如何将通用方法用于一次性密码的好文章,这基本上就是您正在做的。
https://www.freecodecamp.org/news/how-time-based-one-time-passwords-work-and-why-you-should-use-them-in-your-app-fdd2b9ed43c3/
是的,只要密钥处理得当,它是安全的。散列应该能够确保数据的完整性(URL中的数据不会被其他人修改(。
也许,一个小的改进是dispose
HMACSHA256
对象(可能是using
(,但这可能与安全性无关。
我有一个问题。您是说要使用HMACSHA256和私钥,但在安全术语中,您传递给HMAC的不是私钥,而是共享密钥。
如果您必须拥有用于签名和验证身份验证的公钥和私钥,我建议使用RSACryptoServiceProvider
。使用RSA,您有两个密钥,公钥和私钥。
您的客户端创建一个私钥并保存它,然后将其公钥提供给服务器。所以只有客户端可以签名,任何拥有公钥的人都可以验证
另一方面,无论您最终使用了什么算法,我都建议将签名添加到授权标头中,而不是添加到查询字符串中。这更常见,您不需要在每个请求中匹配正则表达式。