add: 通过 ethereum-rsa 实现了椭圆曲线公私钥加解密!fix: is_secword

This commit is contained in:
陆柯 2022-11-11 16:17:18 +08:00
parent c9b147aeaf
commit ec359d0d98
2 changed files with 101 additions and 60 deletions

View File

@ -10,6 +10,7 @@
"bip39": "^3.0.4",
"bs58check": "^2.1.2",
"eccrypto-js": "^5.4.0",
"ethereum-rsa": "^1.0.5",
"hdkey": "^2.0.1",
"js-crypto-key-utils": "^1.0.4",
"keccak": "^3.0.2",

160
ticc.js
View File

@ -6,8 +6,9 @@ const bs58check = require('bs58check')
const bs58 = require('bs58') // bs58check depends on bs58
const uuid = require('uuid')
const keccak = require('keccak')
const eccrypto = require('eccrypto-js') // 用于加解密。eccrypto 在 windows 上和 openssl 的版本兼容性有点麻烦,所以换用 eccrypto-js
const ecc = require('eccrypto-js') // 用于加解密。eccrypto 在 windows 上和 openssl 的版本兼容性有点麻烦,所以换用 eccrypto-js
const keyman = require('js-crypto-key-utils') // 转换原始密钥和 PER/DER 格式。
const ethrsa = require('ethereum-rsa')
// const BitcoreMnemonic = require('bitcore-mnemonic') // https://bitcore.io/api/mnemonic/ https://github.com/bitpay/bitcore-mnemonic // 打包成 app 里常有问题,试图访问 window 变量,无法生成 secword
const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更多语言,但不方便选择语言,也不能使用 pass
const hdkey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能
@ -123,7 +124,10 @@ class TicCrypto {
// return false
//// for bip39. 注意bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false这一点不如 bitcore-mnemonic. 所以不能直接 bip39.validateMnemonic(secword)
if (typeof secword === 'string' && !/(^\s)|\s\s|(\s$)/.test(secword) && [12, 15, 18, 21, 24].includes(secword.split(/\s+/).length)) {
secword = secword
.replace(/^\s+/, '') // 删除开头的空格
.replace(/\s+$/, '') // 删除末尾的空格
if (typeof secword === 'string' && [12, 15, 18, 21, 24].includes(secword.split(/\s+/).length)) {
if (mode === 'easy') return true // easy模式不检查校验等等严格的合法性了反正 secword_to_seed 是接受一切字符串的
if (my.langMap[lang?.toLowerCase?.()]) {
// 指定了语言则针对该语言词库检查
@ -131,7 +135,9 @@ class TicCrypto {
} else {
// 未指定语言则检查所有可能语言词库
for (let lang of my.langList) {
return bip39.validateMnemonic(secword, bip39.wordlists[lang])
if (bip39.validateMnemonic(secword, bip39.wordlists[lang])) {
return true
}
}
}
}
@ -208,35 +214,40 @@ class TicCrypto {
*
* @static
* @param {*} data
* @param {*} option [{ tool, keytype, key, input, output, cipher }={}]
* @param {*} option [{ mode, key, input, output, cipher }={}]
* @return {String}
* @memberof TicCrypto
*/
static async encrypt_easy ({ data, tool = 'crypto', keytype = 'pwd', key, input = my.INPUT, output = my.OUTPUT, cipher = my.CIPHER } = {}) {
if (tool === 'eccrypto') {
static async encrypt_easy ({ data, mode = 'semkey', key, input = my.INPUT, output = my.OUTPUT, cipher = my.CIPHER } = {}) {
if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data)
if (mode === 'ethrsa') {
if (key?.prikey && key?.pubkey) {
return ethrsa.encryptMessage(data, key?.prikey, key?.pubkey)
} else {
return ethrsa.encryptMessage(data, key?.senderPrikey, key?.receiverPubkey)
}
} else if (mode === 'ecc') {
// data 应当是 utf8 的字符串。key 必须是 pubkey
// eccrypto 能用 Uint8Array 和 Buffer
// eccrypto-js 只能用 Buffer
// 在浏览器里 https://github.com/bitchan/eccrypto 库报错,即使用了 Uint8Array: Failed to execute 'encrypt' on 'SubtleCrypto': The provided value is not of type '(ArrayBuffer or ArrayBufferView)'
let cipherObject = await eccrypto.encrypt(Buffer.from(this.hex_to_buf(key)), data)
let cipherObject = await ecc.encrypt(Buffer.from(this.hex_to_buf(key)), data)
return cipherObject // 返回一个复杂的结构 {iv:Buffer, ciphertext:Buffer, ...}。对同样的key和data每次返回的结果不一样
} else if (keytype === 'pwd') {
} else if (mode === 'semkey') {
// 对称加密
if (typeof key === 'string') {
let inputEncoding = input // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView
let outputEncoding = output === 'buf' ? undefined : output // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly
const iv = crypto.randomBytes(16)
let encryptor = crypto.createCipheriv(cipher, this.hex_to_buf(this.hash_easy(key)), iv) // cipher 和 key 的长度必须相同,例如 cipher 是 ***-192那么 key 就必须是 192/8=24 字节 = 48 hex 的。
if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data)
let ciphertext = encryptor.update(data, inputEncoding, outputEncoding)
ciphertext += encryptor.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
return { iv: iv.toString('hex'), ciphertext } // 有 iv显然每次结果不一样
}
} else if (keytype === 'prikey') {
// 尚未走通,不能使用 ticc 生成的 Elliptic curve 椭圆曲线算法公私钥,只能用 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥
let inputEncoding = input // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView
let outputEncoding = output === 'buf' ? undefined : output // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly
const iv = crypto.randomBytes(16)
let encryptor = crypto.createCipheriv(cipher, this.hex_to_buf(this.hash_easy(key)), iv) // cipher 和 key 的长度必须相同,例如 cipher 是 ***-192那么 key 就必须是 192/8=24 字节 = 48 hex 的。
let ciphertext = encryptor.update(data, inputEncoding, outputEncoding)
ciphertext += encryptor.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
return { iv: iv.toString('hex'), ciphertext } // 有 iv显然每次结果不一样
} else if (mode === 'prikey') {
// 只能用于 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥,不能用于 Elliptic Curve 的公私钥
let prikeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。
return crypto.privateEncrypt(prikeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。
} else if (keytype === 'pubkey') {
} else if (mode === 'pubkey') {
// 只能用于 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥,不能用于 Elliptic Curve 的公私钥
let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem')
return crypto.publicEncrypt(pubkeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果不一样。
}
@ -248,39 +259,46 @@ class TicCrypto {
*
* @static
* @param {*} data
* @param {Object} option [{ keytype, key, input, output, cipher, format }={}]
* @param {Object} option [{ mode, key, input, output, cipher, format }={}]
* @return {String}
* @memberof TicCrypto
*/
static async decrypt_easy ({ data = {}, tool = 'crypto', keytype = 'pwd', key, input = my.OUTPUT, output = my.OUTPUT, cipher = my.CIPHER } = {}) {
static async decrypt_easy ({ data = {}, mode = 'semkey', key, input = my.OUTPUT, output = 'utf8', cipher = my.CIPHER } = {}) {
// data 应当是 encrypt 输出的数据类型
if (tool === 'eccrypto') {
if (mode === 'ethrsa') {
if (key?.prikey && key?.pubkey) {
return ethrsa.decryptMessage(data, key?.prikey, key?.pubkey)
} else {
return ethrsa.decryptMessage(data, key?.receiverPrikey, key?.senderPubkey)
}
} else if (mode === 'ecc') {
try {
// eccrypto 只能接受 Buffer, 不接受 Uint8Array, 因为 eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex_to_buf
// eccrypto 也只能接受 Buffer, 不接受 Uint8Array
// data 需要是 eccrypto 自身encrypt方法返回的 cipherObject. key 是 private key。
let plainbuffer = await eccrypto.decrypt(Buffer.from(key, 'hex'), data) // 返回的是 Buffer
let plainbuffer = await ecc.decrypt(Buffer.from(key, 'hex'), data) // 返回的是 Buffer
return plainbuffer.toString('utf8')
} catch (exception) {
// eccrypto 对无法解密的,会抛出异常
return null
}
} else if (keytype === 'pwd') {
} else if (mode === 'semkey') {
// 对称解密
if ((typeof data.ciphertext === 'string' || data.ciphertext instanceof Buffer) && typeof key === 'string') {
if (typeof data.ciphertext === 'string' || data.ciphertext instanceof Buffer) {
let inputEncoding = input // input (=output of encrypt) could be 'latin1', 'base64', 'hex' by default for string or ignored for Buffer
let outputEncoding = output === 'buf' ? undefined : output // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly
let decryptor = crypto.createDecipheriv(cipher, this.hex_to_buf(this.hash_easy(key)), Buffer.from(data.iv, 'hex'))
let decrypted = decryptor.update(data.ciphertext, inputEncoding, outputEncoding)
decrypted += decryptor.final(outputEncoding) // 但是 Buffer + Buffer 还是会变成string
// 如果用户输入错误密deciper也能解密无法自动判断是否正确结果。可在返回后人工判断。
// 如果用户输入错误密deciper也能解密无法自动判断是否正确结果。可在返回后人工判断。
return decrypted
}
} else if (keytype === 'prikey') {
// 尚未走通,不能使用 ticc 生成的 Elliptic curve 椭圆曲线算法公私钥
} else if (mode === 'prikey') {
// 只能用于 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥,不能用于 Elliptic Curve 的公私钥
let prikeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。
return crypto.privateDecrypt(prikeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。
} else if (keytype === 'pubkey') {
} else if (mode === 'pubkey') {
// 只能用于 crypto.generateKeyPairSync('rsa') 生成的 rsa 公私钥,不能用于 Elliptic Curve 的公私钥
let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem')
return crypto.publicDecrypt(pubkeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果不一样。
}
@ -305,9 +323,9 @@ class TicCrypto {
let hashBuf = this.hash_easy(data, { output: 'buf' }) // 哈希必须输出为 buffer
let signature = nacl.sign.detached(hashBuf, Buffer.from(prikey, 'hex'))
return Buffer.from(signature).toString('hex') // 签名是64节128个hex字符
} else if (tool === 'eccrypto' && prikey.length === 64) {
} else if (tool === 'ecc' && prikey.length === 64) {
// eccrypto 对同一组data, prikey 生成的签名是固定的观察到hex长度为140或142是der格式。
let signature = await eccrypto.sign(Buffer.from(prikey, 'hex'), this.hash_easy(data, { output: 'buf' }))
let signature = await ecc.sign(Buffer.from(prikey, 'hex'), this.hash_easy(data, { output: 'buf' }))
return signature.toString('hex')
} else if (prikey.length === 64) {
// 纯 crypto
@ -343,10 +361,10 @@ class TicCrypto {
let bufPubkey = Buffer.from(pubkey, 'hex')
let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey)
return verified
} else if ('eccrypto' === tool && signature.length >= 140) {
} else if ('ecc' === tool && signature.length >= 140) {
// 默认使用 eccrypto // 发现大小写不影响 eccrypto 验签!都能通过
try {
let result = await eccrypto.verify(Buffer.from(pubkey, 'hex'), this.hash_easy(data, { output: 'buf' }), Buffer.from(signature, 'hex')) // 如果给signature添加1位hexeccrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。
let result = await ecc.verify(Buffer.from(pubkey, 'hex'), this.hash_easy(data, { output: 'buf' }), Buffer.from(signature, 'hex')) // 如果给signature添加1位hexeccrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。
return true
} catch (exception) {
// 对能够验证的eccrypto返回 null对无法验证的抛出异常
@ -558,19 +576,20 @@ class TicCrypto {
static secword_to_account ({ secword, coin, coinFamily, world, pass, pathSeed, pathIndex, path, tool, hasher } = {}) {
// account 比 keypair 多了 address 字段。
coin = coin?.toUpperCase?.() || my.COIN
coinFamily = coinFamily?.toUpperCase?.() || my.COIN_FAMILY
let kp = this.secword_to_keypair({ secword, coin, pass, pathSeed, pathIndex, path, tool, hasher })
if (kp) {
if (coin === 'ETH') {
if (coin === 'ETH' || coinFamily === 'ETH') {
world = world || 'mainnet'
kp.address = this.pubkey_to_address({ pubkey: this.decompress_pubkey(kp.pubkey), coin, coinFamily, world })
} else if (coin === 'BTC') {
} else if (coin === 'BTC' || coinFamily === 'BTC') {
world = world || 'mainnet'
kp.address = this.pubkey_to_address({ pubkey: kp.pubkey, coin, coinFamily, world })
} else {
world = world || my.WORLD
kp.address = this.pubkey_to_address({ pubkey: kp.pubkey, coin, coinFamily, world })
}
return { ...kp, coin, world, secword }
return { ...kp, coin, coinFamily, world, secword }
}
return null
}
@ -1344,27 +1363,27 @@ class TicCrypto {
}
// https://en.wikipedia.org/wiki/Base32
static hex_to_b32 (hex, { encoding = 'RFC4648' } = {}) {
static hex_to_b32 (hex) {
if (/^[0-9a-fA-F]+$/.test(hex)) {
return base32encode(Buffer.from(hex, 'hex'), encoding)
return base32encode(Buffer.from(hex, 'hex'), 'RFC4648')
}
return null
}
static b32_to_hex (b32, { encoding = 'RFC4648' } = {}) {
static b32_to_hex (b32) {
if (/^[A-Za-z2-7=]+$/.test(b32)) {
return Buffer.from(base32decode(b32.toUpperCase(), encoding)).toString('hex')
return Buffer.from(base32decode(b32.toUpperCase(), 'RFC4648')).toString('hex')
}
return null
}
static hex_to_b32h (hex, { encoding = 'RFC4648-HEX' } = {}) {
static hex_to_b32h (hex) {
if (/^[0-9a-fA-F]+$/.test(hex)) {
return base32encode(Buffer.from(hex, 'hex'), encoding)
return base32encode(Buffer.from(hex, 'hex'), 'RFC4648-HEX')
}
return null
}
static b32h_to_hex (b32, { encoding = 'RFC4648-HEX' } = {}) {
if (/^[0-9A-Va-v=]+$/.test(b32)) {
return Buffer.from(base32decode(b32.toUpperCase(), encoding)).toString('hex')
static b32h_to_hex (b32h) {
if (/^[0-9A-Va-v=]+$/.test(b32h)) {
return Buffer.from(base32decode(b32.toUpperCase(), 'RFC4648-HEX')).toString('hex')
}
return null
}
@ -1453,14 +1472,17 @@ class TicCrypto {
static cid_to_cosh ({ cid }) {
if (/^[Q|1]/.test(cid)) {
return this.b58_to_hex(cid).slice(4)
} else if (/^b/.test(cid)) {
} else if (/^[b|B]/.test(cid)) {
return this.b32_to_hex(cid.substr(1)).slice(8)
} else if (/^z/.test(cid)) {
return this.b58_to_hex(cid.substr(1)).slice(8)
} else if (/^[m|M|u|U]/.test(cid)) {
return Buffer.from(cid.substr(1), 'base64').toString('hex')
}
}
static cosh_to_cid ({ cosh, cidBase = 'b32', cidVersion = 1, cidCodec = 'raw', cidAlgo = 'sha256' }) {
// https://github.com/multiformats/multibase
const multibase = {
identity: 0x00,
b2: '0',
@ -1472,38 +1494,56 @@ class TicCrypto {
B32: 'B',
b32h: 'v',
B32h: 'V',
b32hp: 't',
B32hp: 'T',
b32p: 'c',
B32p: 'C',
b32z: 'h', // base32z, z-base-32
b36: 'k',
B36: 'K',
b64: 'm',
b64p: 'M',
b64u: 'u',
b64up: 'U',
b58: 'z',
}
// https://github.com/multiformats/multicodec
const multicodec = {
raw: '55',
dagpb: '70',
p2pkey: '72',
raw: '55',
}
const multialgo = {
identify: '00',
sha256: '12',
sha512: '13',
keccak256: '1b',
ripemd160: '1053',
md5: 'd5',
}
if (cidVersion === 0) {
return this.hex_to_b58(`${multialgo[cidAlgo]}${Number(cosh.length / 2).toString(16)}${cosh}`)
}
if (cidVersion === 1) {
let fullHex = `01${multicodec[cidCodec]}${multialgo[cidAlgo]}${Number(cosh.length / 2).toString(16)}${cosh}`
console.log(fullHex)
} else if (cidVersion === 1) {
const fullHex = `01${multicodec[cidCodec]}${multialgo[cidAlgo]}${Number(cosh.length / 2).toString(16)}${cosh}`
let converted = ''
if (cidBase === 'b32') {
return (
multibase[cidBase] +
this.hex_to_b32(fullHex)
.toLowerCase()
.replace(/=/g, '')
)
converted = this.hex_to_b32(fullHex)
.toLowerCase()
.replace(/=/g, '')
} else if (cidBase === 'B32') {
converted = this.hex_to_b32(fullHex)
.toUpperCase()
.replace(/=/g, '')
} else if (cidBase === 'b58') {
return multibase[cidBase] + this.hex_to_b58(fullHex)
converted = this.hex_to_b58(fullHex)
} else if (cidBase === 'b64p') {
converted = Buffer.from(fullHex, 'hex').toString('base64')
} else if (cidBase === 'b64') {
converted = Buffer.from(fullHex, 'hex')
.toString('base64')
.replace(/=/g, '')
}
return multibase[cidBase] + converted
}
}
}