Rumah >pembangunan bahagian belakang >Golang >Tidak dapat mengesahkan tandatangan ECDSA yang dijana Go menggunakan JS

Tidak dapat mengesahkan tandatangan ECDSA yang dijana Go menggunakan JS

王林
王林ke hadapan
2024-02-10 20:03:07875semak imbas

无法使用 JS 验证 Go 生成的 ECDSA 签名

Editor PHP Xiaoxin menghadapi masalah apabila menggunakan bahasa Go untuk menjana tandatangan ECDSA, iaitu, JS tidak boleh digunakan untuk pengesahan. Penyelesaian kepada masalah ini ialah menambah beberapa medan tambahan pada kod Go untuk memastikan ketepatan tandatangan. Dengan membuat beberapa pengubahsuaian pada kod Go, kami boleh menyelesaikan masalah ini dan membolehkan JS mengesahkan tandatangan ECDSA yang dijana oleh Go dengan betul. Artikel ini akan memperkenalkan anda kepada penyelesaian dan langkah khusus secara terperinci.

Isi soalan

Saya menghadapi masalah kecil, (andaian saya ada perkara kecil yang menahan saya, tetapi saya tidak tahu apa), seperti yang dinyatakan dalam tajuk.

Saya akan mulakan dengan menggariskan perkara yang saya lakukan dan kemudian menyediakan semua yang saya ada.

Gambaran Keseluruhan Projek

Saya menggunakan kekunci SHA-256 对文件进行哈希处理,并使用 ECDSA P-256 dalam apl mudah alih saya untuk menandatangani cincang pada bahagian belakang. Dan kemudian ini berterusan dan seterusnya. Jika pengguna mahu, dia boleh mengesahkan integriti fail dengan mencincang fail sekali lagi dan mencari cincang dan mendapatkan cincang, beberapa metadata dan tandatangan.

Untuk mengesahkan bahawa data telah diserahkan kepada permohonan saya dan bukan pihak ketiga (cincang kekal dalam rantaian blok, tetapi itu tidak penting untuk masalah ini), aplikasi akan cuba mengesahkan tandatangan menggunakan kunci awam. Ini berfungsi sangat baik.

Sekarang saya mahu menambah pilihan ini pada laman web saya juga tetapi masalahnya ialah. Jika saya menggunakan jsrsasignwebcrypto api, tandatangan saya tidak sah.

Data

  • Contoh tandatangan: 3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066 ee127cfb7c0ad369521459d00
  • Kunci awam:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END PUBLIC KEY-----
  • Hash: bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942

Skrip

kod JS
<code>const validHash = document.getElementById("valid-hash");
const locationEmbedded = document.getElementById("location-embedded")
const signatureValid = document.getElementById("valid-sig")
const fileSelector = document.getElementById('file-upload');
const mcaptchaToken = document.getElementById("mcaptcha__token")
const submission = document.getElementById("submission")
let publicKey;

fileSelector.addEventListener("change", (event) => {
    document.getElementsByClassName("file-upload-label")[0].innerHTML = event.target.files[0].name
})
submission.addEventListener('click', async (event) => {
    let token = mcaptchaToken.value
    if (token == null || token == "") {
        alert("Please activate the Captcha!")
        return
    }
    const fileList = fileSelector.files;
    if (fileList[0]) {
        const file = fileList[0]
        const fileSize = file.size;
        let fileData = await readBinaryFile(file)
        let byteArray = new Uint8Array(fileData);
        const bytes = await hashFile(byteArray)
        try {
            let resp = await callApi(toHex(bytes), token)
            validHash.innerHTML = "\u2713"

            const mediainfo = await MediaInfo({ format: 'object' }, async (mediaInfo) => { // Taken from docs
                mediaInfo.analyzeData(() => file.size, (chunkSize, offset) => {
                    return new Promise((resolve, reject) => {
                        const reader = new FileReader()
                        reader.onload = (event) => {
                            if (event.target.error) {
                                reject(event.target.error)
                            }
                            resolve(new Uint8Array(event.target.result))
                        }
                        reader.readAsArrayBuffer(file.slice(offset, offset + chunkSize))
                    })
                })
                try {
                    let tags = mediaInfo.media.track[0].extra
                    latitude = tags.LATITUDE
                    longitude = tags.LONGITUDE
                    if (latitude && longitude) {
                        locationEmbedded.innerHTML = "\u2713"
                    } else {
                        locationEmbedded.innerHTML = "\u2717"
                    }
                } catch (e) {
                    locationEmbedded.innerHTML = "\u2717"
                }
               
            })
            if (publicKey == undefined) {
                let req = await fetch("/publickey")
                if (req.ok) {
                    publicKey = await req.text()
                } else {
                    throw "Could not get public key"
                }
            }

            let signature = resp.data.comment
            if (signature == null || signature == "") {
                throw "No signature found"
            }
            //const timeStamps = resp.data.timestamps
            const hashString = resp.data.hash_string
            console.log(hashString)
            if (hashString !== toHex(bytes)) {
                validHash.innerHTML = "\u2717"
            } else {
                validHash.innerHTML = "\u2713"
            }
            const result = await validateSignature(publicKey, signature, hashString)
            console.log("Valid signature: " + result)
            if (result) {
                signatureValid.innerHTML = "\u2713"
            } else {
                signatureValid.innerHTML = "\u2717"
            }
            mcaptchaToken.value = ""
        } catch (e) {
            alert("Error: " + e)
            window.location.reload()
        }

    } else {
        alert("No file selected");
    }
});


