hardhat-keystore 插件全解析:为 Hardhat 配置变量打造加密存储

hardhat-keystore 插件全解析:为 Hardhat 配置变量打造加密存储 hardhat-keystore 插件全解析为 Hardhat 配置变量打造加密存储【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat本文以仓库内 packages/hardhat-keystore/README.md 为核心骨架结合该包的完整源码、测试与错误定义进行纵深讲解。你将掌握如何安装并启用nomicfoundation/hardhat-keystore插件keystore系列 CLI 任务的完整用法与参数语义生产 keystore 与开发 keystore 的差异及文件落盘位置以及 scrypt 密钥派生、AES-GCM-SIV 数据加密、HMAC-SHA-256 完整性校验的底层加密原理。读完即可在自己的 Hardhat 项目Hardhat 3.xpeerDependencies 为hardhat^3.13.0中安全地托管 API Key、私钥等敏感配置。一、插件定位把机密从配置文件和明文环境变量中解放出来Hardhat 项目在配置中经常需要用到 API Key如 Etherscan 验证密钥、Alchemy/Infura RPC 密钥和私钥。传统做法要么直接写死在hardhat.config.ts里一旦提交到 Git 就会泄露要么依赖.env文件仍是明文落盘。nomicfoundation/hardhat-keystore插件的目标就是提供一个加密的 keystore用来在配置中安全地处理这些机密值。包描述原文为A module for managing keystore files that store a map from IDs to encrypted string values.见 packages/hardhat-keystore/package.json即一个管理 keystore 文件的模块其中存储着ID → 加密字符串值的映射。机密值以加密形式保存在磁盘上只有持有密码时才能解密读取。与两个 Toolbox 的关系该插件已内置于两个官方 Toolbox 中Viem Hardhat Toolbox对应仓库packages/hardhat-toolbox-viemEthers Mocha Hardhat Toolbox对应仓库packages/hardhat-toolbox-mocha-ethers如果你已经在用其中任何一个 Toolbox那么无需额外安装插件已经随 Toolbox 一并引入。只有独立使用不通过 Toolbox时才需要手动安装。安装与启用npm install --save-dev nomicfoundation/hardhat-keystore在hardhat.config.ts中导入插件并注册到plugins数组import { defineConfig } from hardhat/config; import hardhatKeystore from nomicfoundation/hardhat-keystore; export default defineConfig({ plugins: [hardhatKeystore], });从插件入口 packages/hardhat-keystore/src/index.ts 可以看到插件通过definePlugin注册了两类能力hookHandlersconfig与configurationVariables两个 hook 处理器分别负责向 Hardhat 配置注入 keystore 路径、以及接管配置变量的取值逻辑tasks一组以keystore为命名空间的 CLI 任务set/get/list/delete/rename/path/change-password外加一个空的父任务keystore描述为 Store keys in an encrypted storage。二、keystore 生命周期从首次 set 到日常读写的完整流程插件把机密的管理抽象成一条清晰的流水线对应源码中的三个核心职责模块见 packages/hardhat-keystore/src/internal/types.tsKeystoreLoader负责从磁盘加载/保存 keystore校验磁盘文件结构并通过内存缓存降低 IO 开销FileManager底层文件读写抽象fileExists/writeJsonFile/readJsonFileKeystore内存中的 keystore 对象封装listUnverifiedKeys、hasKey、addNewValue、removeKey、readValue、isValidPassword等操作。具体到文件加载器 packages/hardhat-keystore/src/internal/loaders/keystore-file-loader.tsKeystoreFileLoader使用#keystoreCache缓存已加载的 Keystore 实例isKeystoreInitialized()先查缓存缓存为空则检查 keystore 文件是否存在loadKeystore()缓存命中直接返回否则读取磁盘 JSON 文件并包装成Keystore实例createUnsavedKeystore()仅当缓存为空即 keystore 尚未初始化时创建全新的空 keystoresaveKeystoreToFile()将内存中的EncryptedKeystore序列化写回磁盘。首次初始化keystore set的分支逻辑以set任务源码见 packages/hardhat-keystore/src/internal/tasks/set.ts为例首次使用与日常使用的路径截然不同const isKeystoreInitialized await keystoreLoader.isKeystoreInitialized(); const password isKeystoreInitialized ? await askPassword() : await setUpPassword(); if (isKeystoreInitialized false) { await keystoreLoader.createUnsavedKeystore(createMasterKey({ password })); }keystore 尚未初始化进入设置密码流程setUpPassword用密码派生 master key 并创建空 keystorekeystore 已存在直接询问密码askPassword并用密码从已有 keystore 中重新派生 master key 解锁。三、keystore命令族七个任务的参数、语义与输出所有任务均挂在keystore命名空间下定义于 packages/hardhat-keystore/src/index.ts 的插件tasks数组中。绝大多数任务共享一个--dev标志用于切换开发 keystore详见第四节。1.npx hardhat keystore set key设置新增或覆盖一个密钥是初始化 keystore 的唯一入口list/get等任务在 keystore 不存在时会提示先执行keystore set对应displayNoKeystoreSetErrorMessage的消息No production keystore found. Please set one up using npx hardhat keystore set {key}。参数与行为源自 src/index.ts 与 tasks/set.ts参数/标志类型说明key位置参数STRING要存储的密钥名。必须以字母或下划线开头仅允许字母、数字、下划线正则^[a-zA-Z_][a-zA-Z0-9_]*$见 validate-key.ts。非法键名会打印红色错误并设置process.exitCode 1--force布尔标志键已存在时强制覆盖不加该标志键已存在则报错退出--dev布尔标志使用开发 keystore 而非生产 keystore交互流程如果 keystore 未初始化先要求设置密码密码需 ≥8 个字符校验正则^.{8,}$见 password.ts且需二次输入确认两次不一致会以红色提示重新输入随后以交互方式输入要存储的机密值空值会被拒绝提示 The value cannot be empty.最后写入并落盘输出 Key set in the production keystore。2.npx hardhat keystore get key按 key 读取并打印解密后的明文值。支持--dev。注意命令行get任务会直接向终端输出机密值适合调试生产场景更推荐通过配置变量机制第五节读取避免机密出现在 shell 历史或日志中。3.npx hardhat keystore list列出 keystore 中的所有 key不显示值。支持--dev。输出形如Keys in the production keystore: MY_API_KEY DEPLOYER_PRIVATE_KEY若 keystore 为空则输出 The production keystore does not contain any keys.。实现上调用的是listUnverifiedKeys()——见 keystore.ts 中的注释该方法不校验 keystore 完整性返回的键名可能被篡改过因为仅用于展示用途时风险可接受。4.npx hardhat keystore delete key删除指定 key。支持--dev与--force两个标志--force的含义是删除时键不存在也不抛错任务描述Force to not throw an error if the key does not exist during deletion。删除成功输出 Key deleted from the production keystore。5.npx hardhat keystore rename oldKey newKey重命名一个 key接收两个位置参数oldKey与newKey。支持--dev与--force其中--force表示新键名已存在时强制覆盖。6.npx hardhat keystore path显示 keystore 文件的存储路径。支持--dev。用于快速确认当前使用的是哪个 keystore 文件。7.npx hardhat keystore change-password修改生产 keystore 的密码。仅支持生产 keystore——如果对开发 keystore 执行会抛出错误码 50001CANNOT_CHANGED_PASSWORD_FOR_DEV_KEYSTORE消息The keystore change-password task cannot be used with the development keystore见 descriptors.ts。流程要求先解锁当前密码再输入新密码同样要求 ≥8 字符并二次确认。四、生产 keystore 与开发 keystore双轨隔离设计这是本插件最具实用价值的设计之一。--dev标志背后的语义源自 password.ts 与 get-keystore-file-path.ts维度生产 keystore开发 keystore--dev存储文件全局配置目录下keystore.json全局配置目录下dev.keystore.json密码来源每次交互式输入自动生成 16 字节随机 hex 串明文写入hardhat.checksum文件解锁方式交互输入密码后经 scrypt 派生直接读取密码文件改密支持change-password不允许抛 50001 错误适用场景本地开发时保护真实密钥测试、CI 中需要自动化运行且不想交互的场景三个默认路径均由 config.ts 在resolveUserConfighook 中注入到hardhat.config的keystore字段类型扩展见 type-extensions.tsdeclare module hardhat/types/config { export interface HardhatConfig { keystore: { filePath: string; devFilePath: string; devPasswordFilePath: string; }; } }对应的路径计算逻辑在 get-keystore-file-path.tskeystore.json生产 keystoredev.keystore.json开发 keystorehardhat.checksum开发 keystore 的明文密码文件文件名略出乎意料但确实是源码中getDevKeystorePasswordFilePath返回的路径。setUpPasswordForDevKeystore用randomBytes(16).toString(hex)生成密码并写入该文件而askPasswordForDevKeystore则直接readUtf8File读回——因此开发 keystore 完全无需人工交互适合自动化与测试流水线。五、配置变量接入keystore 如何与 Hardhat 配置联动这是把 keystore 用起来的关键环节。插件通过configurationVariableshook 的fetchValue处理器源码见 configuration-variables.ts接管了配置变量的取值逻辑优先级设计为环境变量优先process.env[variable.name]已定义时直接交给下一个处理器环境变量 keystoreCI 中跳过 keystore通过isCi()检测在 CI 环境不初始化也不读取 keystore避免在 CI 中弹出密码交互转交默认解析先查开发 keystore尝试从开发 keystore 取值测试模式约束当process.env.HH_TEST true时只允许使用开发 keystore避免在测试中弹出生产 keystore 的密码输入——若开发 keystore 中没有该 key 且变量声明了默认值则回退默认否则抛出错误码 50002KEY_NOT_FOUND_DURING_TESTS_WITH_DEV_KEYSTORE消息提示Key {key} not found in the development keystore. Run npx hardhat keystore set {key} --dev to set it.最后查生产 keystore开发 keystore 无此 key 且非测试场景时读取生产 keystore此步会触发密码交互。内部实现上有两处值得注意的优化masterKey 缓存masterKeyProd/masterKeyDev在 hook 内缓存多次取配置变量时不会反复弹密码框未加锁检查对带默认值的变量会先通过listUnverifiedKeys()检查明文的键名不弹密码、不解锁 keystore命中才解锁读取。代码注释明确指出这是一个权衡跳过 HMAC 完整性校验因此被篡改key 被移除的 keystore 会被当作未命中回退默认值而不是报错。官方使用指南README 中链接的 configuration variables 指南说明了配置侧的用法在hardhat.config.ts中用variables声明配置变量可带默认值配合npx hardhat keystore set KEY存储机密即可在配置中引用而不暴露明文。仓库内的 e2e 示例e2e/fixture-projects/vars含hardhat.config.js、package.json、test.sh与packages/hardhat-keystore/test目录下的hook-handlers、tasks、keystores、loaders测试都是理解这一联动的可执行样例。六、加密原理scrypt AES-GCM-SIV HMAC-SHA-256 三层防护本插件把密码学算法集中在单一文件 packages/hardhat-keystore/src/internal/keystores/encryption.ts 中依赖noble/ciphers与noble/hashes见 package.json。文件结构EncryptedKeystore接口由version、crypto、dataEncryptionKey、hmacKey、hmac、secrets组成其中所有二进制缓冲区都以无0x前缀的 hex 字符串表示。6.1 密钥派生scrypt常量定义源码第 11–33 行export const KEYSTORE_VERSION hardhat-v3-keystore-1; export const PASSWORD_NORMALIZATION_FORM NFKC; export const KEY_DERIVATION_ALGORITHM scrypt; export const KEY_DERIVATION_PARAM_N 131_072; // 2^17内存约 128 MiB export const KEY_DERIVATION_PARAM_R 8; export const KEY_DERIVATION_PARAM_P 1; export const KEY_DERIVATION_SALT_LENGTH_BYTES 32; export const MASTER_KEY_LENGTH_BITS 256;参数N2^17、r8、p1注释明确标注了来源OWASP Password Storage Cheat Sheet 的 scrypt 建议参数并引用了 noble-hashes 的 README。密码在派生前会先做NFKCUnicode 规范化deriveMasterKey中password.normalize(NFKC)避免不同输入法/系统下的等价字符产生不同的 key。每次创建 keystore 都会生成 32 字节随机 saltsalt 随 keystore 持久化供后续从密码重新派生 master keyderiveMasterKeyFromKeystore。6.2 数据加密AES-GCM-SIVIV 碰撞容忍export const DATA_ENCRYPTION_ALGORITHM AES-GCM-SIV; export const DATA_ENCRYPTION_KEY_LENGTH_BITS 256; export const DATA_ENCRYPTION_IV_LENGTH_BYTES 12;选型原因在源码注释中写明AES-GCM-SIV 用于容忍 IV 碰撞相比普通 GCM 在 IV 复用上的灾难性后果更稳健。encryptUtf8String每次生成 12 字节随机 IV注释同时给出了一个安全边界假设随机 IV 只有 12 字节因此假设同一密钥下加密次数不超过 2^20 次——此时 IV 碰撞概率才达到 2^-57 量级。架构上采用双层密钥信封master key由密码经 scrypt 派生不直接加密每个 secret而是创建 keystore 时生成随机的dataEncryptionKey256 位和hmacKey256 位分别用 master key 加密后存入dataEncryptionKey与hmacKey字段每个 secret 使用dataEncryptionKey加密addSecretToKeystore/decryptSecret中先解出dataEncryptionKey再操作。这样即便某个 secret 被攻破也不会直接暴露 master key 派生的口令信息。6.3 完整性校验HMAC-SHA-256 确定性 JSON 序列化generateEncryptedKeystoreHmac用hmacKey对 keystore 的 JSON 序列化结果计算HMAC-SHA-256而validateHmac在每次读/写前校验防止密文被篡改。这里有个关键实现细节deterministicJsonStringify——通过自定义JSON.stringifyreplacer 对对象键按字典序排序后再序列化保证同一 keystore 在不同环境下产生完全一致的字节序列数组、null 等不受支持的类型会被拒绝并抛出UnsupportedTypeInDeterministicJsonError。HMAC 不匹配时抛出InvalidHmacErrorInvalid hmac in keystore。6.4 错误体系解密类错误被集中定义在该文件底部不依赖 Hardhat 全局错误体系保持模块自包含DecryptionError解密失败提示确认使用了正确的密码/密钥且加密数据未被损坏SecretNotFoundErrorkey 不存在于 keystoreHmacKeyDecryptionError解密 hmac key 失败通常意味着密码错误InvalidHmacErrorHMAC 校验失败数据被篡改。而面向用户的错误则映射到nomicfoundation/hardhat-errors的错误码体系见 packages/hardhat-errors/src/descriptors.ts错误码段50000–59999预留给hardhat-keystore错误码名称触发场景50000INVALID_PASSWORD_OR_CORRUPTED_KEYSTORE密码错误或 keystore 文件损坏Invalid password or corrupted keystore file.50001CANNOT_CHANGED_PASSWORD_FOR_DEV_KEYSTORE对开发 keystore 执行 change-password50002KEY_NOT_FOUND_DURING_TESTS_WITH_DEV_KEYSTORE测试模式下开发 keystore 中找不到 key其中 50000 在Keystore类的每个方法hasKey/readValue/removeKey/addNewValue/isValidPassword中都有统一捕获转换逻辑——凡是捕获到HmacKeyDecryptionError就包装成 50000 抛给用户见 keystore.ts。七、Keystore 接口类型层面的操作契约最后从类型视角梳理Keystore接口types.ts它定义了所有 keystore 操作的方法签名KeystoreFileLoader加载出的Keystore类keystore.ts即其实现export interface Keystore { listUnverifiedKeys(): Promisestring[]; hasKey(key: string, masterKey: Uint8Array): Promiseboolean; addNewValue(key: string, value: string, masterKey: Uint8Array): Promisevoid; removeKey(key: string, masterKey: Uint8Array): Promisevoid; readValue(key: string, masterKey: Uint8Array): Promisestring; isValidPassword(masterKey: Uint8Array): Promisevoid; toJSON(): EncryptedKeystore; }注意所有方法都显式接收masterKeyUint8Array作为参数且 master key 的注释强调该值可以安全地留在内存中见deriveMasterKeyFromKeystore的 JSDoc而解密出的 secret 则提醒不要长期保存在内存中见decryptSecret的 JSDoc——这是插件设计中对密钥生命周期管理的重要约定。结语nomicfoundation/hardhat-keystore为 Hardhat 3.x 提供了一个开箱即用、密码学严谨的机密管理方案CLI 层面用 7 个任务覆盖了 keystore 的完整生命周期配置层面通过configurationVariableshook 与现有变量机制无缝集成并通过--dev双轨设计兼顾了交互式开发与自动化测试两种场景。其底层选用 OWASP 推荐的 scrypt 参数、IV 碰撞容忍的 AES-GCM-SIV、以及 HMAC-SHA-256 完整性校验配合确定性 JSON 序列化与双层密钥信封结构构建了一套经得起推敲的加密存储实现。若要继续深入建议直接阅读 encryption.ts全部密码学核心、tasks 目录七个任务的实现以及 test 目录含fixture-projects的端到端测试样例。【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考