目錄
問題內容
專案概述
資料
腳本
我的嘗試
解決方法
首頁 後端開發 Golang 無法使用 JS 驗證 Go 產生的 ECDSA 簽名

無法使用 JS 驗證 Go 產生的 ECDSA 簽名

Feb 10, 2024 pm 08:03 PM
go語言 區塊鏈 行動應用程式

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

php小編小新在使用Go語言產生ECDSA簽章時遇到了一個問題,即無法使用JS進行驗證。這個問題的解決方法是在Go程式碼中添加一些附加的字段,以確保簽名的正確性。透過對Go程式碼進行一些修改,我們可以解決這個問題,並使得JS能夠正確驗證Go產生的ECDSA簽章。這篇文章將為您詳細介紹具體的解決方法和步驟。

問題內容

我遇到了一個小問題,(我的假設是有一件小事情阻礙了我,但我不知道是什麼),如標題中所述。

我將首先概述我正在做的事情,然後提供我所擁有的一切。

專案概述

我在行動應用程式中使用 SHA-256 對檔案進行雜湊處理,並使用 ECDSA P-256 金鑰在後端對雜湊進行簽署。然後這種情況就一直持續下去。如果用戶需要,他可以透過再次散列文件並查找散列並獲取散列、一些元資料和簽名來驗證文件的完整性。

為了驗證資料已提交給我的應用程式而不是第三方(雜湊值保留在區塊鏈中,但這對於此問題並不重要),應用程式將嘗試使用公鑰驗證簽章。這工作很好。

現在我也想將此選項新增到我的網站,但問題是。如果我使用 jsrsasignwebcrypto api,我的簽章無效。

資料

  • 簽名示例:3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066 ee127cfb7c0ad369521459d00
  • 公鑰:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ
c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==
-----END PUBLIC KEY-----
登入後複製
  • 雜湊值:bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942