function toHex(buffer) {
    return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join('');
}

async function callApi(hash, token) {
    const url = "/verify";
    let resp = await fetch(url, {
        headers: { "X-MCAPTCHA-TOKEN": token },
        method: "POST",
        body: JSON.stringify({ hash: hash })
    })
    if (resp.ok) {
        return await resp.json();
    } else {
        if (resp.status == 401) {
            throw resp.status
        } else {
            console.log(resp)
            throw "Your hash is either invalid or has not been submitted via the Decentproof App!"
        }
    }
}

async function hashFile(byteArray) {
    let hashBytes = await window.crypto.subtle.digest('SHA-256', byteArray);
    return new Uint8Array(hashBytes)
}

async function validateSignature(key, signature,hashData) {
    const importedKey = importPublicKey(key)
    const sig = new KJUR.crypto.Signature({"alg": "SHA256withECDSA"});
    sig.init(importedKey)
    sig.updateHex(hashData);
    return sig.verify(signature)
}

function readBinaryFile(file) {
    return new Promise((resolve, reject) => {
        var fr = new FileReader();
        fr.onload = () => {
            resolve(fr.result)
        };
        fr.readAsArrayBuffer(file);
    });
}

function importPublicKey(pem) {
    console.log(pem)
    return KEYUTIL.getKey(pem); 
  }

  function hexToBytes(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2)
    bytes.push(parseInt(hex.substr(c, 2), 16));
    return new Uint8Array(bytes);
}
</code>
Kod pengesahan aplikasi (Flutter Dart)
<code>import 'dart:convert';
import 'package:convert/convert.dart';
import 'dart:typed_data';
import 'package:basic_utils/basic_utils.dart';
import 'package:decentproof/features/verification/interfaces/ISignatureVerifcationService.dart';
import 'package:pointycastle/asn1/asn1_parser.dart';
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
import 'package:pointycastle/signers/ecdsa_signer.dart';

class SignatureVerificationService implements ISignatureVerificationService {
  late final ECPublicKey pubKey;
  SignatureVerificationService() {
    pubKey = loadAndPrepPubKey();
  }

  final String pemPubKey = """
-----BEGIN EC PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END EC PUBLIC KEY-----
""";

  ECSignature loadAndConvertSignature(String sig) {
    //Based on: https://github.com/bcgit/pc-dart/issues/159#issuecomment-1105689978
    Uint8List bytes = Uint8List.fromList(hex.decode(sig));
    ASN1Parser p = ASN1Parser(bytes);
    //Needs to be dynamic or otherwise throws odd errors
    final seq = p.nextObject() as dynamic;
    ASN1Integer ar = seq.elements?[0] as ASN1Integer;
    ASN1Integer as = seq.elements?[1] as ASN1Integer;
    BigInt r = ar.integer!;
    BigInt s = as.integer!;
    return ECSignature(r, s);
  }

  ECPublicKey loadAndPrepPubKey() {
    return CryptoUtils.ecPublicKeyFromPem(pemPubKey);
  }

  @override
  bool verify(String hash, String sig) {
    ECSignature convertedSig = loadAndConvertSignature(sig);
    final ECDSASigner signer = ECDSASigner();
    signer.init(false, PublicKeyParameter<ECPublicKey>(loadAndPrepPubKey()));
    Uint8List messageAsBytes = Uint8List.fromList(utf8.encode(hash));
    return signer.verifySignature(messageAsBytes, convertedSig);
  }
}
</code>
Skrip penjanaan kunci (Go)
<code>package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/x509"
    "encoding/pem"
    "flag"
    "fmt"
    "os"
)

func main() {
    var outPutDir string
    var outPutFileName string
    flag.StringVar(&outPutDir, "out", "./", "Output directory")
    flag.StringVar(&outPutFileName, "name", "key", "Output file name e.g key, my_project_key etc. Adding .pem is not needed")
    flag.Parse()
    key, err := generateKeys()
    if err != nil {
        fmt.Printf("Something went wrong %d", err)
        return
    }
    err = saveKeys(key, outPutDir, outPutFileName)
    if err != nil {
        fmt.Printf("Something went wrong %d", err)
        return
    }
    fmt.Printf("Keys generated and saved to %s%s.pem and %spub_%s.pem", outPutDir, outPutFileName, outPutDir, outPutFileName)

}

