无法使用 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-----
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 < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return new Uint8Array(bytes);
}
应用验证码(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 签名的详细内容,更多请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
无法使用 JS 验证 Go 生成的 ECDSA 签名
下载Word文档到电脑,方便收藏和打印~
猜你喜欢
无法使用 JS 验证 Go 生成的 ECDSA 签名
Go 的 btcec 库中使用哪种方法来验证 secp256k1 签名?
如何使用PHP进行图片验证码的生成和验证?(PHP生成和验证图片验证码的方法是什么?)
编程热搜
Python 学习之路 - Python
一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-chatgpt的中文全称是什么
chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列C/C++可变参数的使用
可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃Python 3 教程
Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 PythonPython pip包管理
一、前言 在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install 和 pip , 目前官方推荐使用 pip。
编程资源站
- 资料下载
- 历年试题