ricmoo/aes-js
GitHub: ricmoo/aes-js
一个无依赖的纯 JavaScript AES 加密库,支持所有常见操作模式,适用于浏览器和 Node.js 环境。
Stars: 1491 | Forks: 274
# AES-JS
[](https://badge.fury.io/js/aes-js)
一个纯 JavaScript 实现的 AES 分组密码算法及其所有常见操作模式(CBC, CFB, CTR, ECB 和 OFB)。
## 功能特性
- 纯 JavaScript(无依赖)
- 支持所有密钥长度(128 位、192 位和 256 位)
- 支持所有常见操作模式(CBC, CFB, CTR, ECB 和 OFB)
- 可在 Node.js 或 Web 浏览器中运行
## 从 2.x 迁移到 3.x
3.x 分支中重命名了实用函数,因为它们在字节和字符串之间转换时引起了极大的混淆。
示例也已更新,将二进制数据编码为可打印的十六进制字符串。
**字符串与字节**
**不应**使用字符串作为密钥。UTF-8 允许可变长度的多字节字符,因此一个 16 个*字符*长的字符串可能并不是 16 个*字节*长。
此外,**不应**使用 UTF8 存储任意二进制数据,因为它是*字符串*编码格式,而不是*二进制*编码格式。
```
// aesjs.util.convertStringToBytes(aString)
// Becomes:
aesjs.utils.utf8.toBytes(aString)
// aesjs.util.convertBytesToString(aString)
// Becomes:
aesjs.utils.utf8.fromBytes(aString)
```
**字节与十六进制字符串**
二进制数据(例如加密后的字节)可以安全地存储并打印为十六进制字符串。
```
// aesjs.util.convertStringToBytes(aString, 'hex')
// Becomes:
aesjs.utils.hex.toBytes(aString)
// aesjs.util.convertBytesToString(aString, 'hex')
// Becomes:
aesjs.utils.hex.fromBytes(aString)
```
**类型化数组**
aes-js 的 3.x 及更高版本使用 [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) 代替 Array,这在与 Browserify 一起使用时减少了代码体积(不再引入 Buffer),并且速度也快了约 **两倍**。
但是,如果您需要支持 IE 10 之前的浏览器,应继续使用 2.x 版本。
# API
#### Node.js
要在您的 node.js 项目中安装 `aes-js`:
```
npm install aes-js
```
要在 node 中访问它,只需添加:
```
var aesjs = require('aes-js');
```
#### Web 浏览器
要在网页中使用 `aes-js`,请添加以下内容:
```
```
## 密钥
所有密钥的长度必须是 128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。
该库适用于 `Array`、`Uint8Array` 和 `Buffer` 对象,以及任何*类数组*对象(即必须具有 `length` 属性,并且每个条目都有有效的字节值)。
```
// 128-bit, 192-bit and 256-bit keys
var key_128 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var key_192 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23];
var key_256 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31];
// or, you may use Uint8Array:
var key_128_array = new Uint8Array(key_128);
var key_192_array = new Uint8Array(key_192);
var key_256_array = new Uint8Array(key_256);
// or, you may use Buffer in node.js:
var key_128_buffer = Buffer.from(key_128);
var key_192_buffer = Buffer.from(key_192);
var key_256_buffer = Buffer.from(key_256);
```
要从易于记忆的密码生成密钥,请考虑使用基于密码的密钥派生函数,例如 [scrypt](https://www.npmjs.com/package/scrypt-js) 或 [bcrypt](https://www.npmjs.com/search?q=bcrypt)。
## 常见操作模式
有多种操作模式,每种都有各自的优缺点。不过,通常推荐使用 **CBC** 和 **CTR** 模式。**不推荐使用 ECB**,包含它主要是为了完整性。
### CTR - 计数器模式(推荐)
```
// An example 128-bit key (16 bytes * 8 bits/byte = 128 bits)
var key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
// Convert text to bytes
var text = 'Text may be any length you wish, no padding is required.';
var textBytes = aesjs.utils.utf8.toBytes(text);
// The counter is optional, and if omitted will begin at 1
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
var encryptedBytes = aesCtr.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "a338eda3874ed884b6199150d36f49988c90f5c47fe7792b0cf8c7f77eeffd87
// ea145b73e82aefcf2076f881c88879e4e25b1d7b24ba2788"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// The counter mode of operation maintains internal state, so to
// decrypt a new instance must be instantiated.
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
var decryptedBytes = aesCtr.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "Text may be any length you wish, no padding is required."
```
### CBC - 密码块链接模式(推荐)
```
// An example 128-bit key
var key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
// The initialization vector (must be 16 bytes)
var iv = [ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,35, 36 ];
// Convert text to bytes (text must be a multiple of 16 bytes)
var text = 'TextMustBe16Byte';
var textBytes = aesjs.utils.utf8.toBytes(text);
var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var encryptedBytes = aesCbc.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "104fb073f9a131f2cab49184bb864ca2"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// The cipher-block chaining mode of operation maintains internal
// state, so to decrypt a new instance must be instantiated.
var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var decryptedBytes = aesCbc.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "TextMustBe16Byte"
```
### CFB - 密文反馈模式
```
// An example 128-bit key
var key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
// The initialization vector (must be 16 bytes)
var iv = [ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,35, 36 ];
// Convert text to bytes (must be a multiple of the segment size you choose below)
var text = 'TextMustBeAMultipleOfSegmentSize';
var textBytes = aesjs.utils.utf8.toBytes(text);
// The segment size is optional, and defaults to 1
var segmentSize = 8;
var aesCfb = new aesjs.ModeOfOperation.cfb(key, iv, segmentSize);
var encryptedBytes = aesCfb.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "55e3af2638c560b4fdb9d26a630733ea60197ec23deb85b1f60f71f10409ce27"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// The cipher feedback mode of operation maintains internal state,
// so to decrypt a new instance must be instantiated.
var aesCfb = new aesjs.ModeOfOperation.cfb(key, iv, 8);
var decryptedBytes = aesCfb.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "TextMustBeAMultipleOfSegmentSize"
```
### OFB - 输出反馈模式
```
// An example 128-bit key
var key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
// The initialization vector (must be 16 bytes)
var iv = [ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,35, 36 ];
// Convert text to bytes
var text = 'Text may be any length you wish, no padding is required.';
var textBytes = aesjs.utils.utf8.toBytes(text);
var aesOfb = new aesjs.ModeOfOperation.ofb(key, iv);
var encryptedBytes = aesOfb.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "55e3af2655dd72b9f32456042f39bae9accff6259159e608be55a1aa313c598d
// b4b18406d89c83841c9d1af13b56de8eda8fcfe9ec8e75e8"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// The output feedback mode of operation maintains internal state,
// so to decrypt a new instance must be instantiated.
var aesOfb = new aesjs.ModeOfOperation.ofb(key, iv);
var decryptedBytes = aesOfb.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "Text may be any length you wish, no padding is required."
```
### ECB - 电子密码本模式(不推荐)
**不**推荐使用此模式。因为对于给定的密钥,相同的明文块输入会产生相同的密文块输出,这种操作模式可能会泄露数据,例如模式。有关更多详细信息和示例,请参阅 Wikipedia 文章 [Electronic Codebook](http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_.28ECB.29)。
```
// An example 128-bit key
var key = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 ];
// Convert text to bytes
var text = 'TextMustBe16Byte';
var textBytes = aesjs.utils.utf8.toBytes(text);
var aesEcb = new aesjs.ModeOfOperation.ecb(key);
var encryptedBytes = aesEcb.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "a7d93b35368519fac347498dec18b458"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// Since electronic codebook does not store state, we can
// reuse the same instance.
//var aesEcb = new aesjs.ModeOfOperation.ecb(key);
var decryptedBytes = aesEcb.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "TextMustBe16Byte"
```
## 分组密码
通常您应该使用上述常见操作模式之一。也可以直接使用分组密码算法,这实际上使用的是 **ECB**,因为该操作模式仅仅是一个简单的包装器。
但这对于试验自定义操作模式或研究分组密码算法可能很有用。
```
// the AES block cipher algorithm works on 16 byte bloca ks, no more, no less
var text = "ABlockIs16Bytes!";
var textAsBytes = aesjs.utils.utf8.toBytes(text)
console.log(textAsBytes);
// [65, 66, 108, 111, 99, 107, 73, 115, 49, 54, 66, 121, 116, 101, 115, 33]
// create an instance of the block cipher algorithm
var key = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3];
var aes = new aesjs.AES(key);
// encrypt...
var encryptedBytes = aes.encrypt(textAsBytes);
console.log(encryptedBytes);
// [136, 15, 199, 174, 118, 133, 233, 177, 143, 47, 42, 211, 96, 55, 107, 109]
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
console.log(encryptedHex);
// "880fc7ae7685e9b18f2f2ad360376b6d"
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// decrypt...
var decryptedBytes = aes.decrypt(encryptedBytes);
console.log(decryptedBytes);
// [65, 66, 108, 111, 99, 107, 73, 115, 49, 54, 66, 121, 116, 101, 115, 33]
// decode the bytes back into our original text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
console.log(decryptedText);
// "ABlockIs16Bytes!"
```
# 说明
## 什么是密钥
这似乎是许多刚开始使用加密的人感到困惑的一点。您可以将密钥视为“密码”。但是,这些算法要求“密码”具有特定的长度。
对于 AES,有三种可能的密钥长度:128 位(16 字节)、192 位(24 字节)或 256 位(32 字节)。创建 AES 对象时,会自动检测密钥大小,因此传入正确长度的密钥非常重要。
通常,您希望提供任意长度的密码,例如易于记忆或写下的内容。在这些情况下,您必须想出一种方法将密码转换为特定长度的密钥。**基于密码的密钥派生函数** (PBKDF) 正是为这一确切目的设计的算法。
这是一个使用流行的(可能已过时?)pbkdf2 的示例:
```
var pbkdf2 = require('pbkdf2');
var key_128 = pbkdf2.pbkdf2Sync('password', 'salt', 1, 128 / 8, 'sha512');
var key_192 = pbkdf2.pbkdf2Sync('password', 'salt', 1, 192 / 8, 'sha512');
var key_256 = pbkdf2.pbkdf2Sync('password', 'salt', 1, 256 / 8, 'sha512');
```
另一种可能性是使用哈希函数(例如 SHA256)对密码进行哈希处理,但除非您使用 [salt](http://en.wikipedia.org/wiki/Salt_(cryptography),否则这种方法容易受到 [Rainbow Attacks](http://en.wikipedia.org/wiki/Rainbow_table)。
## 性能
待办...
## 测试
测试套件是从已知正确的实现 [pycrypto](https://www.dlitz.net/software/pycrypto/) 生成的(`test/test-vectors.json`)。要生成新的测试向量,请运行 `python generate-tests.py`。
要运行 node.js 测试套件:
```
npm test
```
要运行 Web 浏览器测试,请在浏览器中打开 `test/test.html` 文件。
## 常见问题
#### 如何添加我有疑问的问题?
如有任何问题、建议、评论等,请发送电子邮件至 aes-js@ricmoo.com。
## 捐赠
显然,这一切都是在 MIT 许可下授权的,所以请随意使用;但如果您想请我喝杯咖啡,我也不会拒绝。=)
- Bitcoin - `1K1Ax9t6uJmjE4X5xcoVuyVTsiLrYRqe2P`
- Ethereum - `0x70bDC274028F3f391E398dF8e3977De64FEcBf04`
标签:256位密钥, AES, CBC, CFB, CMS安全, CTR, DNS 反向解析, ECB, GNU通用公共许可证, HTTP工具, JavaScript, MITM代理, Node.js, OFB, Rijndael算法, Uint8Array, Web安全, 分组密码, 前端加密, 加密, 后端加密, 密码学, 对称加密, 手动系统调用, 数据加密, 数据可视化, 无依赖, 本体建模, 漏洞扫描器, 纯JS实现, 自动化审计, 自定义脚本, 自定义脚本, 蓝队分析