生成新 API 密钥的最佳方法是啥?

Posted

技术标签:

【中文标题】生成新 API 密钥的最佳方法是啥?【英文标题】:What's the best approach for generating a new API key?生成新 API 密钥的最佳方法是什么? 【发布时间】:2013-01-02 22:38:01 【问题描述】:

所以现在有很多不同的服务,Google API、Twitter API、Facebook API 等等。

每个服务都有一个 API 密钥,例如:

AIzaSyClzfrOzB818x55FASHvX4JuGQciR9lv7q

所有密钥的长度和它们包含的字符都不同,我想知道生成 API 密钥的最佳方法是什么?

我不是要求一种特定的语言,只是创建密钥的一般方法,它们应该是对用户应用程序细节的加密,还是散列,或随机字符串的散列等。我们应该担心关于哈希算法(MSD、SHA1、bcrypt)等?

编辑: 我和几个朋友谈过(电子邮件/推特),他们建议只使用去掉破折号的 GUID。

这对我来说似乎有点 hacky,希望能得到更多的想法。

【问题讨论】:

我已经在这里详细回答了。generating keys and using it as hmac auth 【参考方案1】:

使用专为密码学设计的随机数生成器。然后base-64编码数字。

这是一个 C# 示例:

var key = new byte[32];
using (var generator = RandomNumberGenerator.Create())
    generator.GetBytes(key);
string apiKey = Convert.ToBase64String(key);

【讨论】:

这不是很安全,访问您的数据库的攻击者可以获得密钥。最好将密钥生成为用户独有的哈希值(如盐),并结合服务器机密。 存储随机生成的 API 密钥与存储散列密码具有相同的安全特性。在大多数情况下,这很好。正如您所建议的,可以将随机生成的数字视为盐并使用服务器机密对其进行散列;但是,这样做会在每次验证时产生散列开销。如果不使所有 API 密钥失效,也无法使服务器机密失效。 不错的解决方案,但您可能需要在 apiKey 之前使用 var 关键字:) var apiKey = Convert.ToBase64String(key); @JamesWierzba 如果被攻击者已经在您的数据库中,那么他们对您的 API 的不安全访问可能是您最不关心的问题... @EdwardBrey 的特征不太一样。使用 API 密钥读取数据库的人现在拥有一个有效的 API 密钥。读取散列密码的人不能使用该散列作为密码。【参考方案2】:

API 密钥需要具备以下属性:

唯一标识授权 API 用户——“API 密钥”的“密钥”部分 验证该用户 - 无法猜测/伪造 如果用户行为不端,可以撤销 - 通常他们键入可以删除记录的数据库。

通常您将拥有数千或数百万个 API 密钥,而不是数十亿,因此它们不需要:

可靠地存储有关 API 用户的信息,因为这些信息可以存储在您的数据库中。

因此,生成 API 密钥的一种方法是获取两条信息:

    保证唯一性的序列号 足够的随机位来填充密钥

并使用私人秘密对其进行签名。

计数器保证他们唯一标识用户,并且签名防止伪造。可撤销性要求在执行任何需要 API 密钥授权的操作之前检查密钥在数据库中是否仍然有效。

如果您需要从多个数据中心生成密钥,或者没有其他好的分布式方式来分配序列号,那么一个好的 GUID 生成器是递增计数器的一个很好的近似值。


或随机字符串的散列

散列并不能防止伪造。 Signing 保证密钥来自您。

【讨论】:

如果根据提供 API 的服务器上已注册 API 密钥的数据库检查客户端提供的 API 密钥,您的算法的签名步骤是否必要?如果服务器是提供密钥的服务器,似乎在这里签名将是多余的。 @sappenin,是的。如果您在服务器上存储了一个不可猜测的密钥,那么您就不需要防止伪造。 API 请求通常由一组机器中的任何一台处理—— 服务器是众多服务器之一。可以在任何机器上进行签名检查,而无需往返数据库,这可以避免在某些情况下出现竞争条件。 @MikeSamuel 如果 API 密钥已签名并且您没有往返数据库,那么当密钥被撤销但仍用于访问 API 时会发生什么? @AbhyuditJain,在任何分布式系统中,您都需要一致的消息顺序(撤销happen-before 随后使用已撤销凭据)或其他方式来约束歧义。有些系统不会对每个请求进行往返——如果一个节点缓存了一个密钥在数据库中存在 10 分钟的事实,那么只有 10 分钟。攻击者可以在其中滥用已撤销凭据的窗口。但是可能会导致混淆:用户撤销一个凭证,然后测试它是否被撤销,并且感到惊讶,因为非粘性会话导致两个请求转到不同的节点。【参考方案3】:

我使用 UUID,格式为小写,不带破折号。

生成很容易,因为大多数语言都内置了它。

API 密钥可能会被泄露,在这种情况下,用户可能希望取消其 API 密钥并生成一个新的,因此您的密钥生成方法必须能够满足这一要求。

【讨论】:

不要假设 UUID 很难猜;它们不应用作安全功能(UUID 规范 RFC4122 section 6)。 API 密钥需要一个安全的随机数,但 UUIDs are not securely unguessable. @EdwardBrey Java 中的UUID uuid = UUID.randomUUID(); 怎么样?你是说随机还不够好? @MicroR 随机 UUID 仅在用于使其具有加密安全性且 128 位足够的随机数生成器时才是安全的。尽管 UUID RFC 不需要安全的随机数生成器,但给定的实现可以免费使用。对于randomUUID,API 文档特别声明它使用“加密强伪随机数生成器”。因此,对于 128 位 API 密钥,该特定实现是安全的。【参考方案4】:

如果您想要一个只有字母数字字符的 API 密钥,您可以使用 base64-random 方法的变体,只使用 base-62 编码。 base-62 编码器基于this。

public static string CreateApiKey()

    var bytes = new byte[256 / 8];
    using (var random = RandomNumberGenerator.Create())
        random.GetBytes(bytes);
    return ToBase62String(bytes);


static string ToBase62String(byte[] toConvert)

    const string alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    BigInteger dividend = new BigInteger(toConvert);
    var builder = new StringBuilder();
    while (dividend != 0) 
        dividend = BigInteger.DivRem(dividend, alphabet.Length, out BigInteger remainder);
        builder.Insert(0, alphabet[Math.Abs(((int)remainder))]);
    
    return builder.ToString();

【讨论】:

【参考方案5】:

API 密钥应该是一些随机值。随机到无法预测。它不应包含其所针对的用户或帐户的任何详细信息。如果您确定创建的 ID 是随机的,则使用 UUID 是个好主意。

例如,早期版本的 Windows 产生可预测的 GUID,但这是一个老故事。

【讨论】:

Windows 2000 使用 random numbers 切换到 GUID。但是,不能保证随机数无法预测。例如,如果攻击者为自己创建了多个 API 密钥,则有可能确定未来用于生成另一个用户的 API 密钥的随机数。一般来说,do not consider UUIDs to be securely unguessable.

以上是关于生成新 API 密钥的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

对于使用 Facebook iOS SDK 的混合 SSO 场景,为我们自己的自定义用户记录生成密码/密钥的最佳方式是啥?

生成 Android 房间迁移的最佳方法是啥?

从 bson 结构转换 protoc 生成的结构的最佳方法是啥?

在反应应用程序中保护 Firebase API 密钥以使其无法公开访问的最佳方法是啥? [复制]

为 RESTful/HTTP RPC API 记录/生成参考的最佳工具是啥? [关闭]

使用 Keycloak 时模拟“生成 API 令牌”的最佳实践