ECSDA用Python签名,用JS验证
ECSDA sign with Python, verify with JS
我正在尝试实现与 完全相反的效果,我需要使用 ECDSA 在 Python 中签署有效负载并能够在 JS 中验证签名。
这是我的尝试,但我很确定我遗漏了两端或两端的数据转换。
(密钥类型与上述问题的答案相同)
我尝试了一些其他变体,但到目前为止没有任何效果。
(JS上的验证returns错误)
Python:
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_dss_signature,
decode_dss_signature
)
from cryptography.hazmat.primitives.serialization import load_der_public_key, load_pem_private_key, load_der_private_key
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
import base64
import json
from hashlib import sha256
def order_dict(dictionary):
return {k: order_dict(v) if isinstance(v, dict) else v
for k, v in sorted(dictionary.items())}
async def sign_payload(private_key, data):
"""
Generate a signature based on the data using the local private key.
"""
data = order_dict(data)
# Separators prevent adding whitespaces around commas and :
payload = json.dumps(data, separators=(',', ':')).encode('utf-8')
# payload = base64.b64decode(json.dumps(data, separators=(',', ':')))
sig = private_key.sign(
payload,
ec.ECDSA(hashes.SHA256())
)
return sig
JS:
export function b642ab(base64_string){
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
export async function verifySignature(signature, public_key, data_in) {
// Sorting alphabetically to avoid signature mismatch with BE
const sorted_data_in = sortObjKeysAlphabetically(data_in);
var dataStr = JSON.stringify(sorted_data_in)
console.log(dataStr)
var dataBuf = new TextEncoder().encode(dataStr)
return window.crypto.subtle.verify(
{
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" },
},
public_key,
b642ab(utf8.decode(signature)),
dataBuf
);
}
await sign_payload(private_dsa_key, generated_payload)
主要问题是两个代码使用不同的签名格式:
Python代码中的sign_payload()
生成ASN.1/DER格式的ECDSA签名。另一方面,WebCrypto API 只能处理 IEEE P1363 格式。
由于 Python 密码库比低级 WebCrypto API 更方便,因此在 Python 代码中进行转换是有意义的。
以下Python代码基于您的代码,但在最后额外进行了IEEE P1363格式的转换:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes
import base64
import json
#def order_dict(dictionary):
# return {k: order_dict(v) if isinstance(v, dict) else v
# for k, v in sorted(dictionary.items())}
def sign_payload(private_key, data):
"""
Generate a signature based on the data using the local private key.
"""
#order_dict(data) # not considered!
# Separators prevent adding whitespaces around commas and :
payload = json.dumps(data, separators=(',', ':')).encode('utf-8')
print(payload.decode('utf-8'))
sig = private_key.sign(
payload,
ec.ECDSA(hashes.SHA256())
)
return sig
privateKeyPem = b'''-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrW9XiIs4/Kb0q8kl
TmF3oIwSn4NO3xAjs08F0lJ/5UOhRANCAAQykdP4c0ozvOOHHSNkMfLNCWRstXTG
TQf9MWjqB9PbeKyHnxuU82FisUjnVD9zO+QDAK0tnP/qzWf8zxoD0vVW
-----END PRIVATE KEY-----'''
privateKey = load_pem_private_key(privateKeyPem, password=None, backend=default_backend())
data = {"key1": "value1", "key2": "value2"}
signatureDER = sign_payload(privateKey, data)
# Convert signature format
(r, s) = decode_dss_signature(signatureDER)
signatureP1363 = r.to_bytes(32, byteorder='big') + s.to_bytes(32, byteorder='big')
print(base64.b64encode(signatureP1363).decode('utf-8'))
可能的输出是:
{"key1":"value1","key2":"value2"}
KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg==
请注意,对于此测试,order_dict(data)
调用已被注释掉,因为 JavaScript 对应的调用未发布。
在JavaScript代码中,去掉Base64解码签名时的utf8.decode()
。除此之外,代码还可以。以下 JavaScript 代码基于您的代码,添加了密钥导入:
(async () => {
var x509pem = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0
xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
-----END PUBLIC KEY-----`
var public_key = await importPublicKey(x509pem)
var data_in = {
key1: "value1",
key2: "value2"
}
var signature = "KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg=="
var verified = await verifySignature(signature, public_key, data_in)
console.log(verified);
})();
function b642ab(base64_string){
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
async function verifySignature(signature, public_key, data_in) {
// Sorting alphabetically to avoid signature mismatch with BE
//const sorted_data_in = sortObjKeysAlphabetically(data_in);
//var dataStr = JSON.stringify(sorted_data_in)
var dataStr = JSON.stringify(data_in)
console.log(dataStr)
var dataBuf = new TextEncoder().encode(dataStr)
return window.crypto.subtle.verify(
{
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" },
},
public_key,
b642ab(signature),
dataBuf
);
}
async function importPublicKey(spkiPem) {
return await window.crypto.subtle.importKey(
"spki",
getSpkiDer(spkiPem),
{name: "ECDSA", namedCurve: "P-256"},
false,
["verify"]
);
}
function getSpkiDer(spkiPem){
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
var pemContents = spkiPem.substring(pemHeader.length, spkiPem.length - pemFooter.length);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
使用 Python 代码生成的签名可以通过 JavaScript 代码成功验证消息。
请注意 - 类似于 Python 代码 - sortObjKeysAlphabetically()
由于缺少实现而被注释掉。
我正在尝试实现与
这是我的尝试,但我很确定我遗漏了两端或两端的数据转换。
(密钥类型与上述问题的答案相同)
我尝试了一些其他变体,但到目前为止没有任何效果。
(JS上的验证returns错误)
Python:
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_dss_signature,
decode_dss_signature
)
from cryptography.hazmat.primitives.serialization import load_der_public_key, load_pem_private_key, load_der_private_key
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
import base64
import json
from hashlib import sha256
def order_dict(dictionary):
return {k: order_dict(v) if isinstance(v, dict) else v
for k, v in sorted(dictionary.items())}
async def sign_payload(private_key, data):
"""
Generate a signature based on the data using the local private key.
"""
data = order_dict(data)
# Separators prevent adding whitespaces around commas and :
payload = json.dumps(data, separators=(',', ':')).encode('utf-8')
# payload = base64.b64decode(json.dumps(data, separators=(',', ':')))
sig = private_key.sign(
payload,
ec.ECDSA(hashes.SHA256())
)
return sig
JS:
export function b642ab(base64_string){
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
export async function verifySignature(signature, public_key, data_in) {
// Sorting alphabetically to avoid signature mismatch with BE
const sorted_data_in = sortObjKeysAlphabetically(data_in);
var dataStr = JSON.stringify(sorted_data_in)
console.log(dataStr)
var dataBuf = new TextEncoder().encode(dataStr)
return window.crypto.subtle.verify(
{
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" },
},
public_key,
b642ab(utf8.decode(signature)),
dataBuf
);
}
await sign_payload(private_dsa_key, generated_payload)
主要问题是两个代码使用不同的签名格式:
Python代码中的sign_payload()
生成ASN.1/DER格式的ECDSA签名。另一方面,WebCrypto API 只能处理 IEEE P1363 格式。
由于 Python 密码库比低级 WebCrypto API 更方便,因此在 Python 代码中进行转换是有意义的。
以下Python代码基于您的代码,但在最后额外进行了IEEE P1363格式的转换:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives import hashes
import base64
import json
#def order_dict(dictionary):
# return {k: order_dict(v) if isinstance(v, dict) else v
# for k, v in sorted(dictionary.items())}
def sign_payload(private_key, data):
"""
Generate a signature based on the data using the local private key.
"""
#order_dict(data) # not considered!
# Separators prevent adding whitespaces around commas and :
payload = json.dumps(data, separators=(',', ':')).encode('utf-8')
print(payload.decode('utf-8'))
sig = private_key.sign(
payload,
ec.ECDSA(hashes.SHA256())
)
return sig
privateKeyPem = b'''-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrW9XiIs4/Kb0q8kl
TmF3oIwSn4NO3xAjs08F0lJ/5UOhRANCAAQykdP4c0ozvOOHHSNkMfLNCWRstXTG
TQf9MWjqB9PbeKyHnxuU82FisUjnVD9zO+QDAK0tnP/qzWf8zxoD0vVW
-----END PRIVATE KEY-----'''
privateKey = load_pem_private_key(privateKeyPem, password=None, backend=default_backend())
data = {"key1": "value1", "key2": "value2"}
signatureDER = sign_payload(privateKey, data)
# Convert signature format
(r, s) = decode_dss_signature(signatureDER)
signatureP1363 = r.to_bytes(32, byteorder='big') + s.to_bytes(32, byteorder='big')
print(base64.b64encode(signatureP1363).decode('utf-8'))
可能的输出是:
{"key1":"value1","key2":"value2"}
KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg==
请注意,对于此测试,order_dict(data)
调用已被注释掉,因为 JavaScript 对应的调用未发布。
在JavaScript代码中,去掉Base64解码签名时的utf8.decode()
。除此之外,代码还可以。以下 JavaScript 代码基于您的代码,添加了密钥导入:
(async () => {
var x509pem = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMpHT+HNKM7zjhx0jZDHyzQlkbLV0
xk0H/TFo6gfT23ish58blPNhYrFI51Q/czvkAwCtLZz/6s1n/M8aA9L1Vg==
-----END PUBLIC KEY-----`
var public_key = await importPublicKey(x509pem)
var data_in = {
key1: "value1",
key2: "value2"
}
var signature = "KIkBK4pxSFq/UdsPb/mYCC3y7iAJlULC/jizNp9DrvFFIvZaUjx/M0SAQC7CeBIlLmKzfkGx1fOr7OJ8VlwAdg=="
var verified = await verifySignature(signature, public_key, data_in)
console.log(verified);
})();
function b642ab(base64_string){
return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
}
async function verifySignature(signature, public_key, data_in) {
// Sorting alphabetically to avoid signature mismatch with BE
//const sorted_data_in = sortObjKeysAlphabetically(data_in);
//var dataStr = JSON.stringify(sorted_data_in)
var dataStr = JSON.stringify(data_in)
console.log(dataStr)
var dataBuf = new TextEncoder().encode(dataStr)
return window.crypto.subtle.verify(
{
name: "ECDSA",
namedCurve: "P-256",
hash: { name: "SHA-256" },
},
public_key,
b642ab(signature),
dataBuf
);
}
async function importPublicKey(spkiPem) {
return await window.crypto.subtle.importKey(
"spki",
getSpkiDer(spkiPem),
{name: "ECDSA", namedCurve: "P-256"},
false,
["verify"]
);
}
function getSpkiDer(spkiPem){
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
var pemContents = spkiPem.substring(pemHeader.length, spkiPem.length - pemFooter.length);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
使用 Python 代码生成的签名可以通过 JavaScript 代码成功验证消息。
请注意 - 类似于 Python 代码 - sortObjKeysAlphabetically()
由于缺少实现而被注释掉。