为避免 nodejs 的 crypto 的 sign 产生的签名不固定,换用 eccrypto 的 sign 做为默认。

This commit is contained in:
陆柯 2020-02-26 09:35:22 +08:00
parent 354a90a8ad
commit cbd46d62a7

View File

@ -66,14 +66,14 @@ module.exports = {
return false
}
,
isSecword(secword){
isSecword(secword){ // 注意 not all 12 words combinations are valid for both bitcore and bip39. Must be generated automatically. 另外实际上bitcore和bip39对12, 15, 18, ... 长度的合法助记词都返回 true。
//// for bitcore-mnemonic. 注意bitcore-mnemonic 对少于12词的会抛出异常很蠢。
// if (typeof secword==='string' && 12===secword.split(/ +/).length)
// return BitcoreMnemonic.isValid(secword)
// else
// return false
//// for bip39. 注意bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false所以不能直接 bip39.validateMnemonic(secword)
//// for bip39. 注意bip39对当前defaultWordlist之外其他语言的合法 mnemonic 也返回 false,这一点不如 bitcore-mnemonic. 所以不能直接 bip39.validateMnemonic(secword)
if (typeof secword==='string' && 12===secword.split(/ +/).length)
return true
else
@ -94,7 +94,7 @@ module.exports = {
}
,
isSignature(signature){
return /^[a-fA-F0-9]{128,144}$/.test(signature)
return /^[a-fA-F0-9]{128,144}$/.test(signature) && (signature.length % 2 === 0) // 128 for nacl, 140/142/144 for crypto and eccrypto in der format.
}
,
async encrypt(data, {keytype, key, input, output, cipher}={}){
@ -145,14 +145,22 @@ module.exports = {
}
,
async sign(data, seckey, option={}) { // data can be string or buffer or object, results are the same
if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) { // 纯 crypto
let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem')
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let outputEncoding=(option.output==='buf')?undefined:(my.OUTPUT_LIST.indexOf(option.output)>=0?option.output:my.OUTPUT)
let signer=crypto.createSign(hasher)
signer.update(this.hash(data, option)).end()
let signature = signer.sign(seckeyPEM, 'hex')
return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140~144 hex) 但都可以通过 verify。有一次我竟然徒手修改出一个新签名也通过验证。
if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===64) {
if (option.tool==='crypto') { // 纯 crypto
let seckeyPEM = await new keyman.Key('oct', this.hex2buf(seckey), {namedCurve:'P-256K'}).export('pem') // 私钥导出的der格式为144字节。
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let signer=crypto.createSign(hasher)
signer.update(this.hash(data, option)).end()
let signature = signer.sign(seckeyPEM, 'hex')
return signature // 发现同样的输入,每次调用会生成不同的 signature, 且长度不定(140,142,144 hex) 但都可以通过 verify。
}else if (option.tool==='nacl') {
// 这样不行无法和verify共享一套公私钥。
// let naclSeckey = this.buf2hex(nacl.sign.keyPair.fromSeed(seckey).seckey)
// return await this.sign(data, naclSeckey, option)
}else { // default to eccrypto因为它对同一组data,seckey生成的签名是固定的观察到hex长度为140或142是der格式。
let signature = await eccrypto.sign(Buffer.from(seckey,'hex'), crypto.createHash('sha256').update(data).digest())
return signature.toString('hex')
}
}
if (this.isHashable(data) && this.isSeckey(seckey) && seckey.length===128) { // 使用nacl的签名算法。注意nacl.sign需要的seckey是64字节=128字符。
option.output='buf' // 哈希必须输出为 buffer
@ -164,15 +172,29 @@ module.exports = {
}
,
async verify (data, signature, pubkey, option={}) { // data could be anything, but converts to string or remains be Buffer/TypedArray/DataView
if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length>=140){ // 纯 crypto
let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem')
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let verifier = crypto.createVerify(hasher)
verifier.update(this.hash(data, option)).end() // end() 在 nodejs 12 里返回verifier自身但在浏览器里返回 undefined因此不能串联运行。
let verified = verifier.verify(pubkeyPEM, signature, 'hex')
return verified
if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length>=140){
if (option.tool==='crypto') { // 纯 crypto
let pubkeyPEM = await new keyman.Key('oct', this.hex2buf(pubkey), {namedCurve:'P-256K'}).export('pem') // 公钥导出的der格式为88字节。经测试同一对压缩和非压缩公钥得出的结果一模一样。
let hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
let verifier = crypto.createVerify(hasher)
verifier.update(this.hash(data, option)).end() // end() 在 nodejs 12 里返回verifier自身但在浏览器里返回 undefined因此不能串联运行。
let verified = verifier.verify(pubkeyPEM, signature, 'hex') // 如果给signature添加1位hexcrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。
return verified
}else if ('nacl'===option.tool) {
// 这样不行无法和sign共享一套公私钥
// let naclPubkey = nacl.sign.keyPair.fromSeed()
}else { // 默认使用 eccrypto
try {
await eccrypto.verify(Buffer.from(pubkey, 'hex'),
crypto.createHash('sha256').update(data).digest(),
Buffer.from(signature, 'hex')) // 如果给signature添加1位hexeccrypto 的 verify结果也是true! 估计因为一位hex不被转成字节。
return true
}catch(exception){
return false
}
}
}
if (signature.length===128){ // nacl
if (this.isHashable(data) && this.isSignature(signature) && this.isPubkey(pubkey) && signature.length===128){ // nacl
option.output='buf' // 哈希必须输出为 buffer
let bufHash=this.hash(data, option)
let bufSignature = Buffer.from(signature, 'hex')
@ -180,7 +202,7 @@ module.exports = {
let verified = nacl.sign.detached.verify(bufHash, bufSignature, bufPubkey)
return verified
}
return null
return false
}
,
pass2keypair(pass, option){ // 如果使用其他机制例如密码、随机数不使用secword也可生成keypair
@ -188,7 +210,7 @@ module.exports = {
option=option||{}
option.hasher=my.HASHER_LIST.indexOf(option.hasher)>=0?option.hasher:my.HASHER
var hashBuf = crypto.createHash(option.hasher).update(pass).digest()
var keypair = nacl.sign.keyPair.fromSeed(hashBuf)
var keypair = nacl.sign.keyPair.fromSeed(hashBuf) // nacl的seed要求是32字节
return {
hash: hashBuf.toString('hex'),
pubkey: Buffer.from(keypair.publicKey).toString('hex'), // 测试过 不能直接keypair.publicKey.toString('hex')不是buffer类型
@ -198,6 +220,14 @@ module.exports = {
return null
}
,
entropy2secword(entropy){ // entropy could be hex string or buffer. Byte length could be of 16, 20, 24, 28, ... which outputs mnemonic of length 12, 15, 18, 21, ...
return bip39.entropyToMnemonic(entropy) // results are the same for the same entropy.
}
,
secword2entropy(secword){ // secword could be of length 12, 15, 18, ... which outputs hex of length 32, 40, ...
return bip39.mnemonicToEntropy(secword) // results are the same for the same secword.
}
,
secword2keypair(secword, option){
// option.coin 币种;
// option.passphase 密码,默认为空;
@ -256,6 +286,7 @@ module.exports = {
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
let kp=this.secword2keypair(secword, option)
if (kp) {
kp.secword=secword
kp.address=this.seckey2address(kp.seckey, option)
return kp
}
@ -448,7 +479,7 @@ module.exports = {
return bip39.generateMnemonic()
}
,
randomSeckey(option){ // todo: 使用 crypto.randomBytes(size)
randomSeckey(option){ // 跳过 secword 直接产生随机密钥
option=option||{}
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
if (option.tool==='nacl'){
@ -458,8 +489,7 @@ module.exports = {
}
}
,
randomKeypair(option){
option=option||{}
randomKeypair(option={}){
option.coin=my.COIN_LIST.indexOf(option.coin)>=0?option.coin:my.COIN
let kp
if (option.tool==='nacl'){
@ -482,6 +512,11 @@ module.exports = {
}
}
,
randomAccount(option={}){
let secword=this.randomSecword(option.lang)
return this.secword2account(secword, option)
}
,
randomString (length=6, alphabet) { // 长度为 length字母表为 alphabet 的随机字符串
alphabet = alphabet||"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$%^&*@"
var text = ''