这是获取签名的 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());
                    });
            })
    }
})
});

我也查了https://github.com/digitalbazaar/forge

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 库实际上并不是必需的(至少对于散列)。

我没有详细分析密钥导入,所以这里也可能存在问题。