bouncy castle java example
Forma correcta de firmar y verificar la firma usando bouncycastle (5)
Estoy usando bcmail-jdk16-1.46.jar
y bcprov-jdk16-1.46.jar
(bibliotecas de Bouncycastle) para firmar una string
y luego verificar la signature
.
Este es mi code
para firmar una string
:
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);
}
}
Ahora, la salida EnvelopedData
se usará en el proceso para verify
la signature
de esta manera:
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");
}
}
}
}
Todo funciona bien hasta signer.verify(..)
debido a la siguiente Exception
:
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)
Y realmente no sé lo que estoy haciendo mal. ¿Puede alguien darme una pista de lo que está pasando?
PD . Si alguien quiere probar el code
anterior, necesitará el archivo de certificate
prueba que estoy usando para replicar todo esto, simplemente download/save
desde aquí:
https://dl.dropboxusercontent.com/u/15208254/keys/certificates.p12
Hay dos tipos de objetos CMSSignedData generados usando CMSSignedDataGenerator. Se generan de la siguiente manera:
El siguiente genera un objeto CMSSignedData que lleva una firma de CMS separada
gen.generate(cmsdata);
El código siguiente crea un CMSSignedData que lleva una firma de CMS separada, teniendo los datos encapsulados
gen.generate(cmsdata, true);
Así que verificarlos requiere 2 enfoques
Enfoque No.1 para verificar la firma separada con datos encapsulados
//sig is the Signature object
CMSSignedData signedData = new CMSSignedData(Base64.decode(sig.getBytes()));
Enfoque No.2 para verificar la firma separada sin datos encapsulados, solo la firma separada
//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);
El resto del código para la verificación sigue siendo el mismo
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;
}
}
Lo conseguí trabajando para la firma separada: D
package signature;
import java.security.Provider;
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.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
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 {
static final String DIGEST_SHA1 = "SHA1withRSA";
static final String BC_PROVIDER = "BC";
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==";
String Sig_Bytes ="YduK22AlMLSXV3ajX5r/pX5OQ0xjj58uhGT9I9MvOrz912xNHo+9OiOKeMOD+Ys2/LUW3XaN6T+/"+
"tuRM5bi4RK7yjaqaJCZWtr/O4I968BQGgt0cyNvK8u0Jagbr9MYk6G7nnejbRXYHyAOaunqD05lW"+
"U/+g92i18dl0OMc50m4=";
Provider provider = new BouncyCastleProvider();
Security.addProvider(provider);
CMSSignedData signedData = new CMSSignedData(Base64.decode(envelopedData.getBytes()));
CMSProcessable cmsProcesableContent = new CMSProcessableByteArray(Base64.decode(Sig_Bytes.getBytes()));
signedData = new CMSSignedData(cmsProcesableContent, Base64.decode(envelopedData.getBytes()));
// Verify signature
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 certFromSignedData = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC_PROVIDER).build(certFromSignedData))) {
System.out.println("Signature verified");
} else {
System.out.println("Signature verification failed");
}
}
}
}
Puede encontrar la respuesta en este enlace here . Necesita simplemente agregar algunos encabezados para enviar mensajes o simplemente agregar una línea en blanco antes del mensaje, luego firme el mensaje y luego funcionará correctamente.
Si usamos signature.sign () como en la respuesta del OP, no podremos recuperar el mensaje original, porque es solo la firma.
Solo debe ingresar los bytes de texto originales en lugar del contenido firmado. Básicamente, ignora esta parte:
//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(text.getBytes());
y solo ingrese como:
CMSTypedData msg = new CMSProcessableByteArray(text.getBytes());
los
gen.generate(msg, false)
significa que los datos firmados no están encapsulados en la firma. Esto está bien si desea crear una firma separada, pero significa que cuando vaya a verificar el SignedData debe usar el constructor CMSSignedData que también toma una copia de los datos, en este caso el código está usando el único El constructor de argumentos que tiene que asumir que los datos firmados fueron encapsulados (por lo que para este caso estará vacío), con el resultado de que el intento de verificación está fallando.