mima-kit
是一个使用 TypeScript
实现的密码学套件。目标是提供一个简单易用的密码学库。mima-kit
尚处于早期开发阶段,API 可能会发生变化。
在线使用: https://rsoram.github.io/mima-live/
npm install mima-kit
▶ 字符编码
UTF8
UTF-8 编码HEX
十六进制编码B64
Base64 编码B64URL
Base64URL 编码
密码学中的数据通常是二进制数据,在 JS
中通常以 Uint8Array
表示,string
和 Uint8Array
的转换需要 字符编码
。
如果您使用
Node.js
这类支持Buffer
的环境,那么您可以直接使用Buffer
进行编解码。如果您使用的是浏览器环境,就可以使用mima-kit
提供的解码器。
mima-kit
提供的编解码器会自动判断输入数据的类型。
- 输入
Uint8Array
类型的数据,会将其转换为string
- 输入
string
类型的数据,会将其转换为Uint8Array
// convert utf-8 string to Uint8Array
const e = UTF8('mima-kit')
// convert Uint8Array to utf-8 string
const d = UTF8(e)
console.log(d) // 'mima-kit'
interface Codec {
/** Parse encoded string to Uint8Array */
(input: string): U8
/** Stringify Uint8Array to encoded string */
(input: Uint8Array): string
FORMAT: string
}
在上述代码中,您可能留意到了 U8
类型。mima-kit
中绝大多数函数都会返回 U8
类型,她是 Uint8Array
的子类,旨在提供一些额外的方法。绝大多数情况下,您可以放心地将 U8
类型传递给其他使用 Uint8Array
的函数。
// Parse encoded string to U8
U8.fromSting('6D696D612D6B6974', HEX)
// Stringify U8 to encoded string
U8.fromSting('6D696D612D6B6974', HEX)
.to(UTF8) // 'mima-kit'
// Convert BigInt to U8
U8.fromBI(0x12345678n) // [0x12, 0x34, 0x56, 0x78]
// Convert U8 to BigInt
U8.fromBI(0x12345678n)
.toBI() // 305419896n (0x12345678n)
散列算法
是一种将任意长度的数据映射为固定长度数据的算法。该定义非常宽泛,但在密码学中,通常讨论的是 加密散列算法
。带密钥的加密散列算法
会额外使用一个密钥产生更安全的散列值。
Specification: GM/T 0004-2012
const m = UTF8('mima-kit')
sm3(m).to(HEX)
Specification: RFC 1321
const m = UTF8('mima-kit')
md5(m).to(HEX)
Specification: FIPS PUB 180-4
const m = UTF8('mima-kit')
sha1(m).to(HEX)
Specification: FIPS PUB 180-4
const m = UTF8('mima-kit')
sha224(m).to(HEX)
sha256(m).to(HEX)
sha384(m).to(HEX)
sha512(m).to(HEX)
const sha512_224 = sha512t(224)
sha512_224(m).to(HEX)
Specification: FIPS PUB 202
const m = UTF8('mima-kit')
sha3_224(m).to(HEX)
sha3_256(m).to(HEX)
sha3_384(m).to(HEX)
sha3_512(m).to(HEX)
shake128(256)(m).to(HEX)
shake256(512)(m).to(HEX)
Specification: NIST SP 800-185
// optional function name
const n = UTF8('name')
// optional customization string
const s = UTF8('custom')
const m = UTF8('mima-kit')
cshake128(256, n, s)(m).to(HEX)
cshake256(512, n, s)(m).to(HEX)
Specification: NIST SP 800-185
// optional customization string
const s = UTF8('custom')
const m = ['mima', '-', 'kit'].map(v => UTF8(v))
tuplehash128(256, s)(m).to(HEX)
tuplehash256(512, s)(m).to(HEX)
tuplehash128XOF(256, s)(m).to(HEX)
tuplehash256XOF(512, s)(m).to(HEX)
Specification: NIST SP 800-185
注意:
mima-kit
提供的ParallelHash
算法并不能真正并行计算,只是将输入分块后分别计算,最后将结果拼接。
// optional customization string
const s = UTF8('custom')
const m = UTF8('mima-kit')
const blockSize = 1024
parallelhash128(blockSize, 256, s)(m).to(HEX)
parallelhash256(blockSize, 512, s)(m).to(HEX)
parallelhash128XOF(blockSize, 256, s)(m).to(HEX)
parallelhash256XOF(blockSize, 512, s)(m).to(HEX)
Specification: TurboSHAKE
// optional Domain Separator
// range: 0x01 ~ 0x7F, default: 0x1F
const D = 0x0B
const m = UTF8('mima-kit')
turboshake128(256, D)(m).to(HEX)
turboshake256(512, D)(m).to(HEX)
Specification: KangarooTwelve
// optional customization string
const s = UTF8('custom')
const m = UTF8('mima-kit')
kt128(256, s)(m).to(HEX)
kt256(512, s)(m).to(HEX)
Specification: RFC 2104
密钥长度的参数
k_size
默认使用散列算法的DIGEST_SIZE
。该参数不会影响函数的结果,但会被其他函数使用,例如ECIES
。
const key = UTF8('password')
const m = UTF8('mima-kit')
// HMAC-SM3
hmac(sm3)(key, m).to(HEX)
// HMAC-SHA1-80 with 80-bit digest and 160-bit key
hmac(sha1, 80)(key, m).to(HEX)
// HMAC-SHA1-160 with 160-bit digest and 80-bit key
hmac(sha1, 160, 80)(key, m).to(HEX)
Specification: NIST SP 800-185
// customization string
const s = UTF8('custom')
const key = UTF8('password')
const m = UTF8('mima-kit')
kmac128(256, s)(key, m).to(HEX)
kmac256(512, s)(key, m).to(HEX)
kmac128XOF(256, s)(key, m).to(HEX)
kmac256XOF(512, s)(key, m).to(HEX)
如果您已经实现了一个伟大而又神秘的散列算法,您可以使用 createHash
函数将其包装成一个可被调用的 Hash
对象。然后您就可以像使用其他 加密散列算法
一样,将您的算法和 mima-kit
中其他高级算法一起使用。
如果您熟悉
JS
,您会发现createHash
的本质不过是Object.assign
的包装。您完全可以用Object.assign
替代createHash
,但createHash
会为您提供一些类型提示,避免发生恼人的拼写错误。
const _greatHash: Digest = (M: Uint8Array) => new U8(M)
const greatHashDescription: HashDescription = {
ALGORITHM: 'GreatHash',
BLOCK_SIZE: 64,
DIGEST_SIZE: 64,
}
const greatHash = createHash(_greatHash, greatHashDescription)
// HMAC-GreatHash
const hmac_gh = hmac(greatHash)
interface Digest {
(M: Uint8Array): U8
}
interface HashDescription {
/** Algorithm name */
ALGORITHM: string
/** Block size (byte) */
BLOCK_SIZE: number
/** Digest size (byte) */
DIGEST_SIZE: number
OID?: string
}
对称密钥算法
是一种使用相同密钥进行 加密
和 解密
的加密算法。它可以分为 分组密码算法
和 流密码算法
。分组密码算法
通常需要组合 填充模式
和 工作模式
一起使用。分组密码算法
可以通过特定的 工作模式
和 NO_PAD
转换为 流密码算法
。
你可以在
/test/cipher.test.ts
中找到更多使用示例。
const k = HEX('')
const iv = HEX('')
const p = UTF8('mima-kit')
// using SM4-CBC
const cbc_sm4 = cbc(sm4)(k, iv)
const c = cbc_sm4.encrypt(p)
const m = cbc_sm4.decrypt(c)
m.to(UTF8) // 'mima-kit'
// using SM4-CTR in stream mode
const ctr_sm4 = ctr(sm4, NO_PAD)(k, iv)
const c = ctr_sm4.encrypt(p)
const m = ctr_sm4.decrypt(c)
m.to(UTF8) // 'mima-kit'
单独使用 分组密码算法
没有太大的意义,因为它只能对单个数据块进行加解密。
Specification: GM/T 0002-2012
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
sm4(k).encrypt(m) // c
sm4(k).decrypt(c) // m
Specification: FIPS PUB 197
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
aes(128)(k).encrypt(m) // c
aes(128)(k).decrypt(x) // m
aes(192)(k).encrypt(m) // c
aes(192)(k).decrypt(c) // m
aes(256)(k).encrypt(m) // c
aes(256)(k).decrypt(c) // m
Specification: RFC 5794
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
aria(128)(k).encrypt(m) // c
aria(128)(k).decrypt(c) // m
aria(192)(k).encrypt(m) // c
aria(192)(k).decrypt(c) // m
aria(256)(k).encrypt(m) // c
aria(256)(k).decrypt(c) // m
Specification: RFC 3713
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
camellia(128)(k).encrypt(m) // c
camellia(128)(k).decrypt(c) // m
camellia(192)(k).encrypt(m) // c
camellia(192)(k).decrypt(c) // m
camellia(256)(k).encrypt(m) // c
camellia(256)(k).decrypt(c) // m
Specification: FIPS PUB 46-3
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
des(k).encrypt(m) // c
des(k).decrypt(c) // m
Specification: FIPS PUB 46-3
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
t_des(128)(k).encrypt(m) // c
t_des(128)(k).decrypt(c) // m
t_des(192)(k).encrypt(m) // c
t_des(192)(k).decrypt(c) // m
Specification: ARC5
ARC5
算法是一个参数化的算法,可以接受长度为 0 < k.byteLength < 256
的密钥。参数化后算法标记为 ARC5-w/r
,其中 w
是工作字的比特长度,r
是轮数。
// 推荐的参数化配置
// +-----+----+
// | w | r |
// +-----+----+
// | 8 | 8 |
// | 16 | 12 |
// | 32 | 16 | (default)
// | 64 | 20 |
// | 128 | 24 |
// +-----+----+
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
const spec8 = arc5(8, 8) // ARC5-8/8
const spec16 = arc5(16, 12) // ARC5-16/12
const spec32 = arc5(32, 16) // ARC5-32/16 (default)
const spec64 = arc5(64, 20) // ARC5-64/20
const spec128 = arc5(128, 24) // ARC5-128/24
spec32(k).encrypt(m) // c
spec32(k).decrypt(c) // m
Specification: Blowfish
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
blowfish(k).encrypt(m) // c
blowfish(k).decrypt(c) // m
Specification: Twofish
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
twofish(128)(k).encrypt(m) // c
twofish(128)(k).decrypt(x) // m
twofish(192)(k).encrypt(m) // c
twofish(192)(k).decrypt(c) // m
twofish(256)(k).encrypt(m) // c
twofish(256)(k).decrypt(c) // m
Specification: TEA
向 TEA
算法传递一个代表 轮数
的参数。TEA
算法的 轮数
可以是任意正整数,默认使用 32
。
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
tea(32)(k).encrypt(m) // c
tea(32)(k).decrypt(c) // m
Specification: XTEA
向 XTEA
算法传递一个代表 轮数
的参数。XTEA
算法的 轮数
可以是任意正整数,默认使用 32
。
let k: Uint8Array
let m: Uint8Array
let c: Uint8Array
xtea(32)(k).encrypt(m) // c
xtea(32)(k).decrypt(c) // m
PKCS7_PAD
PKCS#7 填充模式X923_PAD
ANSI X9.23 填充模式ISO7816_PAD
ISO/IEC 7816-4 填充模式ZERO_PAD
零填充模式NO_PAD
无填充模式
单独使用 填充模式
没有太大的意义,因为它只是对数据进行填充或者去填充。
let block_size: number
let m = new Uint8Array()
let p = new Uint8Array()
// add padding
p = PKCS7_PAD(m, block_size)
// remove padding
m = PKCS7_PAD(p)
interface Padding {
/**
* add padding
* @param {Uint8Array} M - Message
* @param {number} BLOCK_SIZE - Block size
*/
(M: Uint8Array, BLOCK_SIZE: number): U8
/**
* remove padding
* @param {Uint8Array} P - Padded message
*/
(P: Uint8Array): U8
ALGORITHM: string
}
const cbc_sm4 = cbc(sm4, PKCS7_PAD)
const cbc_sm4 = cbc(sm4, X923_PAD)
const cbc_sm4 = cbc(sm4, ISO7816_PAD)
const cbc_sm4 = cbc(sm4, ZERO_PAD)
NO_PAD
模式不会对数据进行填充,这一模式仅用于将分组密码算法
转换为流密码算法
。
// run SM4-OFB in stream mode
const ofb_sm4 = ofb(sm4, NO_PAD)
ecb
Electronic Codebookcbc
Cipher Block Chainingpcbc
Progressive Chaining Block Ciphercfb
Cipher Feedbackofb
Output Feedbackctr
Counter Modegcm
Galois/Counter Mode
mima-kit
将 工作模式
与 分组密码算法
完全解偶,这意味着您可以将任意 分组密码算法
与任意 工作模式
结合使用。
Electronic Codebook
(ECB) 是最简单的工作模式。ECB
模式将明文分成固定长度的数据块,然后对每个数据块进行加密。
ECB
模式不需要iv
。- 向
ECB
模式传递的iv
参数会被忽略。
const k = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = ecb(sm4)(k)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Cipher Block Chaining
(CBC) 是最常用的工作模式。CBC
模式每个明文块都会与前一个密文块进行异或操作,然后再进行加密。
CBC
模式需要iv
。iv
的长度与加密算法的BLOCK_SIZE
相同。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = cbc(sm4)(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Progressive Chaining Block Cipher
(PCBC) 是 CBC
的变种。PCBC
模式每个明文块都会与前一个明文和前一个密文块进行异或操作,然后再进行加密。PCBC
模式旨在将密文中的微小变化在加解密时无限传播。
PCBC
模式需要iv
。iv
的长度与加密算法的BLOCK_SIZE
相同。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = pcbc(sm4)(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Cipher Feedback
(CFB) 将分组密码转换为流密码。CFB
模式通过加密前一个密文块获得加密数据流,然后与明文块进行异或操作,获得密文块。
CFB
模式需要iv
。iv
的长度与加密算法的BLOCK_SIZE
相同。CFB
可以通过NO_PAD
转换为流密码算法
。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = cfb(sm4)(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Output Feedback
(OFB) 将分组密码转换为流密码。OFB
模式通过加密 iv
获得加密数据流,然后与明文块进行异或操作,获得密文块。
OFB
模式需要iv
。iv
的长度与加密算法的BLOCK_SIZE
相同。OFB
可以通过NO_PAD
转换为流密码算法
。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = ofb(sm4)(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Counter Mode
(CTR) 将分组密码转换为流密码。CTR
模式将 iv
与计数器组合以生成唯一的 计数器块
,通过加密 计数器块
获得加密数据流,然后与明文块进行异或操作,获得密文块。
CTR
模式需要iv
。iv
的长度与加密算法的BLOCK_SIZE
相同。CTR
可以通过NO_PAD
转换为流密码算法
。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const c = HEX('')
const CIPHER = ctr(sm4)(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
Galois/Counter Mode
(GCM) 将分组密码转换为流密码。GCM
模式可以看作是 CTR
模式的变种,它在 CTR
模式的基础上增加了 认证
功能。
GCM
模式需要iv
。iv
的长度没有限制,但推荐使用96
位长度的iv
。GCM
可以通过NO_PAD
转换为流密码算法
。- 签名生成的
AUTH_TAG
长度由AUTH_TAG_SIZE
参数决定。AUTH_TAG
最大长度为128
位,设置任意长度都不会影响程序的运行,但一般推荐使用128
、120
、112
、104
、96
位长度,对于某些应用也可以使用64
、32
位长度。
mima-kit
实现的 GCM
模式并没有进行查表优化,因此性能可能会比较慢。
const k = HEX('')
const iv = HEX('')
const m = HEX('')
const a = HEX('')
const c = HEX('')
const t = HEX('')
const CIPHER = gcm(aes(128))(k, iv)
CIPHER.encrypt(m) // c
CIPHER.decrypt(c) // m
CIPHER.sign(c, a) // auth tag
CIPHER.verify(t, c, a) // true or false
通常 流密码算法
不需要复杂的配置,一般只需要 key
和 iv
。
const k = HEX('')
const iv = HEX('')
const cipher = salsa20(k, iv)
const p = UTF8('mima-kit')
const c = cipher.encrypt(p)
const m = cipher.decrypt(c)
// p === m
ZUC
是 3GPP
规范中的流密码算法,它包含机密性算法 128-EEA3
和完整性算法 128-EIA3
。由于 ZUC
算法主要用于移动通信,所以函数接口和其他流密码算法有所不同。
参考 /test/cipher.test.ts
以获取更多使用示例。
const k = new Uint8Array(16)
const m = new Uint8Array(4)
const c = new Uint8Array([0x27, 0xBE, 0xDE, 0x74])
const mac = new Uint8Array([0xC8, 0xA9, 0x59, 0x5E])
const params: ZUCParams = {
KEY: k,
M: m,
COUNTER: 0,
BEARER: 0,
DIRECTION: 0,
LENGTH: 1,
}
// 128-EEA3 加密消息
eea3(params) // c
// 128-EIA3 计算消息认证码
eia3(params) // mac
// 128-EEA3 解密消息
params.M = c
eea3(params) // m
Specification: ARC4
ARC4
算法可以接受长度为 0 < k.byteLength < 256
的密钥,同时 ARC4
算法不需要 iv
。
const k = HEX('')
const cipher = arc4(k)
const c = cipher.encrypt(UTF8('mima-kit'))
const m = cipher.decrypt(c)
Specification: Salsa20
Salsa20
算法可以接受长度为 16
或 32
字节的密钥和 8
字节的 iv
。
const k = HEX('')
const iv = HEX('')
const cipher = salsa20(k, iv)
const c = cipher.encrypt(UTF8('mima-kit'))
const m = cipher.decrypt(c)
Specification: Rabbit
Rabbit
算法可以接受长度为 16
字节的密钥。对于 iv
,Rabbit
算法可以接受长度为 0
或 8
字节的 iv
。当 iv
长度为 0
字节时,Rabbit
算法会跳过 iv Setup
步骤。
const p = UTF8('mima-kit')
const k = HEX('')
const iv = new Uint8Array(8)
const cipher = rabbit(k, iv)
const c = cipher.encrypt(p)
const m = cipher.decrypt(c)
// skip iv setup
const cipher = rabbit(k, new Uint8Array(0))
const c = cipher.encrypt(p)
const m = cipher.decrypt(c)
与 包装您的加密散列算法
一样,您可以使用 createCipher
函数将您的 对称密钥算法
包装成一个可被调用的 Cipher
对象。然后您就可以像使用其他 对称密钥算法
一样,将您的算法和 mima-kit
中其他高级算法一起使用。
如果您熟悉
JS
,您会发现createCipher
的本质不过是Object.assign
的包装。您完全可以用Object.assign
替代createCipher
,但createCipher
会为您提供一些类型提示,避免发生恼人的拼写错误。
const _greatCipher: Cipher = (k: Uint8Array) => {
const cipher = {
encrypt: (M: Uint8Array) => new U8(k.map((v, i) => v ^ M[i])),
decrypt: (C: Uint8Array) => new U8(k.map((v, i) => v ^ C[i])),
}
return cipher
}
const greatCipherDescription: BlockCipherInfo = {
ALGORITHM: 'GreatCipher',
BLOCK_SIZE: 16,
KEY_SIZE: 16,
MIN_KEY_SIZE: 16,
MAX_KEY_SIZE: 16,
}
const greatCipher = createCipher(_greatCipher, greatCipherDescription)
// GCM-GreatCipher
const gcm_gc = gcm(greatCipher)
interface Cipher {
(k: Uint8Array): Cipherable
}
interface Cipherable {
encrypt: (M: Uint8Array) => U8
decrypt: (C: Uint8Array) => U8
}
interface BlockCipherInfo {
ALGORITHM: string
/** Block size (byte) */
BLOCK_SIZE: number
/** Recommended key size (byte) */
KEY_SIZE: number
/** Minimum key size (byte) */
MIN_KEY_SIZE: number
/** Maximum key size (byte) */
MAX_KEY_SIZE: number
}
非对称密钥算法是一种使用不同密钥进行加密和解密的加密算法。非对称密钥算法通常包含 公钥
和 私钥
,公钥
用于加密,私钥
用于解密。
mima-kit
不支持也不打算支持ASN.1
编码。如果您真的需要将密钥对导出为ASN.1
编码,您可以使用asn1js
这个库。在
Node.js
环境中,mima-kit
使用本机crypto
模块产生素数。而在浏览器环境中,mima-kit
使用Miller-Rabin
算法产生素数。
Specification: RFC 8017
RSA
算法是一种基于大素数分解的非对称加密算法。mima-kit
提供的 RSA
算法支持大于 256
位的密钥。因为 mima-kit
内部实现的大数运算相关的函数在处理太小的数字时可能会产生错误的结果。且我并没有测试过小于 256
位的密钥,所以我无法保证小于 256
位的密钥是否能正常工作。
我想这个世界上应该没有人会使用这么小的密钥吧...
在 PKCS#1
中规定了 RSA
算法的 密码学原语
,这些原语是实现规范中其他高级方案的基础。当传入 number
时,rsa
会生成一个带有 原语
能力的 RSA
密钥对。当传入 RSAPrivateKey
或 RSAPublicKey
时,会使用传入的对象作为密钥提供 原语
能力。
需要注意的是,
原语
的encrypt
,decrypt
,sign
,verify
方法返回的是bigint
类型,而不是U8
类型。
// Generate RSA key pair
const key = rsa(2048)
const p = UTF8('mima-kit')
const c = U8.fromBI(key.encrypt(p))
const m = U8.fromBI(key.decrypt(c))
// p === m
const s = U8.fromBI(key.sign(p))
const v = U8.fromBI(key.verify(s))
// v === m
// Using existing key pair
const k: RSAPrivateKey = {
n: 82829320812173273978971929158153744899206558830123557057765054811547521644103n,
e: 65537n,
d: 2824085895826802885484730392051734790667622575612305367583022267256127084981n,
p: 259507137283474348662341935422619692757n,
q: 319179355447530963616684534587734455979n,
dP: 244693883692716798906542597942783565521n,
dQ: 232625874131426773839982556335858160883n,
qInv: 143180457747603899913822528225463864868n,
}
const key = rsa(k)
MGF1
是 PKCS#1
标准中的一个函数组件,它用于生成 OAEP
和 PSS
等密码学方案中的 Mask
。MGF1
需要组合 Hash
函数,通常 MGF1
不会直接使用,而是作为 OAEP
和 PSS
的一部分。
const mgf = mgf1(sha1)
const seed = new U8()
const length = 32
const mask = mgf(seed, length)
interface MGF {
(mdfSeed: Uint8Array, maskLen: number): Uint8Array
}
RSAES-PKCS1-v1_5
是 PKCS#1
标准中的一个加密方案。
const p = UTF8('mima-kit')
const key = rsa(2048)
const cipher = pkcs1_es_1_5(key)
const c = cipher.encrypt(p)
const m = cipher.decrypt(c)
// p === m
RSAES-OAEP
是 PKCS#1
标准中的一个加密方案。它需要组合 Hash
函数、MGF
函数和 Label
数据。
const p = UTF8('mima-kit')
const key = rsa(2048)
// using SHA-256, MGF1-SHA-256, and empty label by default
const cipher = pkcs1_es_oaep(key)
// using SHA-1, MGF1-SHA-1, and empty label
const cipher = pkcs1_es_oaep(key, sha1)
// using SHA-1, MGF1-SHA-256, and label 'mima-kit'
const cipher = pkcs1_es_oaep(key, sha1, mgf1(sha256), UTF8('mima-kit'))
const c = cipher.encrypt(p)
const m = cipher.decrypt(c)
// p === m
RSASSA-PKCS1-v1_5
是 PKCS#1
标准中的一个签名方案。它需要组合 Hash
函数。
RSASSA-PKCS1-v1_5
会用到Hash
的OID
,mima-kit
中只有部份Hash
函数记录了OID
,请务必在使用RSASSA-PKCS1-v1_5
时检查Hash
函数的OID
是否正确。
const p = UTF8('mima-kit')
const key = rsa(2048)
// check OID before using
sha256.OID = '2.16.840.1.101.3.4.2.1'
// using SHA-256 by default
const cipher = pkcs1_ssa_1_5(key)
// using SHA-1
const cipher = pkcs1_ssa_1_5(key, sha1)
const s = cipher.sign(p)
const v = cipher.verify(p, s)
// v === true
RSASSA-PSS
是 PKCS#1
标准中的一个签名方案。它需要组合 Hash
函数、MGF
函数和 Salt Length
。
const p = UTF8('mima-kit')
const key = rsa(2048)
// using SHA-256, MGF1-SHA-256, and sha256.DIGEST_SIZE
const cipher = pkcs1_ssa_pss(key)
// using SHA-1, MGF1-SHA-1, and sha1.DIGEST_SIZE
const cipher = pkcs1_ssa_pss(key, sha1)
// using SHA-1, MGF1-SHA-256, and sha1.DIGEST_SIZE
const cipher = pkcs1_ssa_pss(key, sha1, mgf1(sha256))
// using SHA-1, MGF1-SHA-256, and 32
const cipher = pkcs1_ssa_pss(key, sha1, mgf1(sha256), 32)
const s = cipher.sign(p)
const v = cipher.verify(p, s)
// v === true
Specification: SEC 1
Elliptic-Curve Cryptography
是一种基于椭圆曲线的非对称加密算法。mima-kit
目前仅支持基于素域 Weierstrass
椭圆曲线的 ECC
算法。
使用 ECC
算法前需要选择一个 椭圆曲线
。参考 椭圆曲线列表。
在
mima-kit
的仓库中有许多未导出到包外的椭圆曲线
,您可以在/src/core/ecParams.ts
中找到这些椭圆曲线
。这些椭圆曲线
大多是过于老旧且不常用的曲线,我也没有测试过是否能正常地工作。
const ec = FpECC(secp256r1)
// Generate ECC key pair
const key = ec.genKey()
interface FpECPoint {
isInfinity?: boolean
x: bigint
y: bigint
}
interface ECPublicKey {
/** Elliptic Curve Public Key */
readonly Q: Readonly<FpECPoint>
}
interface ECPrivateKey {
/** Elliptic Curve Private Key */
readonly d: bigint
}
interface ECKeyPair extends ECPrivateKey, ECPublicKey {
}
Point Compress
是 ECC
算法的公钥压缩方法,用于转换 FpECPoint
和 U8
。
const ec = FpECC(secp256r1)
const P = ec.genKey().Q
// will not compress by default
const U = ec.pointToU8(P)
// compress
const U = ec.pointToU8(P, true)
// decompress
const P = ec.U8ToPoint(U)
Elliptic Curve Diffie-Hellman
是 ECC
算法的一种密钥协商协议。在计算得到共享密钥后,通常会使用 KDF
从共享密钥中派生出一个或多个密钥。
const ec = FpECC(secp256r1)
const keyA = ec.genKey()
const keyB = ec.genKey()
const secretA = ec.ecdh(keyA, keyB).x
const secretB = ec.ecdh(keyB, keyA).x
// secretA === secretB
Elliptic Curve Co-factor Diffie-Hellman
是基于 ECDH
的一种密钥协商协议。对曲线参数中 co-factor
为 1
的曲线,ECDH
和 ECCDH
的结果是相同的。
const ec = FpECC(w25519)
const keyA = ec.genKey()
const keyB = ec.genKey()
const secretAc = ec.eccdh(keyA, keyB).x
const secretBc = ec.eccdh(keyB, keyA).x
// secretAc === secretBc
Elliptic Curve Menezes-Qu-Vanstone
是基于 ECDH
的一种密钥协商协议。
const ec = FpECC(secp256r1)
const u_k1 = ec.genKey()
const u_k2 = ec.genKey()
const v_k1 = ec.genKey()
const v_k2 = ec.genKey()
const secretA = ec.ecmqv(u_k1, u_k2, v_k1, v_k2).x
const secretB = ec.ecmqv(v_k1, v_k2, u_k1, u_k2).x
// secretA === secretB
Elliptic Curve Digital Signature Algorithm
是 ECC
算法的一种签名方案。
需要注意的是,
ECDSA
的签名
方法返回的是ECDSASignature
类型,而不是U8
类型。因为ECDSA
签名的结果包含了r
和s
两个值。而在不同的标准下,对r
和s
的转换和拼接方式有可能不同。所以返回ECDSASignature
可以提供更多的灵活性。
const ec = FpECC(secp256r1)
const key = ec.genKey()
const p = UTF8('mima-kit')
// using SHA-256 by default
const cipher = ec.ecdsa()
// using SHA-1
const cipher = ec.ecdsa(sha1)
const s = cipher.sign(key, p)
const v = cipher.verify(key, p, s)
// v === true
interface ECDSASignature {
/** Temporary Public Key's x */
r: bigint
/** Signature Value */
s: bigint
}
ECIES
是 ECC
算法的一种集成加密方案。ECIES
的配置内容比较多,请参考 ECIESConfig
接口。
const ec = FpECC(secp256r1)
const key = ec.genKey()
const cipher = ec.ecies()
const p = UTF8('mima-kit')
const c = cipher.encrypt(key, p)
const m = cipher.decrypt(key, c)
// p === m
interface ECIESConfig {
/** Block Cipher Algorithm (default: AES-256-GCM) */
cipher?: IVBlockCipher
/** Key Hash Function (default: HMAC-SHA-256) */
mac?: KeyHash
/** Key Derivation Function (default: ANSI-X9.63-KDF with SHA-256) */
kdf?: KDF
/** Additional Data 1 (default: empty) */
S1?: Uint8Array
/** Additional Data 2 (default: empty) */
S2?: Uint8Array
/** Initialization Vector (default: Uint8Array(cipher.BLOCK_SIZE)) */
iv?: Uint8Array
}
Specification: GB/T 35276-2017
SM2
算法是中国国家密码管理局发布的一种基于 椭圆曲线
的 非对称加密算法
。理论上,SM2
算法可以使用任意的 椭圆曲线
,但是在实际应用中,SM2
算法通常使用 sm2p256v1
曲线,所以 mima-kit
使用 sm2p256v1
曲线作为 SM2
算法的默认曲线。
const sm2ec = sm2()
const key = sm2ec.genKey()
SM2
在 ECC
的基础上增加了 可辨别标识 (Distinguishing Identifier)
的概念。可辨别标识
利用用户标识、公钥和曲线的部分参数,实现无歧义地标识实体的身份信息。
const sm2ec = sm2()
const ID = UTF8('[email protected]')
const KA = sm2ec.genKey(ID)
const ZA = sm2ec.di(ID, KA)
interface SM2DI {
/**
* @param {Uint8Array} id - User Identity
* @param {ECPublicKey} key - Public Key
* @param {Hash} hash - Hash Algorithm (default: SM3)
*/
(id: Uint8Array, key: ECPublicKey, hash?: Hash): U8
}
SM2
算法的密钥协商协议。与标准不同,mima-kit
的 SM2-DH
直接返回 共享密钥
。你需要另外使用 KDF
从 共享密钥
中派生密钥。SM2
标准使用的 KDF
是 ANSI-X9.63-KDF with SM3
。ANSI-X9.63-KDF
和 SM3
都是 mima-kit
支持的算法,你可以直接使用她们。
const sm2ec = sm2()
const kdf = x963kdf(sm3)
// Initiator: Alice
// Responder: Bob
// Step 1: Alice
const KA = sm2ec.genKey()
const KX = sm2ec.genKey()
const ka = { Q: KA.Q } // public key of Alice
const kx = { Q: KX.Q } // temporary public key of Alice
const ID_A = UTF8('[email protected]')
const ZA = sm2ec.di(ID_A, KA) // Alice's distinguishable identifier
// send ZA, ka, kx to Bob
// Step 2: Bob
const KB = sm2ec.genKey()
const KY = sm2ec.genKey()
const kb = { Q: KB.Q } // public key of Bob
const ky = { Q: KY.Q } // temporary public key of Bo
const ID_B = UTF8('[email protected]')
const ZB = sm2ec.di(ID_B, KB) // Bob's distinguishable identifier
const SB = sm2ec.dh(KB, KY, ka, kx, ZA, ZB) // shared secret key
const DKB = kdf(256, S) // derive key
// send ZB, kb, ky to Alice
// Step 3: Alice
const SA = sm2ec.dh(KA, KX, kb, ky, ZA, ZB) // shared secret key
const DKA = kdf(256, S) // derive key
SA === SB
DKA === DKB
interface SM2DH {
/**
* @param {ECKeyPair} KA - Self Key Pair
* @param {ECPublicKey} KX - Self Temporary Key Pair
* @param {ECPublicKey} KB - Opposite Public Key
* @param {ECPublicKey} KY - Opposite Temporary Public Key
* @param [Uint8Array] ZA - Initiator Identity Derived Value
* @param [Uint8Array] ZB - Receiver Identity Derived Value
* @returns {U8} - Keying Material
*/
(KA: ECKeyPair, KX: ECKeyPair, KB: ECPublicKey, KY: ECPublicKey, ZA?: Uint8Array, ZB?: Uint8Array): U8
}
SM2 Digital Signature Algorithm
是 SM2
算法的签名方案。她接受一个 Hash
函数作为参数,SM2-DSA
使用 SM3
作为默认的 Hash
函数。
SM2-DSA
的签名
方法返回的是SM2DSASignature
类型,而不是U8
类型。SM2-DSA
签名的结果包含了r
和s
两个值。
const sm2ec = sm2()
const ID = UTF8('[email protected]')
const KA = sm2ec.genKey(ID)
const ZA = sm2ec.di(ID, KA)
const M = UTF8('mima-kit')
const signer = sm2ec.dsa() // using SM3 by default
const signature = signer.sign(ZA, KA, M)
signer.verify(ZA, KA, M, signature) // true
interface SM2DSASignature {
r: bigint
s: bigint
}
interface SM2DSA {
/**
* @param {Hash} hash - Hash Algorithm (default: SM3)
*/
(hash?: Hash): {
/**
* @param {Uint8Array} Z - Identity Derived Value
* @param {ECPrivateKey} key - Signer Private Key
* @param {Uint8Array} M - Message
*/
sign: (Z: Uint8Array, key: ECPrivateKey, M: Uint8Array) => SM2DSASignature
/**
* @param {Uint8Array} Z - Identity Derived Value
* @param {ECPublicKey} key - Signer Public Key
* @param {Uint8Array} M - Message
* @param {SM2DSASignature} S - Signature
*/
verify: (Z: Uint8Array, key: ECPublicKey, M: Uint8Array, S: SM2DSASignature) => boolean
}
}
SM2-ES
是 SM2
算法的加密方案。
const sm2ec = sm2(curve)
const M = UTF8('The king\'s ears are donkey ears')
const key = sm2ec.genKey()
const cipher = sm2ec.es()
const C = cipher.encrypt(key, M)
cipher.decrypt(key, C) // M
interface SM2Encrypt {
/**
* @param {ECPublicKey} p_key - Receiver Public Key
* @param {Uint8Array} M - Plaintext
*/
(p_key: ECPublicKey, M: Uint8Array): U8
}
interface SM2Decrypt {
/**
* @param {ECPrivateKey} s_key - Decryptor Private Key
* @param {Uint8Array} C - Ciphertext
*/
(s_key: ECPrivateKey, C: Uint8Array): U8
}
interface SM2EncryptionScheme {
/**
* @param {Hash} hash - Hash Algorithm (default: SM3)
* @param {KDF} kdf - Key Derivation Function (default: X9.63 KDF with SM3)
* @param {'c1c2c3' | 'c1c3c2'} order - Ciphertext Segment Order (default: 'c1c3c2')
*/
(hash?: Hash, kdf?: KDF, order?: 'c1c2c3' | 'c1c3c2'): {
encrypt: SM2Encrypt
decrypt: SM2Decrypt
}
}
密钥派生函数 (KDF) 是一种从一个密钥派生出另一个或多个密钥的算法。KDF 很少直接使用,而是作为其他算法方案的一部分。
interface KDF {
/**
* @param {number} k_bit - 期望的密钥长度 / output keying material length
* @param {Uint8Array} ikm - 输入密钥材料 / input keying material
* @param {Uint8Array} info - 附加信息 / optional context and application specific information
*/
(k_bit: number, ikm: Uint8Array, info?: Uint8Array): U8
}
X9.63KDF
是 ANSI-X9.63
标准中的一个密钥派生函数。X9.63KDF
需要组合 Hash
函数。
const kdf = x963kdf(sha256)
HKDF
是 RFC 5869
标准中的一个密钥派生函数。HKDF
需要组合 KeyHash
函数和一个可选的 salt
const mac = hmac(sha256)
const kdf = hkdf(mac)
PBKDF2
是 PKCS#5
标准中的一个密钥派生函数。PBKDF2
需要组合 KeyHash
函数,指定 iteration
次数和一个可选的 salt
。
const mac = hmac(sha256)
const kdf = pbkdf2(mac, 1000)
mima-kit
并没有导出所有的 椭圆曲线
,但是您可以在 /src/core/ecParams.ts
中找到所有的 椭圆曲线
。
在表格之外,
sm2p256v1
也是导出的Weierstrass
曲线。它适用于所有ECC
算法,但是它常用于SM2
算法,所以不写入表格之中。
SEC | NIST | X9.63 | RFC 5639 |
---|---|---|---|
- | w25519 |
- | - |
- | w448 |
- | - |
secp192k1 |
- | - | - |
secp192r1 |
p192 |
prime192v1 |
- |
secp224k1 |
- | - | - |
secp224r1 |
p224 |
- | - |
secp256r1 |
p256 |
prime256v1 |
- |
secp256k1 |
- | - | - |
secp384r1 |
p384 |
- | - |
secp521r1 |
p521 |
- | - |
- | - | - | bp192r1 |
- | - | - | bp224r1 |
- | - | - | bp256r1 |
- | - | - | bp320r1 |
- | - | - | bp384r1 |
- | - | - | bp512r1 |