func generateKeys() (*ecdsa.PrivateKey, error) {
    return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
func saveKeys(key *ecdsa.PrivateKey, outPutDir string, outPutFileName string) error {
    bytes, err := x509.MarshalECPrivateKey(key)
    if err != nil {
        return err
    }
    privBloc := pem.Block{Type: "EC PRIVATE KEY", Bytes: bytes}
    privKeyFile, err := os.Create(outPutDir + outPutFileName + ".pem")
    if err != nil {
        return err
    }
    defer privKeyFile.Close()
    err = pem.Encode(privKeyFile, &privBloc)
    if err != nil {
        return err
    }

    bytes, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
    pubBloc := pem.Block{Type: "EC Public KEY", Bytes: bytes}
    pubKeyFile, err := os.Create(outPutDir + "pub_" + outPutFileName + ".pem")
    if err != nil {
        return err
    }
    defer pubKeyFile.Close()
    err = pem.Encode(pubKeyFile, &pubBloc)
    if err != nil {
        return err
    }

    return nil
}
</code>

Pautan ke skrip pembalut tandatangan: pautan

Percubaan saya

  • Saya telah menguji menggunakan dua pasangan kunci baharu (dan pustaka anda) untuk menandatangani beberapa sampel data untuk melihat sama ada sesuatu dalam kunci itu salah dan tidak
  • Saya telah menguji data yang ditandatangani menggunakan pustaka anda dan kunci peribadi saya dan mengesahkannya menggunakan kunci awam saya untuk melihat sama ada kunci peribadi saya rosak dan ia tidak berlaku
  • Saya telah mencuba segala-galanya dengan API Penyulitan Rangkaian tetapi tidak berjaya
  • Saya cuba memuatkan ECDSA 公钥并使用 new KJUR.crypto.ECDSA({"curve":"secp256r1"}).verifyHex(hash,signature,pubKeyHex) dengan data di atas dan ia tidak berfungsi (hanya diuji dalam konsol penyemak imbas)
  • Saya menggunakan Firefox dan Safari untuk melihat sama ada terdapat sebarang perbezaan, tetapi tiada apa yang berubah
  • Saya cuba menghantar hash sebagai rentetan melalui sig.updateString(hashData) tetapi tidak berjaya
  • Ada beberapa perubahan lain yang lebih kecil
  • Membandingkan cincangan, r&s+tandatangan pada tapak web dan tapak web apl, semuanya seperti yang diharapkan.
  • Saya telah menjejaki keseluruhan proses dari bahagian hadapan ke bahagian belakang dan tiada data telah berubah

Percubaan terakhir saya adalah yang ke-4 kerana sekurang-kurangnya dari pemahaman saya jika anda menggunakan cara biasa (yang saya lakukan dalam skrip di atas) data anda akan dicincang, setakat yang saya bimbang, yang bertentangan dengan produktif, kerana Saya sudah mendapat nilai cincang, jadi jika ia dicincang dua kali, sudah tentu ia tidak akan sepadan. Tetapi atas sebab yang saya tidak faham, saya masih mendapat palsu sebagai nilai pulangan.

Satu pemikiran terakhir, jika menggunakan P-256 tandatangan, mungkinkah masalahnya ialah perpustakaan go ecdsa memotong mesej kepada 32 bait? Mungkin tidak begitu dalam JS?

Penyelesaian

Pengesahan dalam kod JavaScript tidak serasi dengan kod Dart atas dua sebab:

  • 首先,JavaScript代码使用KJUR.crypto.Signature (),它隐式对数据进行哈希处理。由于数据已经被散列,这会导致双重散列。在 Dart 方面,不会发生隐式哈希(因为 ECDSASigner())。
    为了避免 JavaScript 端的隐式哈希并与 Dart 代码兼容,KJUR.crypto.ECDSA() 可以用来代替 KJUR.crypto.Signature()
  • 其次,JavaScript 代码中的 updateHex() 对十六进制编码的哈希值执行十六进制解码,而在 Dart 代码中,十六进制编码的哈希值是 UTF-8 编码的。
    为了与 Dart 代码兼容,十六进制编码的哈希值在 JavaScript 代码中也必须采用 UTF-8 编码。

以下 JavaScript 代码解决了这两个问题:

(async () => {

var spki = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END PUBLIC KEY-----`; 
var pubkey = KEYUTIL.getKey(spki).getPublicKeyXYHex()
var pubkeyHex = '04' + pubkey.x + pubkey.y

var msgHashHex = ArrayBuffertohex(new TextEncoder().encode("bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942").buffer)
// var msgHashHex = ArrayBuffertohex(new TextEncoder().encode("bb5dbfcb5206282627254ab23397cda8").buffer); // works also since only the first 32 bytes are considered for P-256

var sigHex = "3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066ee127cfb7c0ad369521459d00"

var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'})
var verified = ec.verifyHex(msgHashHex, sigHex, pubkeyHex)
console.log("Verification:", verified)
 
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/10.4.0/jsrsasign-all-min.js"></script>

Atas ialah kandungan terperinci Tidak dapat mengesahkan tandatangan ECDSA yang dijana Go menggunakan JS. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:stackoverflow.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam