java - Bouncycastle का उपयोग कर हस्ताक्षर हस्ताक्षर करने और सत्यापित करने का सही तरीका




encryption cryptography (4)

मैं एक string पर हस्ताक्षर करने के लिए bcmail-jdk16-1.46.jar और bcprov-jdk16-1.46.jar (Bouncycastle लाइब्रेरीज़) का उपयोग कर रहा हूं और फिर signature सत्यापित कर रहा हूं।

एक string पर हस्ताक्षर करने code लिए यह मेरा code है:

package my.package;

import java.io.FileInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

import sun.misc.BASE64Encoder;

public class SignMessage {

    static final String KEYSTORE_FILE = "keys/certificates.p12";
    static final String KEYSTORE_INSTANCE = "PKCS12";
    static final String KEYSTORE_PWD = "test";
    static final String KEYSTORE_ALIAS = "Key1";

    public static void main(String[] args) throws Exception {

        String text = "This is a message";

        Security.addProvider(new BouncyCastleProvider());

        KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
        ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
        Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());

        //Sign
        PrivateKey privKey = (PrivateKey) key;
        Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
        signature.initSign(privKey);
        signature.update(text.getBytes());

        //Build CMS
        X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
        List certList = new ArrayList();
        CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
        certList.add(cert);
        Store certs = new JcaCertStore(certList);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
        gen.addCertificates(certs);
        CMSSignedData sigData = gen.generate(msg, false);

        BASE64Encoder encoder = new BASE64Encoder();

        String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
        System.out.println("Signed content: " + signedContent + "\n");

        String envelopedData = encoder.encode(sigData.getEncoded());
        System.out.println("Enveloped data: " + envelopedData);
    }
}

अब, EnvelopedData आउटपुट इस तरीके से signature को verify करने के verify प्रक्रिया में उपयोग किया जाएगा:

package my.package;

import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;

public class VerifySignature {

    public static void main(String[] args) throws Exception {

        String envelopedData = "MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIAwggLQMIIC" + 
                               "OQIEQ479uzANBgkqhkiG9w0BAQUFADCBrjEmMCQGCSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5k" + 
                               "ZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEi" +
                               "MCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUt" + 
                               "Y29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZDAeFw0wNTEyMDExMzQyMTlaFw0xOTA4MTAxMzQy" + 
                               "MTlaMIGuMSYwJAYJKoZIhvcNAQkBFhdyb3NldHRhbmV0QG1lbmRlbHNvbi5kZTELMAkGA1UEBhMC" + 
                               "REUxDzANBgNVBAgTBkJlcmxpbjEPMA0GA1UEBxMGQmVybGluMSIwIAYDVQQKExltZW5kZWxzb24t" + 
                               "ZS1jb21tZXJjZSBHbWJIMSIwIAYDVQQLExltZW5kZWxzb24tZS1jb21tZXJjZSBHbWJIMQ0wCwYD" + 
                               "VQQDEwRtZW5kMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+X1g6JvbdwJI6mQMNT41GcycH" + 
                               "UbwCFWKJ4qHDaHffz3n4h+uQJJoQvc8yLTCfnl109GB0yL2Y5YQtTohOS9IwyyMWBhh77WJtCN8r" + 
                               "dOfD2DW17877te+NlpugRvg6eOH6np9Vn3RZODVxxTyyJ8pI8VMnn13YeyMMw7VVaEO5hQIDAQAB" + 
                               "MA0GCSqGSIb3DQEBBQUAA4GBALwOIc/rWMAANdEh/GgO/DSkVMwxM5UBr3TkYbLU/5jg0Lwj3Y++" + 
                               "KhumYSrxnYewSLqK+JXA4Os9NJ+b3eZRZnnYQ9eKeUZgdE/QP9XE04y8WL6ZHLB4sDnmsgVaTU+p" + 
                               "0lFyH0Te9NyPBG0J88109CXKdXCTSN5gq0S1CfYn0staAAAxggG9MIIBuQIBATCBtzCBrjEmMCQG" + 
                               "CSqGSIb3DQEJARYXcm9zZXR0YW5ldEBtZW5kZWxzb24uZGUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQI" + 
                               "EwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEiMCAGA1UEChMZbWVuZGVsc29uLWUtY29tbWVyY2Ug" + 
                               "R21iSDEiMCAGA1UECxMZbWVuZGVsc29uLWUtY29tbWVyY2UgR21iSDENMAsGA1UEAxMEbWVuZAIE" + 
                               "Q479uzAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx" + 
                               "DxcNMTMwNTIxMDE1MDUzWjAjBgkqhkiG9w0BCQQxFgQU8mE6gw6iudxLUc9379lWK0lUSWcwDQYJ" + 
                               "KoZIhvcNAQEBBQAEgYB5mVhqJu1iX9nUqfqk7hTYJb1lR/hQiCaxruEuInkuVTglYuyzivZjAR54" + 
                               "zx7Cfm5lkcRyyxQ35ztqoq/V5JzBa+dYkisKcHGptJX3CbmmDIa1s65mEye4eLS4MTBvXCNCUTb9" + 
                               "STYSWvr4VPenN80mbpqSS6JpVxjM0gF3QTAhHwAAAAAAAA==";

        Security.addProvider(new BouncyCastleProvider());

        CMSSignedData cms = new CMSSignedData(Base64.decode(envelopedData.getBytes()));
        Store store = cms.getCertificates(); 
        SignerInformationStore signers = cms.getSignerInfos(); 
        Collection c = signers.getSigners(); 
        Iterator it = c.iterator();
        while (it.hasNext()) { 
            SignerInformation signer = (SignerInformation) it.next(); 
            Collection certCollection = store.getMatches(signer.getSID()); 
            Iterator certIt = certCollection.iterator();
            X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
            X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
                System.out.println("verified");
            }
        }

    }

}

