Подпишите ECSDA с помощью Python, проверьте с помощью JS

Я пытаюсь добиться полной противоположности это здесь, где мне нужно подписать полезную нагрузку в Python с помощью ECDSA и иметь возможность проверить подпись в JS.

Вот моя попытка, но я почти уверен, что упускаю что-то с преобразованием данных на одном или обоих концах.

(Типы ключей такие же, как в ответе на вопрос выше)

Я пробовал некоторые другие варианты, но пока ничего не получалось.

(Проверка на JS возвращает False)

Питон:

import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
    encode_dss_signature,
    decode_dss_signature
)
from cryptography.hazmat.primitives.serialization import load_der_public_key, load_pem_private_key, load_der_private_key

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
import base64
import json
from hashlib import sha256



def order_dict(dictionary):
    return {k: order_dict(v) if isinstance(v, dict) else v
            for k, v in sorted(dictionary.items())}


async def sign_payload(private_key, data):
    """
    Generate a signature based on the data using the local private key.
    """
    data = order_dict(data)

    # Separators prevent adding whitespaces around commas and :
    payload = json.dumps(data, separators=(',', ':')).encode('utf-8')

    # payload = base64.b64decode(json.dumps(data, separators=(',', ':')))

    sig = private_key.sign(
        payload,
        ec.ECDSA(hashes.SHA256())
    )

    return sig

JS:

export function b642ab(base64_string){
  return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}


export async function verifySignature(signature, public_key, data_in) {
  // Sorting alphabetically to avoid signature mismatch with BE
  const sorted_data_in = sortObjKeysAlphabetically(data_in);

  var dataStr = JSON.stringify(sorted_data_in)
  console.log(dataStr)

  var dataBuf = new TextEncoder().encode(dataStr)

  return window.crypto.subtle.verify(
    {
      name: "ECDSA",
      namedCurve: "P-256",
      hash: { name: "SHA-256" },
    },
    public_key,
    b642ab(utf8.decode(signature)),
    dataBuf
  );
}

await sign_payload(private_dsa_key, generated_payload)

В чем собственно вопрос/проблема? "но пока ничего не получалось." -> Как задать хороший вопрос? -> «Опишите проблему. «Это не работает» недостаточно описательно, чтобы помочь людям понять вашу проблему. Вместо этого расскажите другим читателям, каким должно быть ожидаемое поведение. Расскажите другим читателям, какова точная формулировка сообщения об ошибке и какая строка код производит его».

Andreas 10.04.2022 18:30

Ну тут цель проста, она либо проверяет правильно, либо нет. Я не уверен, где здесь путаница. В этом случае проверка не проходит.

Waelmas 10.04.2022 18:32

Извините, но вы, похоже, даже не разбираетесь в обсуждаемой теме. Я предоставил пример кода, и что-то мешает успешной проверке. Если вы проверите упомянутый вопрос, вы увидите, что здесь вы совершенно не по теме с вашими рекомендациями по "SO-руководству"...

Waelmas 10.04.2022 18:36

Нет фактического описания проблемы или сообщения об ошибке. Это просто дамп кода и бессмысленный «Я что-то упускаю» - SO работает не так.

Andreas 10.04.2022 18:39

Проверка проблемы и исследование документ У меня есть предположение, что переменная "databuf" может содержать неверные данные после кодирования и т. д. Пожалуйста, отладьте "сигнатуру", поскольку, скорее всего, проблема существует, и проверьте параметры EcdsaParams из документ, если они верны. или чего-то не хватает. Кроме того, плохое отношение аккаунта @Andreas Вопрос очень хороший и хорошо структурированный. Я думаю, что вы должны прочитать это внимательно, и, пожалуйста, будьте более вежливы.

Nicholas 10.04.2022 19:22
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
2
5
22
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная проблема в том, что оба кода используют разные форматы подписи:
sign_payload() в коде Python генерирует подпись ECDSA в формате ASN.1/DER. С другой стороны, API WebCrypto может обрабатывать только формат IEEE P1363.
Поскольку библиотека Python Cryptography намного удобнее, чем низкоуровневый API WebCrypto, имеет смысл выполнить преобразование в коде Python.

