php小編小新在使用Go語言產生ECDSA簽章時遇到了一個問題,即無法使用JS進行驗證。這個問題的解決方法是在Go程式碼中添加一些附加的字段,以確保簽名的正確性。透過對Go程式碼進行一些修改,我們可以解決這個問題,並使得JS能夠正確驗證Go產生的ECDSA簽章。這篇文章將為您詳細介紹具體的解決方法和步驟。
我遇到了一個小問題,(我的假設是有一件小事情阻礙了我,但我不知道是什麼),如標題中所述。
我將首先概述我正在做的事情,然後提供我所擁有的一切。
我在行動應用程式中使用 SHA-256
對檔案進行雜湊處理,並使用 ECDSA P-256
金鑰在後端對雜湊進行簽署。然後這種情況就一直持續下去。如果用戶需要,他可以透過再次散列文件並查找散列並獲取散列、一些元資料和簽名來驗證文件的完整性。
為了驗證資料已提交給我的應用程式而不是第三方(雜湊值保留在區塊鏈中,但這對於此問題並不重要),應用程式將嘗試使用公鑰驗證簽章。這工作很好。
現在我也想將此選項新增到我的網站,但問題是。如果我使用 jsrsasign
或 webcrypto
api,我的簽章無效。
3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066 ee127cfb7c0ad369521459d00
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZ c6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw== -----END PUBLIC KEY-----
bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942
<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>
<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>
<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>
連結到簽章包裝器腳本:連結
ECDSA
公鑰並使用new KJUR.crypto.ECDSA({"curve":"secp256r1"}).verifyHex(hash,signature,pubKeyHex)
與上述數據,它沒有不起作用(僅在瀏覽器控制台中測試)sig.updateString(hashData)
將雜湊值作為字串傳遞,但沒有成功我的最後一次嘗試是第四次嘗試,因為至少從我的理解來看,如果您使用常規方式(我在上面的腳本中所做的),您的資料會被散列,就我而言,這是相反的富有成效,因為我已經得到了哈希值,所以如果它被哈希兩次,當然,它不會匹配。但是由於我不明白的原因,我仍然得到 false 作為回傳值。
最後一個想法,如果使用 P-256
簽名,問題是否可能是 go ecdsa 庫將訊息截斷為 32 個位元組?也許在 JS 中則不然?
JavaScript 程式碼中的驗證與 Dart 程式碼不相容,原因有兩個:
KJUR.crypto.Signature ()
,它隐式对数据进行哈希处理。由于数据已经被散列,这会导致双重散列。在 Dart 方面,不会发生隐式哈希(因为 ECDSASigner()
)。KJUR.crypto.ECDSA()
可以用来代替 KJUR.crypto.Signature()
。updateHex()
对十六进制编码的哈希值执行十六进制解码,而在 Dart 代码中,十六进制编码的哈希值是 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中文網其他相關文章!