首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Node.js和WebCrypto之间的ECDSA签名似乎不兼容?

Node.js和WebCrypto之间的ECDSA签名似乎不兼容?
EN

Stack Overflow用户
提问于 2016-09-18 04:39:02
回答 3查看 6.1K关注 0票数 10

我使用下面的示例在Node.js中进行签名和验证:https://github.com/nodejs/node-v0.x-archive/issues/6904。验证在Node.js中成功,但在WebCrypto中失败。类似地,使用WebCrypto签名的消息无法在Node.js中验证。

下面是我使用WebCrypto - https://jsfiddle.net/aj49e8sj/验证从WebCrypto脚本生成的签名的代码。在Chrome 54.0.2840.27和Firefox 48.0.2中进行测试

代码语言:javascript
复制
// From https://github.com/nodejs/node-v0.x-archive/issues/6904
var keys = {
  priv: '-----BEGIN EC PRIVATE KEY-----\n' +
        'MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49\n' +
        'AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2\n' +
        'pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' +
        '-----END EC PRIVATE KEY-----\n',
  pub: '-----BEGIN PUBLIC KEY-----\n' +
       'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNh\n' +
       'B8i3mXyIMq704m2m52FdfKZ2pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' +
       '-----END PUBLIC KEY-----\n'
};
var message = (new TextEncoder('UTF-8')).encode('hello');

// Algorithm used in Node.js script is ecdsa-with-SHA1, key generated with prime256v1
var algorithm = {
    name: 'ECDSA',
    namedCurve: 'P-256',
    hash: {
        name: 'SHA-1'
    }
};

// Signature from obtained via above Node.js script
var sig64 = 'MEUCIQDkAtiomagyHFi7dNfxMrzx/U0Gk/ZhmwCqaL3TimvlswIgPgeDqgZNqfR5/FZZASYsczUAhGSXjuycLhWnvk20qKc=';

// Decode base64 string into ArrayBuffer
var b64Decode = (str) => Uint8Array.from(atob(str), x => x.charCodeAt(0));

// Get base64 string from public key
const key64 = keys.pub.split('\n')
    .filter(x => x.length > 0 && !x.startsWith('-----'))
    .join('');

// Convert to buffers
var sig = b64Decode(sig64);
var keySpki = b64Decode(key64);

// Import and verify
// Want 'Verification result: true' but will get 'false'
var importKey = crypto.subtle.importKey('spki', keySpki, algorithm, true, ['verify'])
    .then(key => crypto.subtle.verify(algorithm, key, sig, message))
    .then(result => console.log('Verification result: ' + result));

使用SHA-256代替SHA-1:Generating ECDSA signature with Node.js/crypto有类似问题的相关问题

我检查过的东西:

  • 我对Node.js密钥进行了解码,并验证了它们具有与通过WebCrypto生成的密钥相同的OID。这告诉我我用的是正确的曲线。
  • 沙-1被明确地标识为在两个位置使用的散列。
  • 在Node.js和WebCrypto中都明确地标识了ECDSA。

如何成功地验证从Node.js接收到的签名,反之亦然--在Node.js中验证从WebCrypto生成的签名?还是标准的实现微妙地不同,使得它们不兼容?

编辑:

  • uTaUWTfF+AjN3aPj0b5Z2d1HybUEpV/phv/P9RtfKaGXtcYnbgfO43IRg46rznG3/WnWwJ2sV6mPOEnEPR0vWw==签名(64个字节):WebCrypto
  • MEUCIQDkAtiomagyHFi7dNfxMrzx/U0Gk/ZhmwCqaL3TimvlswIgPgeDqgZNqfR5/FZZASYsczUAhGSXjuycLhWnvk20qKc=签名(71个字节):Node.js

验证的Node.js签名是DER编码的,而WebCrypto签名则不是。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-09-19 14:25:22

由于没有使用这些库中的任何一个,我不能肯定地说,但有一种可能是它们对签名不使用相同的编码类型。对于DSA/ECDSA,主要有两种格式:(用于P1363 )和DER (由OpenSSL使用)。

"Windows“格式是有一个预设的大小(由Q决定DSA和P为ECDSA (Windows不支持Char-2,但如果它支持它可能是M为Char-2 ECDSA)。然后rs都用0填充,直到它们达到这个长度为止。

在规模(Q)为3个字节的r = 0x305s = 0x810522的法律示例中,这个例子太小了:

代码语言:javascript
复制
// r
000305
// s
810522

对于"OpenSSL“格式,它是按照DER作为序列(整数(R),整数(S))的规则编码的,如下所示

代码语言:javascript
复制
// SEQUENCE
30
  // (length of payload)
  0A
  // INTEGER(r)
  02
    // (length of payload)
    02
    // note the leading 0x00 is omitted
    0305
  // INTEGER(s)
  02
    // (length of payload)
    04
    // Since INTEGER is a signed type, but this represented a positive number,
    // a 0x00 has to be inserted to keep the sign bit clear.
    00810522

或者,简单地说:

  • Windows:000305810522
  • OpenSSL:300A02020305020400810522

"Windows“格式总是均匀的,长度总是一样的。"OpenSSL“格式通常大约6字节,但在中间可以获得或丢失一个字节;因此有时是偶数,有时是奇数。

Base64 64-解码您的sig64值显示它正在使用DER编码。用WebCrypto生成几个签名;如果没有0x30开始,那么就会遇到IEEE/DER问题。

票数 7
EN

Stack Overflow用户

发布于 2019-01-02 00:03:21

许多小时后,终于找到了一个零依赖的解决方案!!

浏览器中:

