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

2024年 2月 10日 44.7k 0

无法使用 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代码

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-----
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(loadAndPrepPubKey()));
Uint8List messageAsBytes = Uint8List.fromList(utf8.encode(hash));
return signer.verifySignature(messageAsBytes, convertedSig);
}
}

登录后复制

密钥生成脚本(Go)

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
}

登录后复制

链接到签名包装器脚本:链接

我的尝试

  • 我已经使用两个新的密钥对(和您的库)进行了测试,以签署一些示例数据,以查看密钥中的内容是否错误,事实并非如此
  • 我已经使用您的库和我的私钥测试了签名数据,并使用我的公钥对其进行了验证,以查看我的私钥是否已损坏,事实并非如此
  • 我已经尝试了网络加密 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)

})();

登录后复制

登录后复制

以上就是无法使用 JS 验证 Go 生成的 ECDSA 签名的详细内容,更多请关注每日运维网(www.mryunwei.com)其它相关文章!

智能AI问答
每日运维网(www.mryunwei.com)智能助手能迅速回答你的编程问题,提供实时的代码和解决方案,帮助你解决各种难题。不仅如此,它还能提供编程资源和学习指导,帮助你快速提升编程技能。无论你是初学者还是专业人士,AI智能助手都能成为你的可靠助手,助力你在编程领域取得更大的成就。

我要提问

相关标签:

go语言 区块链 移动应用程序 php JavaScript firefox safari 字符串 Go语言 JS flutter

来源:stackoverflow网


收藏


点赞

上一篇:访问切片中包含的字符串

下一篇:在 go 模块文件 (go.mod) 中使用“go”版本指令有什么含义


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

作者最新文章

  • win11怎么设置软件自动装到d盘?win11软件自动装d盘设置方法

    2024-02-10 20:12:24

  • 华为畅享70pro怎么截屏?

    2024-02-10 20:09:29

  • win7本地连接被禁用怎么解除?win7解除本地连接被禁用教程

    2024-02-10 20:06:38

  • 在线系统重装win7系统

    2024-02-10 20:03:41

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

    2024-02-10 20:03:07

  • 访问切片中包含的字符串

    2024-02-10 20:00:19

  • OPPOFind X7 Ultra怎么一键锁屏?

    2024-02-10 20:00:08

  • linux下vim的使用以及高效率的技巧

    2024-02-10 19:57:17

  • hosts有什么用

    2024-02-10 19:57:07

  • win11系统怎么设置息屏后不需要密码登录?

    2024-02-10 19:51:26

最新问题

PHP函数输出结果显示为Array,而非预期的值
我有一个php脚本,我需要将日期格式从CYYMMDD更新为MM/DD/YYYY,我写了一个函数来帮我做这个,但是当我打印它时,结果显示为'Array'。//将日期格式从CYYMMD...

P粉722409996来自于2024-02-04 11:38:12

0
1
387

为什么我无法将 const 变量传输到不同的 React 文件
所以我试图将const变量从一个文件传输到另一个文件。由于某种原因,我无法访问它,并且它不允许我在文本文件中显示它。下面分别是homepage.js代码和pay.js文件。我正在尝...

P粉593118425来自于2024-02-04 11:46:16

0
1
302

MySQL:无法更新存储函数/触发器中的表“订单”,因为它已被调用此存储函数/触发器的语句使用
我在MySQL数据库中有一个名为orders的表。在我们收到资金之前,payment_date属性为Null,此时它会更新为日期。一旦更新了payment_date属性,order...

P粉282627613来自于2024-02-04 11:46:04

0
1
257

寻求 MySQL 查询帮助 - 计算分组范围内的时间戳计数
有人会认为这很简单,但一整晚都在拼命努力。我有一个传感器警报日志,想要生成一个表格,其中包含每个不同传感器24小时、168小时(1周)和336小时(2周)内的事件的简单计数。我对M...

P粉026665919来自于2024-02-04 11:20:54

0
1
320

当用户提交图像时,是否可以动态更新图像“图库”?
我正在创建一个网站,展示我祖父的所有照片。由于我没有所有照片,而且还会有其他人来来去去,我认为有一个网站让他们可以提交照片然后查看它们的“数据库”会很不错。目前,我有一个Infin...

P粉715228019来自于2024-02-04 11:28:23

0
1
268

Buttons 标签对于 Jquery Attr 的工作至关重要
atrr函数没有给出所需的响应,我有一个小代码,可以从外部API中获取数据,该API在dict中有几个数据。


  • php文件怎么打开

  • php怎么取出数组的前几个元素

  • php反序列化失败怎么办

  • php怎么连接mssql数据库

  • php连接mssql数据库的方法

  • html怎么上传

  • PHP出现乱码怎么解决

  • php怎么在浏览器运行

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论