目录
问题内容
项目概述
数据
脚本
我的尝试
解决方法
首页 后端开发 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

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 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教程
1675
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
Solayer(LAYER)是什么?Solayer代币经济学和价格预测 Solayer(LAYER)是什么?Solayer代币经济学和价格预测 May 14, 2025 pm 10:06 PM

介绍去中心化金融(DeFi)正在改变用户与区块链技术的交互方式,为交易、借贷和收益创造创造无缝灵活的途径。Solayer(LAYER)正是这一变革的核心,它构建了一个跨多条区块链连接流动性和实用性的协议。随着DeFi的普及以及对高效跨链基础设施需求的增长,Solayer正吸引着交易员、开发者和投资者的关注,他们正在寻找下一个重大机遇。本文将解释Solayer的概念,详细介绍其创新功能和代币经济学,并展望其2030年

值得购买以获得短期收益的九种加密货币盘点 值得购买以获得短期收益的九种加密货币盘点 May 14, 2025 pm 09:54 PM

目录如何选择加密货币进行短期交易?短期交易最佳加密货币列表比特币雪崩索拉纳狗狗币多边形世界币Chainlink新兴企业如何选择加密货币进行短期交易?短期交易是指购买加密货币并持有一段时间,从几分钟到几天不等。这种方法前景光明,但也存在风险,耗时较长,因为你需要持续关注市场行情。但这还不是全部;在选择合适的加密货币资产时,你还应该注意以下几点:波动性:短期交易成功的主要指标之一是高挥发性加密货币的价值;价值越高,价格波动越大,从而创造更

XDC 与XRP:哪一个对投资更有吸引力? XDC 与XRP:哪一个对投资更有吸引力? May 14, 2025 pm 10:09 PM

两个代币因其不同的使用案例和技术特点而凸显出来,分别是XDC和XRP。这两者都符合ISO20022标准,这意味着它们遵循全球金融消息传递的标准,实现无缝的互操作性。然而,它们的目的、市场以及背后的技术差异显着。在本文中,我们将详细比较XDC与XRP,深入探讨它们的优势、劣势、市场潜力以及投资考量。 XDC与XRP:哪一个对投资更有吸引力?两者XDC和XRP都是去中心化的代币,但它们针对不同的行业并采用独特的方法。以下是它们

VINU币未来如何?2025年VINU币价格分析与投资策略 VINU币未来如何?2025年VINU币价格分析与投资策略 May 14, 2025 pm 09:30 PM

目录 什么是VitaInu(VINU)?什么是VINU代币?2025年VINU币价格预测VitaInu(VINU)价格预测2025-2030至2030年VitaInu(VINU)价格预测2025年VitaInu价格预测2026年VitaInu价格预测2027年VitaInu价格预测2028年VitaInu价格预测2029年VitaInu价格预测2030年VitaInu价格预测解读VINU的市场表现

Bitget宣布VOXEL交易回滚!损失用户将获赔偿 Bitget宣布VOXEL交易回滚!损失用户将获赔偿 May 14, 2025 pm 10:45 PM

加密货币交易所Bitget近日因VOXEL合约市场的异常交易波动,宣布将回滚该时段内的交易,并提出赔偿方案。然而,官方公告中使用「用户操纵市场」的说法,引发外界对其公关处理不当的质疑,担心这将导致危机进一步扩大。BitgetVOXEL代币现异常交易,引发内部做市担忧北京时间昨日上午8:00至8:30,Bitget交易所爆发了VOXEL代币的异常交易事件,该时段的交易量甚至一度超过了比特币,引发市场对其内部操作透明度的高度质疑。事件源于VOXEL代币价格出现剧烈波动后,社群开始质疑Bitget可能

什么是 Sign Protocol (SIGN)?跨链验证网络入门指南 什么是 Sign Protocol (SIGN)?跨链验证网络入门指南 May 14, 2025 pm 10:48 PM

区块链技术持续改变着人们在线上交换价值、验证信息和建立信任的方式。随着去中心化应用在各行各业的蓬勃发展,跨多个区块链确认声明和身份的能力变得越来越重要,也越来越复杂。传统的、依赖于中心化权威的信任模型往往不足以支撑去中心化的生态系统,因此对区块链原生验证解决方案的需求也日益增长。SignProtocol(SIGN)通过提供一个用于跨多个区块链网络创建、验证和管理证明的框架来应对这一挑战。SignProtocol旨在打造一个全链

特斯拉公布第1季度财报:营收不达标导读!但仍然没有卖出比特币 特斯拉公布第1季度财报:营收不达标导读!但仍然没有卖出比特币 May 14, 2025 pm 10:03 PM

电动车巨头特斯拉(Tesla)发布了第一季度的财报,业绩未能达到市场预期,营收略低于分析师的预测。然而,值得注意的是,特斯拉仍持有价值约10亿美元的比特币,这表明该公司在上个季度没有通过卖出比特币来套现。根据特斯拉截至3月31日的财报,公司持有的比特币价值为9.51亿美元,相比去年底的10.76亿美元有所下降。外界分析,这主要是由于比特币价格的波动,而不是特斯拉进行了实际的交易操作。根据BitcoinTreasuries的数据,特斯拉目前在其资产负债表中持有11,509枚比特币。区块链数据平台A

SWCH是什么币种?值得投资吗?SWCH币详细购买教程 SWCH是什么币种?值得投资吗?SWCH币详细购买教程 May 14, 2025 pm 10:30 PM

加密货币市场项目随着区块链发展而不断涌现,目的就是改变传统金融实践,SwissCheese就是其中一个项目。据了解,SwissCheese是一个让用户能够交易通证化股票的去中心化平台,旨在增强市场的可及性并降低交易成本,为更具包容性的金融环境铺平道路。平台的原生代币为SWCH,主要作用就是交易、治理等。该项目上线后立即引起了投资者的关注,但仅仅了解SWCH是什么币种?还不能分析出SWCH值得投资吗?结合当前数据来看,SWCH具有一定的

See all articles