निम्नलिखित Exception कारण signer.verify(..) तक सब कुछ अच्छा काम करता है:

Exception in thread "main" org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value
    at org.bouncycastle.cms.SignerInformation.doVerify(Unknown Source)
    at org.bouncycastle.cms.SignerInformation.verify(Unknown Source)
    at my.package.VerifySignature.main(VerifySignature.java:64)

और मैं वास्तव में नहीं जानता कि मैं क्या गलत कर रहा हूं। क्या कोई मुझे बता रहा है कि क्या हो रहा है?

पीएस अगर कोई उपर्युक्त code का परीक्षण करना चाहता है, तो आपको टेस्ट certificate फ़ाइल की आवश्यकता होगी जिसका उपयोग मैं इन सभी को दोहराने के लिए कर रहा हूं, बस इसे यहां से download/save करें:

https://dl.dropboxusercontent.com/u/15208254/keys/certificates.p12


आप here इस लिंक पर उत्तर पा सकते हैं आपको संदेश में कुछ शीर्षलेख जोड़ने की आवश्यकता है या संदेश से पहले एक खाली रेखा जोड़ें, फिर संदेश पर हस्ताक्षर करें, फिर यह ठीक काम करेगा।


यदि हम ओपी के उत्तर में signature.sign () का उपयोग करते हैं, तो हम मूल संदेश पुनर्प्राप्त नहीं कर पाएंगे, क्योंकि यह केवल हस्ताक्षर है।

आपको हस्ताक्षरित सामग्री की बजाय मूल टेक्स्ट बाइट्स को इनपुट करना चाहिए। असल में, इस भाग को अनदेखा करें:

//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(text.getBytes());

और बस के रूप में इनपुट:

CMSTypedData msg = new CMSProcessableByteArray(text.getBytes());

gen.generate(msg, false)

का मतलब है कि हस्ताक्षरित डेटा हस्ताक्षर में encapsulated नहीं है। यह ठीक है अगर आप एक अलग हस्ताक्षर बनाना चाहते हैं, लेकिन इसका मतलब यह है कि जब आप साइनडडेटा को सत्यापित करने के लिए जाते हैं तो आपको CMSSignedData निर्माता का उपयोग करना होगा जो डेटा की प्रतिलिपि भी लेता है - इस मामले में कोड एकल का उपयोग कर रहा है तर्क कन्स्ट्रक्टर जिसे हस्ताक्षरित डेटा मानना ​​है, को समाहित किया गया था (इसलिए इस मामले के लिए खाली हो जाएगा), जिसके परिणामस्वरूप सत्यापन पर प्रयास विफल हो रहा है।


CMSSignedDataGenerator का उपयोग करके उत्पन्न दो प्रकार के CMSSignedData ऑब्जेक्ट हैं वे निम्न तरीके से उत्पन्न होते हैं:

नीचे दिया गया एक अलग सीएमएस हस्ताक्षर ले जाने वाला एक CMSSignedData ऑब्जेक्ट उत्पन्न करता है

gen.generate(cmsdata);

नीचे दिया गया कोड एक पृथक सीएमएस हस्ताक्षर लेते हुए एक CMSSignedData बनाता है, जिसमें डेटा encapsulated है

gen.generate(cmsdata, true);

तो उन्हें सत्यापित करने के लिए 2 दृष्टिकोण की आवश्यकता है

Encapsulated डेटा के साथ अलग हस्ताक्षर सत्यापित करने के लिए दृष्टिकोण संख्या 1

//sig is the Signature object
CMSSignedData signedData = new CMSSignedData(Base64.decode(sig.getBytes()));

Encapsulated डेटा के बिना अलग हस्ताक्षर सत्यापित करने के लिए दृष्टिकोण संख्या 2, बस अलग हस्ताक्षर

//Create a CMSProcessable object, specify any encoding, I have used mine 
CMSProcessable signedContent = new CMSProcessableByteArray(content.getBytes("ISO-8859-1"));
//Create a InputStream object
InputStream is = new ByteArrayInputStream(Base64.decode(sig.getBytes()));
//Pass them both to CMSSignedData constructor
CMSSignedData signedData = new CMSSignedData(signedContent, is);

सत्यापन के लिए शेष कोड वही रहता है

Store store = signedData.getCertificates(); 
SignerInformationStore signers = signedData.getSignerInfos(); 

Collection c = signers.getSigners(); 
Iterator it = c.iterator(); 

while (it.hasNext()) { 
    SignerInformation signer = (SignerInformation)it.next(); 

    Collection certCollection = store.getMatches(signer.getSID()); 
    Iterator certIt = certCollection.iterator(); 

    X509CertificateHolder certHolder = (X509CertificateHolder)certIt.next(); 
    X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder); 

    if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
        ret = true; 
    }
}




bouncycastle