概述
笔者在最近使用NATS系统的时候,看到其提供了一种新的认证方式-Nkey。经过一段时间的研究和实践,觉得这是一个很好的技术方案,也有很合适的使用场景,值得研究和应用。
NKey
什么是NKey?
nkey是NATS生态中(2.0+)的一种更现代化安全机制,用于身份验证和资源授权,它的基本特点是:
- 基于Ed25519算法
这是一种椭圆曲线(ECC)密码学算法,可以提供更强的加密安全性,和更快的签名和验证操作。
- 自描述性
NKey的密钥本身包含类型信息,通过前缀标识不同用途。
- 无状态验证
Nkey体系不需要中央密钥存储,支持分布式验证,这为在自有业务应用程序中使用提供了更好的灵活性。按照其官方说法,NKey是令牌身份验证的绝佳替代品,因为连接客户端必须证明它控制授权公钥的私钥。
和传统的基于账号/密码的认证方式相比,Nkey基于非对称加密的基本原理,完美的解决了需要在两端共享机密信息的问题。从而能够提供更高的安全性。
基于Nkey技术,NATS生态系统将迁移到Ed25519密钥,用于为帐户、用户、服务器和群集等实体提供身份识别、验证和授权等操作。Ed25519速度比较快,并可以抵抗侧信道攻击。NATS系统使用Ed25519密钥,这意味着NATS系统本身不会存储甚至访问任何私钥,身份验证将利用随机挑战响应机制。在这个体系中,种子密钥的生成是存储和保持安全所需的全部,因为种子衍生出公钥和私钥。
和比特币使用Base58不同,Nkey使用Stellar的技术方案来编码密钥。它使用一个更传统的带有CRC16的Base32编码,加一个类型前缀作为编码格式。好处是人类可以很方便的检查密钥类型。如Nkey的密钥类型(密钥信息前缀)包括:
- O - Operator (操作员): 最高级别,管理整个NATS系统
- A - Account (账户): 管理一组用户和权限
- U - User (用户): 终端用户身份
- N - Server (服务器): NATS服务器身份
- C - Cluster (集群): 集群间通信身份
- S - Seed (种子): 私钥种子,以S开头,而且用第二个字符标识公钥类型,如U用户,A账号
基本原理和流程
在开始之前,笔者觉得可以先来了解一下NATS中应用NKey的基本原理和方式。
在NATS中,创建Nkey标识时,需要使用命令行工具,按照Nkey的标准规范,会生成一个用户标识(公钥)和种子(私钥)。私钥由客户端保存,在后续认证时使用;公钥由服务器管理员配置到认证信息当中。可以看到,这里的核心,就是使用公钥来作为用户的标识(而非常规意义上的用户名)。
客户端连接服务器时,需要使用这个公钥作为用户请求登录;服务端检查确认这个公钥在认证信息中存在后,会向客户端响应一个随机生成的挑战信息(nonce);客户端随后使用种子(Seed,其实就是私钥)对这个信息进行签名计算,并再次将签名结果提交给服务端;然后服务端使用Nkey(公钥)对这个挑战信息和签名来进行验证,验证通过后,就可以建立连接了。这就是所谓的"挑战/响应"的认证机制。
由此,我们也可以更容易的理解,Nkey更适合于应用到应用集成这种客户端环境相对固定的使用场景当中,因为需要有一个稳定的环境,来安全的保存和使用认证标识的私钥。
我们随后来了解一下相关的具体操作和开发方面的内容。相关的操作和编码环境是标准Linux操作系统和JS/Bun语言环境。
NKey密钥
首先是Nkey密钥的生成。理论上所有满足Nkey标准的工具和程序都可以完成这个操作。比如nk命令行工具,或者相关的程序代码。
先来看看nk,这是一个官方的命令行工具。nk本来就是一个简单的Go程序,但不知道什么原因,它没有提供直接的二进制版本下载,而是需要使用go来进行安装。
shell
// 安装go
sudo apt install golang
go version
// 安装nk
sudo go install github.com/nats-io/nk
which nk
/usr/local/bin/nk
// nk使用方式
nk
Usage: nk [options]
-v Show version
-gen <type> Generate key for [user|account|server|cluster|operator|curve|x25519]
-sign <file> Sign <file> with -inkey <keyfile>
-verify <file> Verfify <file> with -inkey <keyfile> or -pubin <public> and -sigfile <file>
-inkey <file> Input key file (seed/private key)
-pubin <file> Public key file
-sigfile <file> Signature file
-pubout Output public key
-e Entropy file, e.g. /dev/urandom
-pre <vanity> Attempt to generate public key given prefix, e.g. nk -gen user -pre derek
-maxpre <N> Maximum attempts at generating the correct key prefix, default is 10,000,000
// 生成认证信息
nk -gen user -pubout
SUANRGRUCB2N3QJMHYXLNB5BEN4GBC44BSXS7OFGLLBPLUVAMBOYWXSLLI
UCA6GYTLZPSLAO4F75XTL2V5GFVGSF2AMIKW24ENY63HK5RCURSYPSUU
安装的结果就是一个可执行文件和命令。在本文中,这个命令主要就是用来生成一个NKey用户标识(其实就是公钥)和对应的种子(Seed,其实就是私钥)。
除了命令行工具之外,开发者也可以基于软件包进行编程,使用代码生成这些认证信息。
js
const
{ createUser, fromSeed, fromPublic } = require("@nats-io/nkeys");
// create an user nkey KeyPair (can also create accounts, operators, etc).
const user = createUser();
// A seed is the public and private keys together.
const seed: Uint8Array = user.getSeed();
// Seeds
console.log("seed", new TextDecoder().decode(seed));
// public key
const publicKey = user.getPublicKey();
console.log("public key", publicKey);
// 执行结果
seed SUALEFTFF7LVPTF56SUWAMFV3KMRF2WDW46KK2ZLIZG42CJ3XNBPXIJUJY
public key UCWNAOJWZEJPXT2VVOHKUX4EYJGTLHBX5VNBP6IRAR6ZPC6HGBEW6WFS
可以看到,这里主要使用mats官方的nkeys库。这一过程就是先创建一个nkey用户,就可以获取其种子和公钥,并将其转换成文本信息,来本地保存和提交给服务方。而且在这里,其实用户名不是重要的,因为Nkey公钥,本身就是一个有效的标识,再结合授权的操作,完全可以替代传统的用户/密码的认证和授权体系。
NATS服务配置
Nkey密钥生成后,需要配置到NATS服务器当中。例如下面的配置信息:
n.conf
# 客户端认证
authorization {
timeout: 5
users : [
{ nkey: "UBNL...", permissions: { publish: ["*"], subscribe: ["*"] }},
{ nkey: "UA4P..."},
{ user: "testuser", password: "testpassword" }
]
}
这里可以看到,nats服务的配置信息,支持同时配置nkey和用户的方式。也可以在配置nkey的同时,配置其授权信息。
客户端连接
如果使用nkey进行客户端连接,核心是配置连接信息中的authenticator:
js
import { connect, nkeyAuthenticator } from 'nats';
import { fromSeed } from '@nats-io/nkeys';
const UNKEY = "UA...";
const SEED = "SU...";
try {
// 连接NATS
const nc = await connect({
servers: "nats://192.168.9.194:4222",
authenticator: nkeyAuthenticator(new TextEncoder().encode(SEED)),
debug: true
});
console.log("连接成功!");
} catch (error) {
console.error("连接失败:", error);
}
这里有几个要点:
- 使用的是nkey比较官方的 @nats-io/nkeys 库
- 使用连接信息的authenticator,需要执行nats库的nkeyAuthenticator方法
- 方法的参数,就是nkey的seed,类型是Uint8Array,Buffer应当也可以
- 这个方法调用时,应该是要注入服务器响应的nonce作为签名依据的
- 这个连接信息中,并不需要Nkey公钥!(因为可以从seed计算得到)
注意这里有一个坑!
在较老的客户端版本中,nkey是通过配置nkey属性和认证回调方法来实现的(甚至官方文档提供的示例也是如此),但笔者的实践表明,可能是由于版本限制的问题,这个方式已经无法使用了,表现就是认证超时。所以需要改进为前面代码中,配置authenticator的方式。
js
nkey: 'UDXU...你的公钥...',
sigCB: nonce => {
return nkeys.fromSeed(Buffer.from(SEED)).sign(nonce)
}
这个老的版本虽然笔者没有执行成功,但它确实让我们更容易理解nkey认证的过程和原理。
小结
本文作为NATS技术的扩展内容,简单介绍了在NATS中应用的一种比较新型的认证方式:Nkey。探讨了其基本技术原理和流程,并结合nats的配置和认证过程,介绍了在nats应用中的实现和操作。