腳本

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>
登入後複製
應用驗證碼(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>
登入後複製
金鑰產生腳本(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>
登入後複製

連結到簽章包裝器腳本:連結

我的嘗試

  • 我已經使用兩個新的密鑰對(和您的庫)進行了測試,以簽署一些示例數據,以查看密鑰中的內容是否錯誤,事實並非如此
  • 我已經使用您的庫和我的私鑰測試了簽名數據,並使用我的公鑰對其進行了驗證,以查看我的私鑰是否已損壞,事實並非如此
  • 我已經嘗試了網路加密 API 的全部操作,但沒有成功
  • 我嘗試載入ECDSA 公鑰並使用new KJUR.crypto.ECDSA({"curve":"secp256r1"}).verifyHex(hash,signature,pubKeyHex) 與上述數據,它沒有不起作用(僅在瀏覽器控制台中測試)
  • 我使用了 Firefox 和 Safari 來查看是否有任何差異,但沒有改變任何內容
  • 我嘗試透過 sig.updateString(hashData) 將雜湊值作為字串傳遞,但沒有成功
  • 還有其他一些較小的變化
  • 比較網站和應用程式網站上的雜湊、r & s 簽名,一切都符合預期。
  • 我已經從前端到後端追蹤了整個過程,沒有資料發生變化

我的最後一次嘗試是第四次嘗試,因為至少從我的理解來看,如果您使用常規方式(我在上面的腳本中所做的),您的資料會被散列,就我而言,這是相反的富有成效,因為我已經得到了哈希值,所以如果它被哈希兩次,當然,它不會匹配。但是由於我不明白的原因,我仍然得到 false 作為回傳值。

最後一個想法,如果使用 P-256 簽名,問題是否可能是 go ecdsa 庫將訊息截斷為 32 個位元組?也許在 JS 中則不然?

解決方法

JavaScript 程式碼中的驗證與 Dart 程式碼不相容,原因有兩個:

  • 首先,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>
登入後複製

以上是無法使用 JS 驗證 Go 產生的 ECDSA 簽名的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1665
14
CakePHP 教程
1424
52
Laravel 教程
1322
25
PHP教程
1270
29
C# 教程
1250
24
幣安怎麼加倉 幣安怎麼加倉 May 07, 2025 pm 07:06 PM

在加密貨幣交易中,加倉是一個重要的策略,旨在通過增加持倉量來增強潛在的盈利機會。幣安作為全球領先的加密貨幣交易平台,提供了多種方式讓用戶能夠在其平台上進行加倉操作。本文將詳細介紹在幣安上如何加倉,包括不同方法的具體步驟和需要注意的事項。

2025最安全交易所TOP5:黑U避坑指南,資金100%保命法則 2025最安全交易所TOP5:黑U避坑指南,資金100%保命法則 May 08, 2025 pm 08:27 PM

在加密貨幣交易領域,交易所的安全性始終是用戶關注的重點。 2025年,經過多年的發展和演變,一些交易所憑藉其卓越的安全措施和用戶體驗脫穎而出。本文將詳細介紹2025年最安全的五大交易所,並提供如何避開黑U(黑客攻擊用戶)的實用指南,確保您的資金100%安全。

歐意OKX6.118.0版本最新下載教程 歐意OKX6.118.0版本最新下載教程 May 07, 2025 pm 06:51 PM

歐意OKX6.118.0版本最新下載教程:1、點擊文章中快捷鏈接;2、點擊下載即可(如果是網頁用戶請先進行信息註冊)。最新安卓版本v6.118.0優化了部分功能和體驗,讓交易更容易。立即更新App,感受更加極致的交易體驗。

2025幣安Binance交易所最新登錄入口 2025幣安Binance交易所最新登錄入口 May 07, 2025 pm 07:03 PM

幣安(Binance)作為全球領先的加密貨幣交易所,始終致力於為用戶提供安全、便捷的交易體驗。隨著時間的推移,幣安不斷優化其平台功能和用戶界面,以滿足用戶不斷變化的需求。 2025年,幣安推出了新的登錄入口,旨在進一步提升用戶體驗。

2025年十大數字虛擬幣交易APP排行 十大數字幣交易所app匯總 2025年十大數字虛擬幣交易APP排行 十大數字幣交易所app匯總 May 08, 2025 pm 05:24 PM

2025年十大數字虛擬幣交易APP排行:1. Binance:全球領先,提供高效交易和多種金融產品。 2. OKX:創新多樣,支持多種交易類型。 3. Huobi:穩定可靠,服務優質。 4. Coinbase:新手友好,界面簡潔。 5. Kraken:專業交易者首選,工具強大。 6. Bitfinex:高效交易,交易對豐富。 7. Bittrex:安全合規,監管合作。

2025幣安交易所最新入口地址 2025幣安交易所最新入口地址 May 07, 2025 pm 07:00 PM

幣安(Binance)作為全球領先的加密貨幣交易所,始終致力於為用戶提供安全、便捷的交易體驗。隨著時間的推移,幣安不斷優化其平台功能和用戶界面,以滿足用戶不斷變化的需求。 2025年,幣安推出了新的登錄入口,旨在進一步提升用戶體驗。

ok交易所國內如何註冊? ok交易平台大陸新手註冊使用指南 ok交易所國內如何註冊? ok交易平台大陸新手註冊使用指南 May 08, 2025 pm 10:51 PM

在加密貨幣市場中,選擇一個可靠的交易平台是至關重要的。 OK交易平台作為全球知名的數字資產交易所,吸引了大量大陸新手用戶。本指南將詳細介紹如何在OK交易平台上進行註冊和使用,幫助新手用戶快速上手。

歐意OKX5.1最新版本下載 歐意OKX5.1最新版本下載 May 07, 2025 pm 06:48 PM

歐意OKX6.118.0版本最新下載教程:1、點擊文章中快捷鏈接;2、點擊下載即可(如果是網頁用戶請先進行信息註冊)。最新安卓版本v6.118.0優化了部分功能和體驗,讓交易更容易。立即更新App,感受更加極致的交易體驗。

See all articles