Следующий код Python основан на вашем коде, но дополнительно выполняет преобразование в формат IEEE P1363 в конце:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes
import base64
import json

#def order_dict(dictionary):
#    return {k: order_dict(v) if isinstance(v, dict) else v
#            for k, v in sorted(dictionary.items())}

def sign_payload(private_key, data):
    """
    Generate a signature based on the data using the local private key.
    """    
    
    #order_dict(data) # not considered!

    # Separators prevent adding whitespaces around commas and :
    payload = json.dumps(data, separators=(',', ':')).encode('utf-8')
    print(payload.decode('utf-8'))

    sig = private_key.sign(
        payload,
        ec.ECDSA(hashes.SHA256())
    )

    return sig

privateKeyPem = b'''-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrW9XiIs4/Kb0q8kl
TmF3oIwSn4NO3xAjs08F0lJ/5UOhRANCAAQykdP4c0ozvOOHHSNkMfLNCWRstXTG
TQf9MWjqB9PbeKyHnxuU82FisUjnVD9zO+QDAK0tnP/qzWf8zxoD0vVW
-----END PRIVATE KEY-----'''

privateKey = load_pem_private_key(privateKeyPem, password=None, backend=default_backend())
data = {"key1": "value1", "key2": "value2"}
signatureDER = sign_payload(privateKey, data)

# Convert signature format
(r, s) = decode_dss_signature(signatureDER)
signatureP1363 = r.to_bytes(32, byteorder='big') + s.to_bytes(32, byteorder='big')
print(base64.b64encode(signatureP1363).decode('utf-8'))

Возможный вывод:

{"key1":"value1","key2":"value2"}
KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg==

Обратите внимание, что для этого теста вызов order_dict(data) закомментирован, так как аналог JavaScript не был опубликован.


В коде JavaScript удалите utf8.decode() при декодировании подписи Base64. Кроме того, код в порядке. Следующий код JavaScript основан на вашем коде с добавлением ключа импорта:

(async () => {

     var x509pem = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0
xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
-----END PUBLIC KEY-----`
     var public_key = await importPublicKey(x509pem)
     var data_in = {
         key1: "value1",
         key2: "value2"
     }
     var signature = "KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg=="
     var verified = await verifySignature(signature, public_key, data_in)
     console.log(verified);
  
})();

function b642ab(base64_string){
     return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}

async function verifySignature(signature, public_key, data_in) {
    // Sorting alphabetically to avoid signature mismatch with BE
  
    //const sorted_data_in = sortObjKeysAlphabetically(data_in);
    //var dataStr = JSON.stringify(sorted_data_in)
    var dataStr = JSON.stringify(data_in)
    console.log(dataStr)

    var dataBuf = new TextEncoder().encode(dataStr)
  
    return window.crypto.subtle.verify(
        {
            name: "ECDSA",
            namedCurve: "P-256",
            hash: { name: "SHA-256" },
        },
        public_key,
        b642ab(signature),
        dataBuf
    );
}

async function importPublicKey(spkiPem) {   
    return await window.crypto.subtle.importKey(
        "spki",
        getSpkiDer(spkiPem),
        {name: "ECDSA", namedCurve: "P-256"},
        false,
        ["verify"]
    );
}

function getSpkiDer(spkiPem){
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    var pemContents = spkiPem.substring(pemHeader.length, spkiPem.length - pemFooter.length);
    var binaryDerString = window.atob(pemContents);
    return str2ab(binaryDerString); 
}

function str2ab(str) {
    const buf = new ArrayBuffer(str.length);
    const bufView = new Uint8Array(buf);
    for (let i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

Сообщение можно успешно проверить с помощью кода JavaScript, используя подпись, сгенерированную кодом Python.

Обратите внимание, что по аналогии с кодом Python sortObjKeysAlphabetically() был закомментирован из-за отсутствия реализации.

Я не могу отблагодарить вас достаточно! Вы причина, по которой я не отказался от криптографии, и источник бесценных знаний!

Waelmas 10.04.2022 20:48

Другие вопросы по теме