php小编小新在使用Go语言生成ECDSA签名时遇到了一个问题,即无法使用JS进行验证。这个问题的解决方法是在Go代码中添加一些附加的字段,以确保签名的正确性。通过对Go代码进行一些修改,我们可以解决这个问题,并使得JS能够正确验证Go生成的ECDSA签名。这篇文章将为您详细介绍具体的解决方法和步骤。
问题内容
我遇到了一个小问题,(我的假设是有一件小事情阻碍了我,但我不知道是什么),如标题中所述。
我将首先概述我正在做的事情,然后提供我所拥有的一切。
项目概述
我在移动应用程序中使用 SHA-256 对文件进行哈希处理,并使用 ECDSA P-256 密钥在后端对哈希进行签名。然后这种情况就一直持续下去。如果用户需要,他可以通过再次散列文件并查找散列并获取散列、一些元数据和签名来验证文件的完整性。
为了验证数据已提交给我的应用程序而不是第三方(哈希值保留在区块链中,但这对于此问题并不重要),应用程序将尝试使用公钥验证签名。这工作很好。
现在我也想将此选项添加到我的网站,但问题是。如果我使用 jsrsasign 或 webcrypto api,我的签名无效。
数据
签名示例:3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066 ee127cfb7c0ad369521459d00公钥:
-----BEGIN PUBLIC KEY-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZc6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==-----END PUBLIC KEY-----
登录后复制哈希值:bb5dbfcb5206282627254ab23397cda842b082696466f2563503f79a5dccf942
脚本
JS代码
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 应用验证码(Flutter Dart)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-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZc6/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(loadAndPrepPubKey())); Uint8List messageAsBytes = Uint8List.fromList(utf8.encode(hash)); return signer.verifySignature(messageAsBytes, convertedSig); }}登录后复制密钥生成脚本(Go)
package mainimport ( "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}登录后复制
链接到签名包装器脚本:链接
我的尝试
我已经使用两个新的密钥对(和您的库)进行了测试,以签署一些示例数据,以查看密钥中的内容是否错误,事实并非如此我已经使用您的库和我的私钥测试了签名数据,并使用我的公钥对其进行了验证,以查看我的私钥是否已损坏,事实并非如此我已经尝试了网络加密 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-----MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq6iOuQeIhlhywCjo5yoABGODOJRZc6/L8XzUYEsocCbc/JHiByGjuB3G9cSU2vUi1HUy5LsCtX2wlHSEObGVBw==-----END PUBLIC KEY-----`; var pubkey = KEYUTIL.getKey(spki).getPublicKeyXYHex()var pubkeyHex = '04' + pubkey.x + pubkey.yvar 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-256var sigHex = "3045022100f28c29042a6d766810e21f2c0a1839f93140989299cae1d37b49a454373659c802203d0967be0696686414fe2efed3a71bc1639d066ee127cfb7c0ad369521459d00"var ec = new KJUR.crypto.ECDSA({'curve': 'secp256r1'})var verified = ec.verifyHex(msgHashHex, sigHex, pubkeyHex)console.log("Verification:", verified) })();登录后复制
登录后复制
以上就是无法使用 JS 验证 Go 生成的 ECDSA 签名的详细内容,更多请关注【创想鸟】其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。
发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2483932.html