输出漂亮的 API 密钥的开源工具

API 密钥是用户与我们产品首次互动的重要组成部分,我们希望给用户留下良好的印象。我们希望我们的密钥看起来不错,感觉也不错,但业内似乎没有“好”的标准。
我们是一家忙碌的初创公司,但我们也是一家以开发者为先的平台公司。因此,我们认为花一些时间、精力和心思来找到一个我们(希望我们的开发者)都会满意的解决方案是有意义的。

我们列出了 API 密钥的要求:

  • 安全的
  • 全球独一无二
  • 可排序
  • Postgres 中的性能
  • 看起来不错
不幸的是,大多数 API 密钥都很丑陋。它们通常只是格式不一致的随机字符串,很难阅读、排序和识别。

由于没有一个选项足够美观(对称),所以我们创造了自己的方法:

  1. 使用 UUIDv7 作为基本 ID 来利用时间戳
  2. 使用 Crockford Base32 对 ID 进行编码以提高可读性
  3. 添加巧妙放置的破折号以增添美感
结果:
key, _ := uuidkey.Encode("d1756360-5da0-40df-9926-a76abff5601d")
fmt.Println(key)
// Output: 38QARV0-1ET0G6Z-2CJD9VA-2ZZAR0X

特点:

  • 31 个字符(不含破折号为 28 个) vs UUID 为 36 个
  • 高度可读的段落,包含 4 组 7 个大写字母和数字,具有“块状”美感和可读性
  • 解码为 UUID 存储时可按时间顺序排序
  • 面向用户的密钥中存在模糊的时间戳(但精明的用户仍然可以对其进行解码)。我们发现密钥中的时间戳元数据是一个优势,您可以随时使用 UUIDv4 来代替!你做到了!


我们制作了自己的密钥包  uuidkey :

  • 您可以使用它将 UUID编码和格式化为人类可读的密钥。
  • 如果您使用UUIDv7,您还可以解码密钥以将它们存储为可排序、可索引的 ID 在您的数据库中。

为什么选择 UUIDv7 
除了时间戳优势之外,UUIDv7 还将在 v18 3中获得原生 Postgres 支持。虽然目前可以使用扩展在服务器端生成 UUIDv7,但原生 Postgres 支持的性能肯定会更高4,并且可以很好地传递给uuidkey.Encode()。

对于我们的实现,我们目前在应用程序层生成密钥并将其存储为 UUID,以进行排序和索引。一旦 Postgres v18 发布,我们将切换到 Postgres 生成,以从我们的应用程序层卸载该数据流并获得略微更好的性能。

为什么选择 Crockford Base32 
我们选择Crockford Base32编码是因为它:

  • 仅使用大写字母和数字,提高可读性
  • 将密钥长度缩短约 1/5
  • 制图高效且可预测
  • 是所有酷孩子都在用的东西

为什么选择 Dashes
由此产生的虚线键是“块状”且对称的。如果您将单个字符变灰,它们看起来几乎像条形码。我们认为它可以轻松快速地读取键的部分内容以进行识别。
我们可能在潜意识中受到了老式产品 CD 密钥的启发。

破折号确实消除了简单的双击复制,但我们认为这对可读性来说是一个很好的权衡。我们不希望用户到处复制和粘贴它们,事实上我们希望小心处理它们。理想情况下,用户只复制每个密钥一次 - 当他们从我们的仪表板生成密钥时 - 因此我们在用户界面中添加了一个复制按钮来解决这种情况。

我们在github.com/agentstation/uuidkey上开源了这些设计选择。如果您认同我们的美学、推理、对称性,并想要拥有自己漂亮的 API 密钥,欢迎您试用我们的开源项目。
该软件包的核心是通过 Base32-Crockford 编解码器uuidkey将 UUID 编码为可读Key格式,然后将其解码回 UUID。

// Encode will encode a given UUID string into a Key with basic length validation.
func Encode(uuid string) (Key, error) {
    if len(uuid) != UUIDLength {
// basic length validation to ensure we can encode
        return
"", fmt.Errorf("invalid UUID length: expected %d characters, got %d", UUIDLength, len(uuid))
    }

    
// select the 5 parts of the UUID string
    s1 := uuid[0:8]  
// [d1756360]-5da0-40df-9926-a76abff5601d
    s2 := uuid[9:13]  
// d1756360-[5da0]-40df-9926-a76abff5601d
    s3 := uuid[14:18]
// d1756360-5da0-[40df]-9926-a76abff5601d
    s4 := uuid[19:23]
// d1756360-5da0-40df-[9926]-a76abff5601d
    s5 := uuid[24:36]
// d1756360-5da0-40df-9926-[a76abff5601d]

    
// decode each string part into uint64
    n1, _ := strconv.ParseUint(s1, 16, 32)
    n2, _ := strconv.ParseUint(s2+s3, 16, 32)    
// combine s2 and s3
    n3, _ := strconv.ParseUint(s4+s5[:4], 16, 32)
// combine s4 and the first 4 chars of s5
    n4, _ := strconv.ParseUint(s5[4:], 16, 32)    
// the last 8 chars of s5

    
// encode each uint64 into base32 crockford encoding format
    e1 := encode(n1)
    e2 := encode(n2)
    e3 := encode(n3)
    e4 := encode(n4)

    
// build and return key
    return Key(e1 +
"-" + e2 + "-" + e3 + "-" + e4), nil
}

解锁:

// Decode will decode a given Key into a UUID string with basic length validation.
func (k Key) Decode() (string, error) {
    if len(k) != KeyLength {
// basic length validation to ensure we can decode
        return
"", fmt.Errorf("invalid Key length: expected %d characters, got %d", KeyLength, len(k))
    }

    
// select the 4 parts of the key string
    key := string(k)
// convert the type from a Key to string
    s1 := key[0:7]  
// [38QARV0]-1ET0G6Z-2CJD9VA-2ZZAR0X
    s2 := key[8:15]  
// 38QARV0-[1ET0G6Z]-2CJD9VA-2ZZAR0X
    s3 := key[16:23]
// 38QARV0-1ET0G6Z-[2CJD9VA]-2ZZAR0X
    s4 := key[24:31]
// 38QARV0-1ET0G6Z-2CJD9VA-[2ZZAR0X]

    
// decode each string part into original UUID part string
    n1 := decode(s1)
    n2 := decode(s2)
    n3 := decode(s3)
    n4 := decode(s4)

    
// select the 4 parts of the decoded parts
    n2a := n2[0:4]
    n2b := n2[4:8]
    n3a := n3[0:4]
    n3b := n3[4:8]

    
// build and return UUID string
    return (n1 +
"-" + n2a + "-" + n2b + "-" + n3a + "-" + n3b + n4), nil
}

该软件包旨在与遵循官方 UUID 规范(RFC 4122)的任何 UUID 配合使用,但我们专门测试并保持与两个最流行的 UUID Go 生成器的兼容性:

安装非常简单:

go get github.com/agentstation/uuidkey

基本用法:

key, _ := uuidkey.Encode("d1756360-5da0-40df-9926-a76abff5601d")
fmt.Println(key)
// 38QARV0-1ET0G6Z-2CJD9VA-2ZZAR0X

我们尽力将开销降至最低:

BenchmarkValidate-12           33527211    35.72 ns/op
BenchmarkParse-12              32329798    36.96 ns/op
BenchmarkEncode-12             3151844     377.0 ns/op
BenchmarkDecode-12             5587066     216.7 ns/op