代码语言:javascript
复制
      // Tip: Copy & Paste in the console for test.

      // Text to sign:
      var source = 'test';

      // Auxs
      function length(hex) {
        return ('00' + (hex.length / 2).toString(16)).slice(-2).toString();
      }

      function pubKeyToPEM(key) {
        var pem = '-----BEGIN PUBLIC KEY-----\n',
            keydata = '',
            bytes = new Uint8Array( key );

        for (var i = 0; i < bytes.byteLength; i++) {
          keydata += String.fromCharCode( bytes[ i ] );
        }

        keydata = window.btoa(keydata);

        while(keydata.length > 0) {
          pem += keydata.substring(0, 64) + '\n';
          keydata = keydata.substring(64);
        }

        pem = pem + "-----END PUBLIC KEY-----";

        return pem;
      }

      // Generate new keypair.
      window.crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, ["sign", "verify"])
            .then(function(keypair) {

              // Encode as UTF-8
              var enc = new TextEncoder('UTF-8'),
                  digest = enc.encode(source);
              
              // Sign with subtle
              window.crypto.subtle.sign({ name: "ECDSA", hash: {name: "SHA-1"} }, keypair.privateKey, digest)
                    .then(function(signature) {
                        signature = new Uint8Array(signature);

                        // Extract r & s and format it in ASN1 format.
                        var signHex = Array.prototype.map.call(signature, function(x) { return ('00' + x.toString(16)).slice(-2); }).join(''),
                            r = signHex.substring(0, 96),
                            s = signHex.substring(96),
                            rPre = true,
                            sPre = true;

                        while(r.indexOf('00') === 0) {
                          r = r.substring(2);
                          rPre = false;
                        }

                        if (rPre && parseInt(r.substring(0, 2), 16) > 127) {
                          r = '00' + r;
                        }

                        while(s.indexOf('00') === 0) {
                          s = s.substring(2);
                          sPre = false;
                        }

                        if(sPre && parseInt(s.substring(0, 2), 16) > 127) {
                          s = '00' + s;
                        }

                        var payload = '02' + length(r) + r +
                                      '02' + length(s) + s,
                            der = '30' + length(payload) + payload;
                        
                        // Export public key un PEM format (needed by node)
                        window.crypto.subtle.exportKey('spki', keypair.publicKey)
                                                   .then(function(key) {
                                                      var pubKey = pubKeyToPEM(key);
                                                      
                                                      console.log('This is pubKey -> ', pubKey);
                                                      console.log('This is signature -> ', der);                                                      
                                                   });
                                         
                        
                        // For test, we verify the signature, nothing, anecdotal.
                        window.crypto.subtle.verify({ name: "ECDSA", hash: {name: "SHA-1"} }, keypair.publicKey, signature, digest)
                              .then(console.log);
                    });
                    
            });

在节点中:

代码语言:javascript
复制
const crypto = require('crypto');

// ----------------------------------------------------------------------------

// Paste from browser!

var puKeyPem = '-----BEGIN PUBLIC KEY-----\n' +
               'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEmDubwJuORpMMoMnvv59W8tU8PxPChh75\n' +
               'vjlfVB2+tPY5KDy1I0ohz2US+2K1T/ROcDCSRAjyONRzzwVBm9S6bqbk3KuaT2KG\n' +
               'ikoe0KLfTeQtdEUyq8J0aEOKRXoCJLZq\n' +
               '-----END PUBLIC KEY-----';
               
var hexSign = '306402305df22aa5f4e7200b7c264c891cd3a8c5b4622c25872020832d5bb3d251773592020249a46a8349754dc58c47c4cbb7c9023053b929a98f5c8cccf2c1a4746d82fc751e044b1f76dffdf9ef73f73bee1499c5e20aadddda41e3373760b8b0f3c1bbb2';

// ----------------------------------------------------------------------------

var verifier = crypto.createVerify('sha1'),
    digest   = 'test';
    
verifier.update(digest);
verifier.end();

console.log(verifier.verify(puKeyPem, hexSign, 'hex'));

// ----------------------------------------------------------------------------

票数 7
EN

Stack Overflow用户

发布于 2020-09-29 22:59:36

现在,您可以生成兼容的密钥和签名(nodejs webcrypto),而无需修改它们。下面的示例适用于RSA,但ECDSA应该非常类似--本质是类型/格式/编码。

生成密钥对(nodejs):

代码语言:javascript
复制
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: {
    type: 'spki',
    format: 'der'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'der'
  }
});
    
console.log('PRIVATE', Buffer.from(privateKey).toString('base64'));
console.log('PUBLIC', Buffer.from(publicKey).toString('base64'));

签名信息(nodejs):

代码语言:javascript
复制
const signature = crypto.sign(
  'sha256',
  Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8'),
  {
    key: crypto.createPrivateKey({
      key: Buffer.from('...base64 encoded private key', 'base64'),
      format: 'der',
      type: 'pkcs8'
    }),
    padding: crypto.constants.RSA_PKCS1_PADDING,
    dsaEncoding: 'ieee-p1363'
  }
);

console.log('SIGNATURE', signature.toString('base64'));

验证消息(webcrypto) -您必须更改vanilla js中的缓冲区功能。

代码语言:javascript
复制
  (async () => console.log(await crypto.subtle.verify(
    { name: 'RSASSA-PKCS1-v1_5' },
    await crypto.subtle.importKey(
      'spki',
      Buffer.from('...base64 encoded public key', 'base64'),
      { name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'} },
      false,
      [ 'verify' ]
    ),
    Buffer.from('...base64 encoded signature', 'base64'),
    Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8')
  )))();
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39554165

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档