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
登录后复制
应用验证码(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怎么在浏览器运行