From 447a3eb4426d7159089f885168d4e601282f1fe5 Mon Sep 17 00:00:00 2001 From: Luk Lu Date: Thu, 12 May 2022 08:52:48 +0800 Subject: [PATCH] =?UTF-8?q?rename=20b64u=20to=20b64t;=20rename=20x2y=20?= =?UTF-8?q?=E4=B8=BA=20x=5Fto=5F;=20add=20b32=20=E7=9A=84=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 43 +++++++++++++------- index.js | 110 ++++++++++++++++++++++++++++++++------------------- package.json | 2 + test.js | 2 +- 4 files changed, 100 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index fd4c14f..f3ee494 100644 --- a/README.md +++ b/README.md @@ -57,40 +57,53 @@ const keyPair = crypto.generateKeyPairSync('rsa', { }) ``` -这样生成的 keyPair.privateKey 开头是 -----BEGIN ENCRYPTED PRIVATE KEY----- +这样生成的 keyPair.privateKey 开头是 -----BEGIN ENCRYPTED PRIVATE KEY----- + +如果直接 -如果直接 ``` crypto.privateEncrypt(kp.privateKey, Buffer.from('sdafasfdsaf')) ``` -会报错 + +会报错 + ``` Uncaught TypeError: Passphrase required for encrypted key ``` -所以要这样才行 + +所以要这样才行 + ``` crypto.privateEncrypt({key:kp.privateKey, passphrase:''}, Buffer.from('sdafasfdsaf')) ``` -我从 https://www.cnblogs.com/chyingp/p/nodejs-asymmetric-enc-dec.html 抄到一个 privateKey 可以直接使用,不需要 passphrase -返回 Buffer。每次结果都一样 - +我从 https://www.cnblogs.com/chyingp/p/nodejs-asymmetric-enc-dec.html 抄到一个 privateKey 可以直接使用,不需要 passphrase -这样生成的 keyPair.publicKey 开头是 -----BEGIN PUBLIC KEY----- +返回 Buffer。每次结果都一样 + +这样生成的 keyPair.publicKey 开头是 -----BEGIN PUBLIC KEY----- + +可以直接 -可以直接 ``` crypto.publicEncrypt(kp.publicKey, Buffer.from('sdafasfdsaf')) ``` -返回 Buffer。每次结果不一样 +返回 Buffer。每次结果不一样 - +似乎 crypto 一定要 rsa 公私钥才可以用加解密,ticCrypto.randomKeypair() 生成的 ecc 公私钥不行。 -似乎 crypto 一定要 rsa 公私钥才可以用加解密,ticCrypto.randomKeypair() 生成的 ecc 公私钥不行。 +而 eccrypto 和 eccrypto-js 可以用。eccrypto.generateKeyPair() 生成的和 ticCrypto.randomKeypair() 一样 -而 eccrypto 和 eccrypto-js 可以用。eccrypto.generateKeyPair() 生成的和 ticCrypto.randomKeypair() 一样 +eccrypto 在 windows 上的安装有麻烦,一来需要手工安装 OpenSSL 到 c:\openssl-win64\,二来 openssl 1.1.0 起把 libeay32.lib 改名为 libcrypto.dll,而 eccrypto 需要 c:\openssl-win64\lib\libeay32.lib,会报错 -eccrypto 在 windows 上的安装有麻烦,一来需要手工安装 OpenSSL 到 c:\openssl-win64\,二来 openssl 1.1.0 起把 libeay32.lib 改名为 libcrypto.dll,而 eccrypto 需要 c:\openssl-win64\lib\libeay32.lib,会报错 +eccrypto-js 在 devDependencies 里继承了 eccrypto,因此 npm i --production 即可 -eccrypto-js 在 devDependencies 里继承了 eccrypto,因此 npm i --production 即可 +base32 有多种字符集:[Base32 - Wikipedia](https://en.wikipedia.org/wiki/Base32) + +IPFS用的是 RFC4648字符集 + +- 从数到数符串:Number(数).toString(进制数),0x数.toString(进制数), 0b数.toString(进制数) +- 从数符串到数字:parseInt(str, 进制数) +- Buffer到数符串: Buffer.toString(编码方案例如'hex','base64',默认'utf8') +- 字符串到Buffer: Buffer.from(data, 编码方案如'hex','base64',默认'utf8') diff --git a/index.js b/index.js index 8667817..3041c2a 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,8 @@ const bip39 = require('bip39') // https://github.com/bitcoinjs/bip39 // 有更 const hdkey = require('hdkey') // https://github.com/cryptocoinjs/hdkey // 或者用 bitcore-mnemonic 或者 ethers 里的相同功能 // const bitcorelib = require('bitcore-lib') const secp256k1 = require('secp256k1') +const base32encode = require('base32-encode') +const base32decode = require('base32-decode') // 全部以hex为默认输入输出格式,方便人的阅读,以及方便函数之间统一接口 @@ -182,7 +184,7 @@ class TICrypto { // 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.hex2buf(key)), data) + let cipherobject = await eccrypto.encrypt(Buffer.from(this.hex_to_buf(key)), data) return cipherobject // 返回一个复杂的结构 {iv:Buffer, ciphertext:Buffer, ...}。对同样的key和data,每次返回的结果不一样 } else if (keytype === 'pwd') { // 对称加密 @@ -190,7 +192,7 @@ class TICrypto { let inputEncoding = my.INPUT_LIST.indexOf(input) >= 0 ? input : my.INPUT // 'utf8' by default, 'ascii', 'latin1' for string or ignored for Buffer/TypedArray/DataView let outputEncoding = output === 'buf' ? undefined : my.OUTPUT_LIST.indexOf(output) >= 0 ? output : my.OUTPUT // 'latin1', 'base64', 'hex' by default or 'buf' to Buffer explicitly const iv = crypto.randomBytes(16) - let encryptor = crypto.createCipheriv(my.CIPHER_LIST.indexOf(cipher) >= 0 ? cipher : my.CIPHER, this.hex2buf(this.hash(key)), iv) // cipher 和 key 的长度必须相同,例如 cipher 是 ***-192,那么 key 就必须是 192/8=24 字节 = 48 hex 的。 + let encryptor = crypto.createCipheriv(my.CIPHER_LIST.indexOf(cipher) >= 0 ? cipher : my.CIPHER, this.hex_to_buf(this.hash(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 @@ -198,10 +200,10 @@ class TICrypto { } } else if (keytype === 'seckey') { // 尚未走通,不能使用 ticCrypto 生成的 Elliptic curve 椭圆曲线算法公私钥,只能用 crypto.generateKeypairs() 生成的 rsa 公私钥 - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 return crypto.privateEncrypt(seckeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 } else if (keytype === 'pubkey') { - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') + 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。每次结果不一样。 } return null @@ -220,7 +222,7 @@ class TICrypto { // data 应当是 encrypt 输出的数据类型 if (tool === 'eccrypto') { try { - // eccrypto 只能接受 Buffer, 不接受 Uint8Array, 因为 eccrypto 需要调用 Buffer.compare 方法,不能在这里直接用 hex2buf + // 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 @@ -236,7 +238,7 @@ class TICrypto { let outputEncoding = output === 'buf' ? undefined : my.INPUT_LIST.indexOf(output) >= 0 ? output : my.INPUT // output (=input of encrypt) could be 'latin1', 'ascii', 'utf8' by default or 'buf' to Buffer explicitly let decryptor = crypto.createDecipheriv( my.CIPHER_LIST.indexOf(cipher) >= 0 ? cipher : my.CIPHER, - this.hex2buf(this.hash(key)), + this.hex_to_buf(this.hash(key)), Buffer.from(data.iv, 'hex') ) let decrypted = decryptor.update(data.ciphertext, inputEncoding, outputEncoding) @@ -246,10 +248,10 @@ class TICrypto { } } else if (keytype === 'seckey') { // 尚未走通,不能使用 ticCrypto 生成的 Elliptic curve 椭圆曲线算法公私钥 - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(key), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 return crypto.privateDecrypt(seckeyPEM, Buffer.from(data)) // 返回 Buffer。每次结果都一样。 } else if (keytype === 'pubkey') { - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(key), { namedCurve: 'P-256K' }).export('pem') + 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。每次结果不一样。 } return null @@ -279,7 +281,7 @@ class TICrypto { return signature.toString('hex') } else if (seckey.length === 64) { // 纯 crypto - let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 + let seckeyPEM = await new keyman.Key('oct', this.hex_to_buf(seckey), { namedCurve: 'P-256K' }).export('pem') // 私钥导出的der格式为144字节。 let signer = crypto.createSign(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) // 注意,不知为何,hasher必须含有'sha'才能完成签名,例如 sha1, sha256, sha512, sha3, RSA-SHA1, id-rsassa-pkcs1-v1_5-with-sha3-224, 其他都会报错。 signer.update(this.hash(data)).end() let signature = signer.sign(seckeyPEM, 'hex') @@ -322,7 +324,7 @@ class TICrypto { } } else if (signature.length >= 140) { // 纯 crypto // 发现大小写不影响 crypto 验签!都能通过 - let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), { namedCurve: 'P-256K' }).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 + let pubkeyPEM = await new keyman.Key('oct', this.hex_to_buf(pubkey), { namedCurve: 'P-256K' }).export('pem') // 公钥导出的der格式为88字节。经测试,同一对压缩和非压缩公钥得出的结果一模一样。 let verifier = crypto.createVerify(my.HASHER_LIST.indexOf(hasher) >= 0 ? hasher : my.HASHER) verifier.update(this.hash(data)).end() // end() 在 nodejs 12 里返回verifier自身,但在浏览器里返回 undefined,因此不能串联运行。 let verified = verifier.verify(pubkeyPEM, signature, 'hex') // 如果给signature添加1位hex,crypto 的 verify结果也是true! 估计因为一位hex不被转成字节。但减少1位会导致false @@ -554,13 +556,13 @@ class TICrypto { curve = my.CURVE_LIST.indexOf(curve) >= 0 ? curve : my.CURVE // 默认为 secp256k1 // return new crypto.createECDH(curve).setPrivateKey(seckey,'hex').getPublicKey('hex', compress===false?'uncompressed':'compressed') // ecdh.getPublicKey(不加参数) 默认为 'compressed'。用 HBuilderX 2.6.4 打包成ios或安卓 app 后 setPrivateKey() 报错:TypeError: null is not an object (evaluating 'this.rand.getBytes') // 从 nodejs 10.0 开始,还有 crypto.ECDH.convertKey 方法,更直接。但可惜,浏览器里不存在 crypto.ECDH。 - return this.buf2hex(secp256k1.publicKeyCreate(Buffer.from(seckey, 'hex'), compress !== false)) // 可用于浏览器。缺省输出压缩公钥,compress=false时输出非压缩公钥。 + return this.buf_to_hex(secp256k1.publicKeyCreate(Buffer.from(seckey, 'hex'), compress !== false)) // 可用于浏览器。缺省输出压缩公钥,compress=false时输出非压缩公钥。 // 或者 bitcorelib.PublicKey.fromPrivateKey(new bitcorelib.PrivateKey(seckey)).toString('hex') // 可用于浏览器 // 或者 const ecc = require('eccrypto') // if (compress===false){ - // return ecc.getPublic(this.hex2buf(seckey)).toString('hex') + // return ecc.getPublic(this.hex_to_buf(seckey)).toString('hex') // }else{ - // return ecc.getPublicCompressed(this.hex2buf(seckey)).toString('hex') + // return ecc.getPublicCompressed(this.hex_to_buf(seckey)).toString('hex') // } // 注意,Buffer.from(nacl.box.keyPair.fromSecretKey(Buffer.from(seckey,'hex')).publicKey).toString('hex') 得到的公钥与上面的不同 } else if (this.isSeckey(seckey) && seckey.length === 128) { @@ -685,7 +687,7 @@ class TICrypto { address = bs58check.encode(Buffer.from(prefix + position, 'hex')) // wallet import format return address } else { - // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64u(base64 for url) 地址。 + // 默认为 TIC。把纯位置转换为大小写敏感能自我验证的 b64t 地址。 let prefix switch (world) { // Base64: https://baike.baidu.com/item/base64 @@ -702,8 +704,8 @@ class TICrypto { prefix = '4c' } let checksum = this.hash(this.hash(prefix + position)).slice(0, 6) // 添加 checksum 使得能够检测大小写错误。[todo] 校验码里要不要包含 prefix? - // address = this.hex2eip55(prefix + position + checksum) // 前缀1节,位置20节,校验3节,共24节=48字符(能够完全转化为8个色彩),再转eip55。 - address = this.hex2b64u(prefix + position + checksum) // 实际采用 b64u (named by luk.lu as base 64 for url), 共 32字符。 + // address = this.hex_to_eip55(prefix + position + checksum) // 前缀1节,位置20节,校验3节,共24节=48字符(能够完全转化为8个色彩),再转eip55。 + address = this.hex_to_b64t(prefix + position + checksum) // 实际采用 b64t, 共 32字符。 return address } return null @@ -722,13 +724,13 @@ class TICrypto { // todo: 如果是大小写敏感的,进行有效性验证 return address.toLowerCase() } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address)) { // BTC - let hex = this.b58c2hex(address) + let hex = this.b58c_to_hex(address) if (hex) { return hex.slice(2) // 去除网络前缀 } } else if (/^[Tt][0-9a-zA-Z\._]{31}$/.test(address)) { // TIC // 格式合法 - let hex = this.b64u2hex(address) + let hex = this.b64t_to_hex(address) let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/) if (this.hash(this.hash(position)).slice(0, 6) === checksum) { return position @@ -750,13 +752,13 @@ class TICrypto { return 'ETH' } else if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{26,34}$/.test(address) && address.length !== 32) { // 格式合法。常见的是 33或34字符长度 - let prefixedPosition = this.b58c2hex(address) + let prefixedPosition = this.b58c_to_hex(address) if (prefixedPosition && prefixedPosition.length === 42) // 内容合法 return 'BTC' } else if (/^[Ttd][0-9a-zA-Z\._]{31}$/.test(address)) { // 格式合法 - let hex = Buffer.from(this.b64u_to_b64(address), 'base64').toString('hex') + let hex = Buffer.from(this.b64t_to_b64(address), 'base64').toString('hex') let [all, prefix, position, checksum] = hex.match(/^([\da-fA-F]{2})([\da-fA-F]{40})([\da-fA-F]{6})$/) // 内容合法 if (this.hash(this.hash(prefix + position)).slice(0, 6) === checksum) // [todo] 校验码里要不要包含 prefix? @@ -1181,7 +1183,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static buf2hex(buffer) { + static buf_to_hex(buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('') } @@ -1194,7 +1196,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static hex2buf(hex) { + static hex_to_buf(hex) { return new Uint8Array( hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16) @@ -1213,7 +1215,7 @@ class TICrypto { * bs58check 和 bs58 可接受string, Buffer, ArrayBuffer, Array (包括空字符串'', 各种内容的数组例如包含 undefined,{...},等等); * 不可接受 undefined, null, {...}, 等等,会返回 exception */ - static hex2b58c(hex) { + static hex_to_b58c(hex) { try { return bs58check.encode(Buffer.from(hex, 'hex')) } catch (exception) { @@ -1221,7 +1223,7 @@ class TICrypto { } } - static hex2b58(hex) { + static hex_to_b58(hex) { try { return bs58.encode(Buffer.from(hex, 'hex')) } catch (exception) { @@ -1237,7 +1239,7 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static b58c2hex(box) { + static b58c_to_hex(box) { try { return bs58check.decode(box).toString('hex') } catch (exception) { @@ -1245,7 +1247,7 @@ class TICrypto { } } - static b582hex(box) { + static b58_to_hex(box) { try { return bs58.decode(box).toString('hex') } catch (exception) { @@ -1256,45 +1258,71 @@ class TICrypto { /** * b64 字符串为 a-zA-Z0-9+/ * 其中,+ 和 / 会在 url query string 里被转成 %2B 和 %2F - * 因此定义 b64u (base64 for url),用 . 和 _ 替换。 - * (为何不用 -,因为 - 和空格一样导致 css white-space 自动换行。) + * 因此定义 b64t (base64 for tic),用 . 和 _ 替换。 + * (为何不用 base64url, 因为 base64url 把 + 变成 - 和空格一样导致 css white-space 自动换行。) * @param {*} b64 * @returns */ - static b64_to_b64u(b64='') { + static b64_to_b64t(b64='') { return b64.replace(/\+/g, '.').replace(/\//g, '_').replace(/=/g, '') } - static b64u_to_b64(b64u='') { - return b64u.replace(/\./g, '+').replace(/_/g, '/') + static b64t_to_b64(b64t='') { + return b64t.replace(/\./g, '+').replace(/_/g, '/') } /** - * 十六进制转b64u + * 十六进制转b64t * * @static * @param {*} hex * @return {*} * @memberof TICrypto */ - static hex2b64u(hex) { + static hex_to_b64t(hex) { if (/^[0-9a-fA-F]+$/.test(hex)) { - return this.b64_to_b64u(Buffer.from(hex, 'hex').toString('base64')) + return this.b64_to_b64t(Buffer.from(hex, 'hex').toString('base64')) } return null } /** - * b64u转16进制 + * b64t转16进制 * * @static - * @param {*} b64u + * @param {*} b64t * @return {*} * @memberof TICrypto */ - static b64u2hex(b64u) { - if (/^[0-9a-zA-Z\._]+$/.test(b64u)) { - return Buffer.from(this.b64u_to_b64(b64u), 'base64').toString('hex') + static b64t_to_hex(b64t) { + if (/^[0-9a-zA-Z\._]+$/.test(b64t)) { + return Buffer.from(this.b64t_to_b64(b64t), 'base64').toString('hex') + } + return null + } + + // https://en.wikipedia.org/wiki/Base32 + static hex_to_b32(hex, {encoding='RFC4648'}={}) { + if (/^[0-9a-fA-F]+$/.test(hex)) { + return base32encode(Buffer.from(hex, 'hex'), encoding) + } + return null + } + static b32_to_hex(b32, {encoding='RFC4648'}={}) { + if (/^[A-Za-z2-7=]+$/.test(b32)) { + return Buffer.from(base32decode(b32.toUpperCase(), encoding)).toString('hex') + } + return null + } + static hex_to_b32h(hex, {encoding='RFC4648-HEX'}={}) { + if (/^[0-9a-fA-F]+$/.test(hex)) { + return base32encode(Buffer.from(hex, 'hex'), encoding) + } + 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') } return null } @@ -1307,8 +1335,8 @@ class TICrypto { * @return {*} * @memberof TICrypto */ - static hex2eip55(hex) { - if (/^(0x)?[\da-fA-F]*$/.test(hex)) { + static hex_to_eip55(hex) { + if (/^(0x)?[\da-fA-F]+$/.test(hex)) { hex = hex.toLowerCase().replace('0x', '') let hash = keccak('keccak256').update(hex).digest('hex') let result = '' diff --git a/package.json b/package.json index 16f254f..23a422f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "base32-decode": "^1.0.0", + "base32-encode": "^1.2.0", "big-integer": "^1.6.48", "bip39": "^3.0.2", "bs58check": "^2.1.2", diff --git a/test.js b/test.js index 9e409f9..669e3d4 100644 --- a/test.js +++ b/test.js @@ -76,7 +76,7 @@ console.log('address = ', add) /////////////////////// keyutil let seckeyObject = new keyutil.Key('oct', Buffer.from(acc.seckey, 'hex'), {namedCurve:'P-256K'}) // {P-256 : secp256r1, P-384 : secp384r1, P-521 : secp521r1, P-256K : secp256k1} -let seckeyObject2 = new keyutil.Key('oct', tic.hex2buf(acc.seckey, 'hex'), {namedCurve:'P-256K'}) +let seckeyObject2 = new keyutil.Key('oct', tic.hex_to_buf(acc.seckey, 'hex'), {namedCurve:'P-256K'}) let seckeyPEM seckeyObject.export('pem').then(data=>seckeyPEM=data) let seckeyDER