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中文网其他相关文章!