使用 EC 在 JS(前端)中签署有效负载并在 Python 中验证
Signing payload in JS (Frontend) using EC and validating in Python
我有一个 Python 后端生成 public/private 密钥,生成有效负载,然后需要让客户端(ReactJS 或纯 JS)签署该有效负载,稍后进行验证。
Python 中的实现如下所示:
进口
import json
import uuid
from backend.config import STARTING_BALANCE
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 import hashes, serialization
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import hashlib
生成密钥:
class User:
def __init__(self):
self.address = hashlib.sha1(str(str(uuid.uuid4())[0:8]).encode("UTF-8")).hexdigest()
self.private_key = ec.generate_private_key(
ec.SECP256K1(),
default_backend()
)
self.private_key_return = self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
self.public_key = self.private_key.public_key()
self.serialize_public_key()
def serialize_public_key():
"""
Reset the public key to its serialized version.
"""
self.public_key = self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
签名:
def sign(self, data):
"""
Generate a signature based on the data using the local private key.
"""
return decode_dss_signature(self.private_key.sign(
json.dumps(data).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
))
验证:
@staticmethod
def verify(public_key, data, signature):
"""
Verify a signature based on the original public key and data.
"""
deserialized_public_key = serialization.load_pem_public_key(
public_key.encode('utf-8'),
default_backend()
)
(r, s) = signature
try:
deserialized_public_key.verify(
encode_dss_signature(r, s),
json.dumps(data).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
return True
except InvalidSignature:
return False
我现在需要的是在客户端加载(甚至生成)PEM 密钥,然后根据请求签署一个 JSON 有效载荷,稍后可以从 Python后端。
我尝试研究网络密码学和 cryptoJS 的用法,但没有成功。
我可以使用另一种更兼容的算法,但至少我需要签名功能完全正常工作。
我还尝试使用 Brython 和 Pyodide 将 Python 编译为 JS,但两者都无法支持所有必需的包。
简单来说,我正在寻找以下内容:
生成有效负载 (Python) -----> 签署有效负载 (JS) -----> 验证签名 (Python)
任何 help/advice 将不胜感激。
CryptoJS only supports symmetric encryption and therefore not ECDSA. WebCrypto 支持 ECDSA,但不支持 secp256k1。
WebCrypto 的优点是所有主流浏览器都支持它。由于您可以根据您的评论使用其他曲线,因此我将描述一种使用 WebCrypto 支持的曲线的解决方案。
否则,sjcl would also be an alternative, a pure JavaScript library that supports ECDSA and especially secp256k1, s.here.
WebCrypto 是一个低级别 API,它提供您需要的功能,如密钥生成、密钥导出和签名。关于 ECDSA WebCrypto 支持曲线 P-256(又名 secp256r1)、P-384(又名 secp384r1)和 p-521(又名 secp521r1)。下面我用P-256.
以下JavaScript代码为P-256生成密钥对,导出public X.509/SPKI格式的密钥,DER编码(因此可以发送到Python 站点),并在消息上签名:
(async () => {
// Generate key pair
var keypair = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256", // secp256r1
},
false,
["sign", "verify"]
);
// Export public key in X.509/SPKI format, DER encoded
var publicKey = await window.crypto.subtle.exportKey(
"spki",
keypair.publicKey
);
document.getElementById("pub").innerHTML = "Public key: " + ab2b64(publicKey);
// Sign data
var data = {
"data_1":"The quick brown fox",
"data_2":"jumps over the lazy dog"
}
var dataStr = JSON.stringify(data)
var dataBuf = new TextEncoder().encode(dataStr).buffer
var signature = await window.crypto.subtle.sign(
{
name: "ECDSA",
hash: {name: "SHA-256"},
},
keypair.privateKey,
dataBuf
);
document.getElementById("sig").innerHTML = "Signature: " + ab2b64(signature);
})();
// Helper
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
<p style="font-family:'Courier New', monospace;" id="pub"></p>
<p style="font-family:'Courier New', monospace;" id="sig"></p>
可能的输出是:
Public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWzC5lPNifcHNuKL+/jjhrtTi+9gAMbYui9Vv7TjtS7RCt8p6Y6zUmHVpGEowuVMuOSNxfpJYpnGExNT/eWhuwQ==
Signature: XRNTbkHK7H8XPEIJQhS6K6ncLPEuWWrkXLXiNWwv6ImnL2Dm5VHcazJ7QYQNOvWJmB2T3rconRkT0N4BDFapCQ==
在 Python 方面,可以通过以下方式成功验证:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
from cryptography.hazmat.primitives.serialization import load_der_public_key
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
import base64
import json
publikKeyDer = base64.b64decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWzC5lPNifcHNuKL+/jjhrtTi+9gAMbYui9Vv7TjtS7RCt8p6Y6zUmHVpGEowuVMuOSNxfpJYpnGExNT/eWhuwQ==")
data = {
"data_1":"The quick brown fox",
"data_2":"jumps over the lazy dog"
}
signature = base64.b64decode("XRNTbkHK7H8XPEIJQhS6K6ncLPEuWWrkXLXiNWwv6ImnL2Dm5VHcazJ7QYQNOvWJmB2T3rconRkT0N4BDFapCQ==")
publicKey = load_der_public_key(publikKeyDer, default_backend())
r = int.from_bytes(signature[:32], byteorder='big')
s = int.from_bytes(signature[32:], byteorder='big')
try:
publicKey.verify(
encode_dss_signature(r, s),
json.dumps(data, separators=(',', ':')).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
print("verification succeeded")
except InvalidSignature:
print("verification failed")
其中,与张贴的 Python 代码不同,load_der_public_key()
is used instead of load_pem_public_key()
.
此外,WebCrypto returns IEEE P1363 格式的签名,但作为串联的 ArrayBuffer
r|s,因此必须将两个部分转换为整数才能将格式转换为ASN.1/DER encode_dss_signature()
.
关于JSON,必须将分隔符重新定义为最紧凑的表示形式(但这取决于JavaScript 端的设置)。
我有一个 Python 后端生成 public/private 密钥,生成有效负载,然后需要让客户端(ReactJS 或纯 JS)签署该有效负载,稍后进行验证。
Python 中的实现如下所示:
进口
import json
import uuid
from backend.config import STARTING_BALANCE
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 import hashes, serialization
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import hashlib
生成密钥:
class User:
def __init__(self):
self.address = hashlib.sha1(str(str(uuid.uuid4())[0:8]).encode("UTF-8")).hexdigest()
self.private_key = ec.generate_private_key(
ec.SECP256K1(),
default_backend()
)
self.private_key_return = self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
self.public_key = self.private_key.public_key()
self.serialize_public_key()
def serialize_public_key():
"""
Reset the public key to its serialized version.
"""
self.public_key = self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
签名:
def sign(self, data):
"""
Generate a signature based on the data using the local private key.
"""
return decode_dss_signature(self.private_key.sign(
json.dumps(data).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
))
验证:
@staticmethod
def verify(public_key, data, signature):
"""
Verify a signature based on the original public key and data.
"""
deserialized_public_key = serialization.load_pem_public_key(
public_key.encode('utf-8'),
default_backend()
)
(r, s) = signature
try:
deserialized_public_key.verify(
encode_dss_signature(r, s),
json.dumps(data).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
return True
except InvalidSignature:
return False
我现在需要的是在客户端加载(甚至生成)PEM 密钥,然后根据请求签署一个 JSON 有效载荷,稍后可以从 Python后端。
我尝试研究网络密码学和 cryptoJS 的用法,但没有成功。
我可以使用另一种更兼容的算法,但至少我需要签名功能完全正常工作。
我还尝试使用 Brython 和 Pyodide 将 Python 编译为 JS,但两者都无法支持所有必需的包。
简单来说,我正在寻找以下内容:
生成有效负载 (Python) -----> 签署有效负载 (JS) -----> 验证签名 (Python)
任何 help/advice 将不胜感激。
CryptoJS only supports symmetric encryption and therefore not ECDSA. WebCrypto 支持 ECDSA,但不支持 secp256k1。
WebCrypto 的优点是所有主流浏览器都支持它。由于您可以根据您的评论使用其他曲线,因此我将描述一种使用 WebCrypto 支持的曲线的解决方案。
否则,sjcl would also be an alternative, a pure JavaScript library that supports ECDSA and especially secp256k1, s.here.
WebCrypto 是一个低级别 API,它提供您需要的功能,如密钥生成、密钥导出和签名。关于 ECDSA WebCrypto 支持曲线 P-256(又名 secp256r1)、P-384(又名 secp384r1)和 p-521(又名 secp521r1)。下面我用P-256.
以下JavaScript代码为P-256生成密钥对,导出public X.509/SPKI格式的密钥,DER编码(因此可以发送到Python 站点),并在消息上签名:
(async () => {
// Generate key pair
var keypair = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256", // secp256r1
},
false,
["sign", "verify"]
);
// Export public key in X.509/SPKI format, DER encoded
var publicKey = await window.crypto.subtle.exportKey(
"spki",
keypair.publicKey
);
document.getElementById("pub").innerHTML = "Public key: " + ab2b64(publicKey);
// Sign data
var data = {
"data_1":"The quick brown fox",
"data_2":"jumps over the lazy dog"
}
var dataStr = JSON.stringify(data)
var dataBuf = new TextEncoder().encode(dataStr).buffer
var signature = await window.crypto.subtle.sign(
{
name: "ECDSA",
hash: {name: "SHA-256"},
},
keypair.privateKey,
dataBuf
);
document.getElementById("sig").innerHTML = "Signature: " + ab2b64(signature);
})();
// Helper
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
<p style="font-family:'Courier New', monospace;" id="pub"></p>
<p style="font-family:'Courier New', monospace;" id="sig"></p>
可能的输出是:
Public key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWzC5lPNifcHNuKL+/jjhrtTi+9gAMbYui9Vv7TjtS7RCt8p6Y6zUmHVpGEowuVMuOSNxfpJYpnGExNT/eWhuwQ==
Signature: XRNTbkHK7H8XPEIJQhS6K6ncLPEuWWrkXLXiNWwv6ImnL2Dm5VHcazJ7QYQNOvWJmB2T3rconRkT0N4BDFapCQ==
在 Python 方面,可以通过以下方式成功验证:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
from cryptography.hazmat.primitives.serialization import load_der_public_key
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
import base64
import json
publikKeyDer = base64.b64decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWzC5lPNifcHNuKL+/jjhrtTi+9gAMbYui9Vv7TjtS7RCt8p6Y6zUmHVpGEowuVMuOSNxfpJYpnGExNT/eWhuwQ==")
data = {
"data_1":"The quick brown fox",
"data_2":"jumps over the lazy dog"
}
signature = base64.b64decode("XRNTbkHK7H8XPEIJQhS6K6ncLPEuWWrkXLXiNWwv6ImnL2Dm5VHcazJ7QYQNOvWJmB2T3rconRkT0N4BDFapCQ==")
publicKey = load_der_public_key(publikKeyDer, default_backend())
r = int.from_bytes(signature[:32], byteorder='big')
s = int.from_bytes(signature[32:], byteorder='big')
try:
publicKey.verify(
encode_dss_signature(r, s),
json.dumps(data, separators=(',', ':')).encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
print("verification succeeded")
except InvalidSignature:
print("verification failed")
其中,与张贴的 Python 代码不同,load_der_public_key()
is used instead of load_pem_public_key()
.
此外,WebCrypto returns IEEE P1363 格式的签名,但作为串联的 ArrayBuffer
r|s,因此必须将两个部分转换为整数才能将格式转换为ASN.1/DER encode_dss_signature()
.
关于JSON,必须将分隔符重新定义为最紧凑的表示形式(但这取决于JavaScript 端的设置)。