工作笔记 - NATS的Nkey认证

概述

笔者在最近使用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应用中的实现和操作。

相关推荐
_Kayo_4 小时前
VUE2 学习笔记14 nextTick、过渡与动画
javascript·笔记·学习
九伯都4 小时前
rabbitmq的安装和使用-windows版本
windows·分布式·rabbitmq
_oP_i4 小时前
RabbitMQ 队列配置设置 RabbitMQ 消息监听器的并发消费者数量java
java·rabbitmq·java-rabbitmq
咔咔一顿操作5 小时前
Vue 3 入门教程7 - 状态管理工具 Pinia
前端·javascript·vue.js·vue3
漂流瓶jz6 小时前
JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
前端·javascript·编译原理
努力的小雨6 小时前
还在为调试提示词头疼?一个案例教你轻松上手!
后端
魔都吴所谓7 小时前
【go】语言的匿名变量如何定义与使用
开发语言·后端·golang
陈佬昔没带相机7 小时前
围观前后端对接的 TypeScript 最佳实践,我们缺什么?
前端·后端·api
落雪小轩韩8 小时前
Vue常见题目
javascript·vue.js