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

无法使用 js 验证 go 生成的 ecdsa 签名

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

(0)
上一篇 2025年3月4日 21:25:46
下一篇 2025年2月23日 13:25:53

AD推荐 黄金广告位招租... 更多推荐

相关推荐

发表回复

登录后才能评论