65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 JavaScript 签名和验证表单

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2016年7月6日

CPOL

1分钟阅读

viewsIcon

34328

downloadIcon

927

客户端签名和验证。

引言

演示如何创建一个表单,要求用户使用他们的私钥对其进行签名,然后验证签名。我假设你已经了解 PKI 机制。 我在各种地方搜索过,一篇有帮助的文章是 http://stackoverflow.com/questions/36018233/how-to-load-a-pkcs12-digital-certificate-with-javascript-webcrypto-api 以及其他文章。

这里讨论的 API 仅在安全(或本地文件)连接中有效。

使用 WebCrypto API 签名

通常我们有一个用户将要填写表单,并且希望对其进行数字签名。例如,像这样的表单

//
<form name="form1" id="form1" method="post" action="">
  <label for="firstname">First name:</label>
  <input type="text" name="firstname" id="firstname" required><br>
  <label for="lastname">Last name:</label>
  <input type="text" name="lastname" id="lastname" required><br>
</form>
//

我们可以使用 jQuery 获取此表单的内容

//
$('#form1').serialize();
//

我们还需要让用户选择他的 PFX 文件并输入私钥密码

 <label for="pfx">Select PFX/P12 file:</label><br>
 <input name="pfx" type="file" id="pfx" accept=".pfx,.p12" required /><br>
  <label for="pfxp">Enter Private Key password:</label><br>
 <input name="pfxp" type="password" id="pfxp" /><br>

现在我们需要使用 forge.js 将 PFX 文件读取到一个结构中

// Get PFX
    var fileInput = document.getElementById('pfx');
    var file = fileInput.files[0];

    // Read it
    var reader = new FileReader();
    reader.onload = function(e) 
        {
        var contents = e.target.result;
        var pkcs12Der = arrayBufferToString(contents)
        var pkcs12B64 = forge.util.encode64(pkcs12Der);
        var privateKey;
        var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
        var password = $('#pfxp').val();
        
        var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
        // load keys
        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];
                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...
                    cert = safeBag.cert;
                    }    
                }
            }
       }
reader.readAsArrayBuffer(file); 

这将把我们的私钥和证书读取到两个变量中,分别是 privatekey 和 cert。 现在我们需要将其导入到 WebCrypto PKCS#8

 

function importCryptoKeyPkcs8(privateKey,extractable) 
    {
    var privateKeyInfoDerBuff = privateKeyToPkcs8(privateKey);

    //Importa la clave en la webcrypto
    return crypto.subtle.importKey(
        'pkcs8',
        privateKeyInfoDerBuff,
        { name: "RSASSA-PKCS1-v1_5", hash:{name:"SHA-256"}},
        extractable,
        ["sign"]);
    }

 

现在我们可以签名了

importCryptoKeyPkcs8(privateKey,true).then(function(cryptoKey) 
            {
            // Imported!
            
            // Empty stuff
            var digestToSignBuf = stringToArrayBuffer(ser);
            var pem = forge.pki.certificateToPem(cert);
            $('#pfxc').val(forge.util.encode64(pem));

            crypto.subtle.sign(
                {name: "RSASSA-PKCS1-v1_5"},
                cryptoKey,
                digestToSignBuf)
                .then(function(signature){
                    sign = arrayBufferToString(signature);
                    signatureB64 = forge.util.encode64(sign);
            });
        
        });

我们可以存储原始文本、PEM 格式的证书以及 base64 格式的签名。

验证签名

为了验证,我们需要三个项目(数据、签名、证书)

 

// From Public Key to a PKCS#8
function publicKeyToPkcs8(pk) 
    {
    var subjectPublicKeyInfo = forge.pki.publicKeyToAsn1(pk);
    var der = forge.asn1.toDer(subjectPublicKeyInfo).getBytes();
    return stringToArrayBuffer(der);
    }

// Verify it
function Verify()
    {
    var pem = ...
    var signature64 = ...
    var signature = forge.util.decode64(signature64);
    var data = ...
    var cert = forge.pki.certificateFromPem(pem);

    // Import the certifcate
    window.crypto.subtle.importKey("spki",publicKeyToPkcs8(cert.publicKey),
        {   
        name: "RSASSA-PKCS1-v1_5",
        hash: {name: "SHA-256"}, 
        },
        false,
        ["verify"]
        ).then(function(k)
            {
            window.crypto.subtle.verify(
                {
                name: "RSASSA-PKCS1-v1_5",
                },
                k, //from generateKey or importKey above
                stringToArrayBuffer(signature), //ArrayBuffer of the signature
                stringToArrayBuffer(data) //ArrayBuffer of the data
                ).then(function(isvalid)
                {
                //returns a boolean on whether the signature is true or not
                   if (!isvalid)
                       {
                       }
                else
                       {
                         // Valid signature
                       }
                }).catch(function(err)
                    {
                       // Invalid sig or something not worked
                    });
                }
                
            );
    }

HTML 文件

你可以使用附带的 HTML 文件进行实验。 截至此版本,并非所有证书都适用于此过程。 如果你发现错误,请告诉我!

历史

06 - 07 - 2016 : 首次发布。

© . All rights reserved.