这是获取签名的 md5 摘要的 Python 代码,需要帮助在 JavaScript 中实现这个东西
This is a Python code to get a md5 digest of a signature, Need help to implement this thing in JavaScript
Python 代码运行良好,我检查了此 Python 代码中的消息“a”,结果为“52F17E7031982DE1744A57F6EE9BD3A3”
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from OpenSSL import crypto
message = "a".encode('utf-8')
p12 = crypto.load_pkcs12(company_p12_certificate, certificate_password)
key_bytes = crypto.dump_privatekey(crypto.FILETYPE_PEM, p12.get_privatekey())
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
我使用 JavaScript 实现了这个,但没有得到相同的结果。它为消息“a”提供了“4EB5DB7F5459E832DE3E0638A8F4C4A0”
我的 JavaScript 代码是:
$(document).ready(function () {
function _privateKeyToPkcs8(privateKey) {
var rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
var privateKeyInfoDer = forge.asn1.toDer(privateKeyInfo).getBytes();
var privateKeyInfoDerBuff = stringToArrayBuffer(privateKeyInfoDer);
return privateKeyInfoDerBuff;
}
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
$("#file").change(function () {
var file = this.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
var contents = e.target.result;
var pkcs12Der = arrayBufferToString(contents)
var pkcs12B64 = forge.util.encode64(pkcs12Der);
var pkcs12Der = forge.util.decode64(pkcs12B64);
var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, '123456');
var privateKey
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
// this bag has a private key
if (safeBag.type === forge.pki.oids.keyBag) {
//Found plain private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
// found encrypted private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.certBag) {
// this bag has a certificate...
}
}
}
var privateKeyInfoDerBuff = _privateKeyToPkcs8(privateKey);
//Import the webcrypto key
crypto.subtle.importKey(
'pkcs8',
privateKeyInfoDerBuff,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"]).
then(function (cryptoKey) {
var sha256 = forge.md.sha256.create();
sha256.update('a'); // Message will come here
var digestToSignBuf = stringToArrayBuffer(sha256.digest().toHex());
crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, cryptoKey, digestToSignBuf)
.then(function (signature) {
var signatureB64 = forge.util.encode64(arrayBufferToString(signature))
var md5 = forge.md.md5.create();
md5.update(signatureB64);
console.log("final result", md5.digest().toHex().toUpperCase());
});
})
}
})
});
WebCrypto 在签名期间使用密钥中指定的摘要 SHA-256 隐式生成数据哈希,因此不需要使用 SHA-256 进行显式哈希。
此外,生成的签名直接使用 MD5 进行哈希处理,即没有事先进行 Base64 编码。
进行这些更改后,JavaScript 代码为(使用测试密钥):
// Import private test key
var pkcs8pem = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----`;
var pkcs8 = convertDER(pkcs8pem);
crypto.subtle.importKey(
'pkcs8',
pkcs8,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"])
.then(function (cryptoKey) {
// Sign message
var message = 'The quick brown fox jumps over the lazy dog';
crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
cryptoKey,
stringToArrayBuffer(message))
.then(function (signature) {
// Create MD5 hash
var md5 = forge.md.md5.create();
md5.update(arrayBufferToString(signature));
console.log("Final result", md5.digest().toHex().toUpperCase()); // Final result 30FD001CFD12D0A3DF000D216C82C47E
});
});
// Helper
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
function convertDER(pem){
var pemHeader = "-----BEGIN PRIVATE KEY-----";
var pemFooter = "-----END PRIVATE KEY-----";
var pemContents = pkcs8pem.substring(pemHeader.length, pkcs8pem.length - pemFooter.length);
var binaryDerString = window.atob(pemContents);
var pkcs8 = stringToArrayBuffer(binaryDerString);
return pkcs8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/forge/0.10.0/forge.min.js"></script>
输出:
Final result 30FD001CFD12D0A3DF000D216C82C47E
Python代码returns相同密钥和明文的相同结果。
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
key_bytes = '''-----BEGIN PRIVATE KEY-----
MIIBVQI...
-----END PRIVATE KEY-----'''
message = "The quick brown fox jumps over the lazy dog".encode('utf-8')
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
print(result) # 30FD001CFD12D0A3DF000D216C82C47E
正如评论中已经指出的那样,由于 MD5 哈希,无法使用 public 密钥进行验证(但显然不是故意的)。
此外,WebCrypto 提供了 SubtleCrypto.digest()
函数来确定哈希,因此 forge 库实际上并不是必需的(至少对于散列)。
我没有详细分析密钥导入,所以这里也可能存在问题。
Python 代码运行良好,我检查了此 Python 代码中的消息“a”,结果为“52F17E7031982DE1744A57F6EE9BD3A3”
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from OpenSSL import crypto
message = "a".encode('utf-8')
p12 = crypto.load_pkcs12(company_p12_certificate, certificate_password)
key_bytes = crypto.dump_privatekey(crypto.FILETYPE_PEM, p12.get_privatekey())
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
我使用 JavaScript 实现了这个,但没有得到相同的结果。它为消息“a”提供了“4EB5DB7F5459E832DE3E0638A8F4C4A0” 我的 JavaScript 代码是:
$(document).ready(function () {
function _privateKeyToPkcs8(privateKey) {
var rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
var privateKeyInfoDer = forge.asn1.toDer(privateKeyInfo).getBytes();
var privateKeyInfoDerBuff = stringToArrayBuffer(privateKeyInfoDer);
return privateKeyInfoDerBuff;
}
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
$("#file").change(function () {
var file = this.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
var contents = e.target.result;
var pkcs12Der = arrayBufferToString(contents)
var pkcs12B64 = forge.util.encode64(pkcs12Der);
var pkcs12Der = forge.util.decode64(pkcs12B64);
var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, '123456');
var privateKey
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
// this bag has a private key
if (safeBag.type === forge.pki.oids.keyBag) {
//Found plain private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
// found encrypted private key
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.certBag) {
// this bag has a certificate...
}
}
}
var privateKeyInfoDerBuff = _privateKeyToPkcs8(privateKey);
//Import the webcrypto key
crypto.subtle.importKey(
'pkcs8',
privateKeyInfoDerBuff,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"]).
then(function (cryptoKey) {
var sha256 = forge.md.sha256.create();
sha256.update('a'); // Message will come here
var digestToSignBuf = stringToArrayBuffer(sha256.digest().toHex());
crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, cryptoKey, digestToSignBuf)
.then(function (signature) {
var signatureB64 = forge.util.encode64(arrayBufferToString(signature))
var md5 = forge.md.md5.create();
md5.update(signatureB64);
console.log("final result", md5.digest().toHex().toUpperCase());
});
})
}
})
});
WebCrypto 在签名期间使用密钥中指定的摘要 SHA-256 隐式生成数据哈希,因此不需要使用 SHA-256 进行显式哈希。
此外,生成的签名直接使用 MD5 进行哈希处理,即没有事先进行 Base64 编码。
进行这些更改后,JavaScript 代码为(使用测试密钥):
// Import private test key
var pkcs8pem = `-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEA2gdsVIRmg5IH0rG3
u3w+gHCZq5o4OMQIeomC1NTeHgxbkrfznv7TgWVzrHpr3HHK8IpLlG04/aBo6U5W
2umHQQIDAQABAkEAu7wulGvZFat1Xv+19BMcgl3yhCdsB70Mi+7CH98XTwjACk4T
+IYv4N53j16gce7U5fJxmGkdq83+xAyeyw8U0QIhAPIMhbtXlRS7XpkB66l5DvN1
XrKRWeB3RtvcUSf30RyFAiEA5ph7eWXbXWpIhdWMoe50yffF7pW+C5z07tzAIH6D
Ko0CIQCyveSTr917bdIxk2V/xNHxnx7LJuMEC5DcExorNanKMQIgUxHRQU1hNgjI
sXXZoKgfaHaa1jUZbmOPlNDvYYVRyS0CIB9ZZee2zubyRla4qN8PQxCJb7DiICmH
7nWP7CIvcQwB
-----END PRIVATE KEY-----`;
var pkcs8 = convertDER(pkcs8pem);
crypto.subtle.importKey(
'pkcs8',
pkcs8,
{ name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } },
true,
["sign"])
.then(function (cryptoKey) {
// Sign message
var message = 'The quick brown fox jumps over the lazy dog';
crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
cryptoKey,
stringToArrayBuffer(message))
.then(function (signature) {
// Create MD5 hash
var md5 = forge.md.md5.create();
md5.update(arrayBufferToString(signature));
console.log("Final result", md5.digest().toHex().toUpperCase()); // Final result 30FD001CFD12D0A3DF000D216C82C47E
});
});
// Helper
function stringToArrayBuffer(data) {
var arrBuff = new ArrayBuffer(data.length);
var writer = new Uint8Array(arrBuff);
for (var i = 0, len = data.length; i < len; i++) {
writer[i] = data.charCodeAt(i);
}
return arrBuff;
}
function arrayBufferToString(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
function convertDER(pem){
var pemHeader = "-----BEGIN PRIVATE KEY-----";
var pemFooter = "-----END PRIVATE KEY-----";
var pemContents = pkcs8pem.substring(pemHeader.length, pkcs8pem.length - pemFooter.length);
var binaryDerString = window.atob(pemContents);
var pkcs8 = stringToArrayBuffer(binaryDerString);
return pkcs8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/forge/0.10.0/forge.min.js"></script>
输出:
Final result 30FD001CFD12D0A3DF000D216C82C47E
Python代码returns相同密钥和明文的相同结果。
from Crypto.Hash import SHA256, MD5
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
key_bytes = '''-----BEGIN PRIVATE KEY-----
MIIBVQI...
-----END PRIVATE KEY-----'''
message = "The quick brown fox jumps over the lazy dog".encode('utf-8')
key = RSA.import_key(key_bytes)
h = SHA256.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
md5_digest = MD5.new(signature)
result = str(md5_digest.digest().hex()).upper()
print(result) # 30FD001CFD12D0A3DF000D216C82C47E
正如评论中已经指出的那样,由于 MD5 哈希,无法使用 public 密钥进行验证(但显然不是故意的)。
此外,WebCrypto 提供了 SubtleCrypto.digest()
函数来确定哈希,因此 forge 库实际上并不是必需的(至少对于散列)。
我没有详细分析密钥导入,所以这里也可能存在问题。