ECDH для P-521 (Web Crypto Api) / secp521r1 (NodeJS Crypto) генерирует немного другой общий секрет

Я создал пару открытого и закрытого ключей с помощью ECDH из NodeJS.

function _genPrivateKey(curveName = "secp384r1", encoding = "hex") {
    const private_0 = crypto.createECDH(curveName);
    private_0.generateKeys();
    return private_0.getPrivateKey().toString(encoding);
}

ЧАСТНЫЙ КЛЮЧ БОБА

9d0c809d692c83c7d8d1355205dd78e679066fd9c15d12cdea3e1103b041873765264351e8939083b876d89d423301d8486a956da455ddbdc4a91ef60af7dd2325

ОТКРЫТЫЙ КЛЮЧ БОБА

[hex] 
0400d7a342e89f501e1cd8224e2463ef1ad057c9b64bf45c1d3627cc1f06c055c80f75c2013c27b63dc984b467ecfc5202cf9a126ef1f1487e92b9acfb52abaeb7022e01f4259918ca34f442214a31d1bad329f9be1c67ce98af6621f622e44887264e856ee8c664a51e56f24008c932ee1cb5514c02d03ba27f6b6a1cd0aa0f8eac261250

[jwk] {
  key_ops: [ 'deriveKey' ],
  ext: true,
  kty: 'EC',
  x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
  y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
  crv: 'P-521'
}

и ключи Alice с веб-страницы с помощью Web Crypto API.

const generateAlicesKeyPair = window.crypto.subtle.generateKey({
        name: "ECDH",
        namedCurve: "P-521"
    },
    false,
    ["deriveBits"]
);

ПУБЛИЧНЫЙ КЛЮЧ АЛИСЫ

04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609

Когда я пытаюсь получить shared key, происходит странная вещь, ключ имеет разные биты в конце.

NodeJS:

function _getSharedSecret(privateKey, publicKey, curveName = "secp521r1", encoding = "hex") {
    const private_0 = crypto.createECDH(curveName);
    private_0.setPrivateKey(privateKey, encoding);
    const _sharedSecret = private_0.computeSecret(publicKey, encoding);
    return _sharedSecret
};

API веб-криптографии

const sharedSecret = await window.crypto.subtle.deriveBits({
        name: "ECDH",
        namedCurve: "P-521",
        public: publicKey
    },
    privateKey, 
    521
);

Полученные результаты:


//NodeJS: 
0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[eb]

//WebCryptoAPI:

0089b99212c9348a6fd3aa78225a773a90ef45f57bbd10dc86d8e52fa26662c550b56d2368ee358ab240ceec10191b6cdd7d09bb0a8763ea48a487a5676ebdf7af[80]

Это происходит только с кривой P-521/secp521r1, но не с кривыми P-256/secp256r1 и P-384/secp384r1.


person jay    schedule 03.05.2021    source источник
comment
Не могли бы вы проверить результат, выбрав один и тот же закрытый ключ и распечатав точки для каждой стороны?   -  person kelalaka    schedule 04.05.2021


Ответы (1)


Проверка с помощью третьей библиотеки (Python, библиотека Cryptography) показывает, что результат NodeJS является правильным (т. е. заканчивающимся на 0xEB).

Координаты X и Y P-521 представлены 66 байтами (521 бит = 65 байтов + 1 бит) (см. здесь) . Общий секрет — это координата X точки кривой (s. здесь) и, следовательно, также имеет 66 байтов = 528 бит. Это значение должно быть указано в реализации WebCrypto API в deriveBits() как длина общего секрета.

Если вы указываете 521 бит, учитывается только самый старший бит (который установлен для 0xEB), остальные биты устанавливаются в 0, что приводит к значению 0x80.

Следующий код иллюстрирует это (обратите внимание, что скрипт не работает в Firefox, что предположительно является ошибкой):

(async () => {
    await getSharedSecret(521);
    await getSharedSecret(528);
})();

async function getSharedSecret(bits) {
    var bobPrivateKeyJwk = {   
        kty: "EC",
        crv: "P-521",
        x:'ANejQuifUB4c2CJOJGPvGtBXybZL9FwdNifMHwbAVcgPdcIBPCe2PcmEtGfs_FICz5oSbvHxSH6Suaz7UquutwIu',
        y:'AfQlmRjKNPRCIUox0brTKfm-HGfOmK9mIfYi5EiHJk6FbujGZKUeVvJACMky7hy1UUwC0Duif2tqHNCqD46sJhJQ',
        d: "AJ0MgJ1pLIPH2NE1UgXdeOZ5Bm_ZwV0Szeo-EQOwQYc3ZSZDUeiTkIO4dtidQjMB2EhqlW2kVd29xKke9gr33SMl",
        ext: true,
    }
    var alicePublicKeyBuffer = typedArray('04000eefa90c3de22e79e6742f807806a603059d16afaa9f1bc69f420050aae100d0006e6510fe17a8f6767fe1e69bada039175ef5a375e30af4085e4315cf7527655f00ed9a39552a5f9170cc7626c1f4584d0e6de17870e336bcc5b6e251e3ea2c7cd9633e1afe2f9aee5f9a7445d38218c20695cc7ba2a462b67ce39a060e6464133609');    
    var privateKey = await window.crypto.subtle.importKey(
        "jwk", 
        bobPrivateKeyJwk,
        { name: "ECDH", namedCurve: "P-521" },
        true, 
        ["deriveKey", "deriveBits"] 
    );
    var publicKey = await window.crypto.subtle.importKey(
        "raw", 
        alicePublicKeyBuffer.buffer,
        { name: "ECDH", namedCurve: "P-521"},
        true, 
        [] 
    );
    var sharedSecret = await window.crypto.subtle.deriveBits(
        { name: "ECDH", namedCurve: "P-521", public: publicKey },
        privateKey, 
        bits 
    );
    console.log("Bob's shared secret:\n", buf2hex(sharedSecret).replace(/(.{48})/g,'$1\n'));
}; 

function typedArray(hex) {
    return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { // from: https://stackoverflow.com/a/43131635
        return parseInt(h, 16)
    }))
}

function buf2hex(buffer) {
    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); // from: https://stackoverflow.com/a/40031979/9014097 
}

С кривыми P-256 и P-384 проблемы не возникает, потому что простое поле уже кратно 8 (32 байта и 48 байт соответственно).

person user 9014097    schedule 04.05.2021