jedisct1/libsodium.js
GitHub: jedisct1/libsodium.js
将成熟的 libsodium 加密库通过 Emscripten 编译为 WebAssembly 和纯 JavaScript,为 Web 和 Node.js 应用提供高性能、全面的现代密码学原语。
Stars: 1132 | Forks: 162
# libsodium.js
## 概述
[sodium](https://github.com/jedisct1/libsodium) 加密库
使用 [Emscripten](https://github.com/emscripten-core/emscripten) 编译为 WebAssembly 和纯 JavaScript,
并带有自动生成的封装,使其易于在 Web 应用程序中使用。
标准版本大约 290 KB,sumo 版本大约 375 KB
(经压缩和 gzip 处理,包含纯 JS + WebAssembly 版本)。两者都可以在 Web 浏览器以及服务端运行。
### 兼容性
支持的浏览器/JS 引擎:
* Chrome >= 16
* Edge >= 0.11
* Firefox >= 21
* iOS >= 8.0 上的 Mobile Safari(较旧版本会产生不正确的结果)
* NodeJS
* Bun
* Opera >= 15
* Safari >= 6(较旧版本会产生不正确的结果)
这与 WebCrypto API 的兼容性相当,后者也兼容相似数量的浏览器。
签名和其他基于 Edwards25519 的操作与 [WasmCrypto](https://github.com/jedisct1/wasm-crypto) 兼容。
## 安装
[dist](https://github.com/jedisct1/libsodium.js/tree/master/dist) 目录包含预构建的脚本。将其子目录中的文件复制到您的应用程序中:
- [browsers](https://github.com/jedisct1/libsodium.js/tree/master/dist/browsers) 包含一个可以包含在网页中的单文件脚本。
它包含了常用函数的代码。
- [browsers-sumo](https://github.com/jedisct1/libsodium.js/tree/master/dist/browsers-sumo) 是前一个脚本的超集,包含所有函数,
包括极少使用的和未记录的函数。
- [modules](https://github.com/jedisct1/libsodium.js/tree/master/dist/modules) 包含常用函数,设计为作为 CommonJS 模块加载。
`libsodium-wrappers` 是您的应用程序应该加载的模块,它
随后会自动加载 `libsodium` 作为依赖项。
- [modules-sumo](https://github.com/jedisct1/libsodium.js/tree/master/dist/modules-sumo) 包含上述模块的 sumo 变体。
- [modules-esm](https://github.com/jedisct1/libsodium.js/tree/master/dist/modules-esm) 包含带有 `.mjs` 扩展名的 ESM(ES modules)版本。
- [modules-sumo-esm](https://github.com/jedisct1/libsodium.js/tree/master/dist/modules-sumo-esm) 包含 sumo 的 ESM 变体。
这些模块也可以在 npm 上获取:
- [libsodium-wrappers](https://www.npmjs.com/package/libsodium-wrappers)
- [libsodium-wrappers-sumo](https://www.npmjs.com/package/libsodium-wrappers-sumo)
### 使用方法(作为模块)
加载 `libsodium-wrappers` 模块。返回的对象包含一个 `.ready`
属性:这是一个 Promise,必须在 sodium 函数可用之前被 resolve。
示例:
```
import sodium from 'libsodium-wrappers';
await sodium.ready;
let key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
let { state: state_out, header } =
sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
let c1 = sodium.crypto_secretstream_xchacha20poly1305_push(state_out,
sodium.from_string('message 1'), null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE);
let c2 = sodium.crypto_secretstream_xchacha20poly1305_push(state_out,
sodium.from_string('message 2'), null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL);
let state_in = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key);
let r1 = sodium.crypto_secretstream_xchacha20poly1305_pull(state_in, c1);
let { message: m1_bytes, tag: tag1 } = r1;
let m1 = sodium.to_string(m1_bytes);
let r2 = sodium.crypto_secretstream_xchacha20poly1305_pull(state_in, c2);
let { message: m2_bytes, tag: tag2 } = r2;
let m2 = sodium.to_string(m2_bytes);
console.log(m1);
console.log(m2);
```
**命名导出:** ESM 模块也提供了辅助函数的命名导出:
```
import { ready, from_hex, to_hex, from_string, to_string } from 'libsodium-wrappers';
await ready;
const bytes = from_hex('deadbeef');
console.log(to_hex(bytes));
```
**注意:** 加密函数(如 `crypto_secretbox_easy`)和常量(如 `crypto_secretbox_KEYBYTES`)是在 `ready` 解决后,在运行时动态添加到模块中的。它们不能作为命名导出被导入,必须通过默认导出进行访问:
```
import sodium from 'libsodium-wrappers';
await sodium.ready;
// Now crypto functions and constants are available on the sodium object
const key = sodium.crypto_secretbox_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
```
### 使用方法(在 Web 浏览器中,通过回调)
`sodium.js` 文件包含核心的 libsodium 函数以及更高级的 JavaScript 封装。它可以被异步加载。
一个 `sodium` 对象应该在全局命名空间中定义,并包含以下属性:
- `onload`:封装初始化完成后调用的函数。
示例:
```
```
**重要提示:** 如果您直接将库内联到 HTML 中(而不是从单独的文件加载),请确保您的页面声明了 UTF-8 编码:
```
```
如果不这样做,浏览器可能会在 HTML 解析期间损坏嵌入的 WebAssembly 二进制数据,导致出现诸如 "failed to match magic number" 或 "HEAPU8 is undefined" 等错误。同时请确保您的服务器发送了 `charset=utf-8` 响应头。
## 其他辅助函数
* `from_base64()`、`to_base64()`,带有可选的第二个参数,
其值为以下之一:`base64_variants.ORIGINAL`、`base64_variants.ORIGINAL_NO_PADDING`、
`base64_variants.URLSAFE` 或 `base64_variants.URLSAFE_NO_PADDING`。默认值为 `base64_variants.URLSAFE_NO_PADDING`。
* `from_hex()`、`to_hex()`
* `from_string()`、`to_string()`
* `pad(, )`、`unpad(, )`
* `memcmp()`(常数时间相等性检查,返回 `true` 或 `false`)
* `compare()`(常数时间比较。值必须具有相同的大小。返回 `-1`、`0` 或 `1`)
* `memzero()`(适用于 `Uint8Array` 对象)
* `increment()`(递增一个以 little-endian `Uint8Array` 存储的任意长度数字 - 通常用于递增 nonces)
* `add()`(将两个以 little-endian `Uint8Array` 向量存储的任意长度数字相加)
* `is_zero()`(常数时间检查 `Uint8Array` 对象是否全为零)
## API
封装器暴露的 API 与 C 库的 API 相同,只是缓冲区长度永远不需要显式给出。
二进制输入缓冲区应为 `Uint8Array` 对象。但是,如果改为传入字符串,封装器将自动把字符串转换为包含该字符串 UTF-8 表示形式的数组。
示例:
```
var key = sodium.randombytes_buf(sodium.crypto_shorthash_KEYBYTES),
hash1 = sodium.crypto_shorthash(new Uint8Array([1, 2, 3, 4]), key),
hash2 = sodium.crypto_shorthash('test', key);
```
如果输出是唯一的二进制缓冲区,它将作为 `Uint8Array` 对象返回。
示例 (secretbox):
```
let key = sodium.from_hex('724b092810ec86d7e35c9d067702b31ef90bc43a7b598626749914d6a3e033ed');
function encrypt_and_prepend_nonce(message) {
let nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
let ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
let result = new Uint8Array(nonce.length + ciphertext.length);
result.set(nonce);
result.set(ciphertext, nonce.length);
return result;
}
function decrypt_after_extracting_nonce(nonce_and_ciphertext) {
if (nonce_and_ciphertext.length < sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) {
throw "Short message";
}
let nonce = nonce_and_ciphertext.slice(0, sodium.crypto_secretbox_NONCEBYTES),
ciphertext = nonce_and_ciphertext.slice(sodium.crypto_secretbox_NONCEBYTES);
return sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
}
```
此外,提供了 `from_hex`、`to_hex`、`from_string` 和 `to_string` 函数,用于在十六进制和任意字符串表示形式与 `Uint8Array` 对象之间进行显式转换。
返回多个输出缓冲区的函数将把它们作为一个对象返回。例如,`sodium.crypto_box_keypair()` 函数返回以下对象:
```
{ publicKey: (Uint8Array), privateKey: (Uint8Array), keyType: 'x25519' }
```
这也适用于较新的 API,例如:
```
{ state: (StateAddress), header: (Uint8Array) }
{ ciphertext: (Uint8Array), sharedSecret: (Uint8Array) }
```
用于 `crypto_secretstream_xchacha20poly1305_init_push()` 和 `crypto_kem_enc()`。
### 标准版与 Sumo 版本对比
标准版本(位于 `dist/browsers`、`dist/modules` 和 `dist/modules-esm` 目录中)包含高级函数,并且是大多数项目推荐使用的版本。
或者,在 `dist/browsers-sumo`、`dist/modules-sumo` 和 `dist/modules-sumo-esm` 目录中可用的“sumo”版本包含了原始库中的所有符号。这包括未记录的、未经测试的、已弃用的、底层的以及容易误用的函数。
`crypto_pwhash_*` 函数集仅包含在 sumo 版本中。
sumo 版本比标准版本略大,占用更多内存,应仅在您确实需要它提供的额外符号时才使用。
### 编译
如果您想自行编译这些文件,您的系统上需要安装以下依赖项:
* Emscripten
* binaryen
* git
* bun
* make
运行 `make` 将在需要时安装开发依赖项,构建标准版和 sumo 发行版,重新生成 API 文档和 TypeScript 绑定,打包生成的输出,并重新构建浏览器测试构件。
### 基准测试
运行 `make benchmark` 来测量加密函数的性能。
您也可以使用 `bun benchmark/index.ts --only ` 运行特定的基准测试
(使用 `--list` 查看可用的基准测试)。
## 相关项目
* [react-native-libsodium](https://github.com/serenity-kit/react-native-libsodium):与 libsodium-wrappers 包 API 匹配的 Libsodium React Native 绑定
## 作者
由 Ahmad Ben Mrad、Frank Denis 和 Ryan Lester 构建。
## 许可证
此封装器在 [ISC 许可证](https://en.wikipedia.org/wiki/ISC_license) 下分发。
标签:AI工具, CMS安全, CommonJS, CVE, Ed25519, Emscripten, GNU通用公共许可证, JavaScript, libsodium, MITM代理, Node.js, WebAssembly, 前端加密, 加密库, 后端开发, 密码学, 手动系统调用, 数字签名, 数据可视化, 浏览器兼容, 网络安全, 隐私保护