I want to sign file with the SunMSCAPI provider. As public key and signatures needs to be imported using MS Crypto API.
Generally generating signatures with SHA1withRSA, ends up with big-endian to little-endian (byte order) conversion.
//generate keystore with java keytool
$Keytool -genkey -alias tsign -keystore c:\test\tsignjks.p12 - keyalg rsa -storetype pkcs12
In Java application:
//for signing and getting keystore, assuming windows certificate is installed
..ks = KeyStore.getInstance("Windows-MY","SunMSCAPI");
PrivateKey priv = ks.getKey("tsign",password);
Signature rsa = Signature.getInstance("SHA1withRSA","SunMSCAPI");
rsa.initSign(priv);
..
rsa.update(buffer, 0, len);
..
byte[] realSig = rsa.sign();
//for writing public key for ms crypto api or exporting it from windows certificate store
Certificate cert = ks.getCertificate("tsign");
byte[] encodedCert = cert.getEncoded();
FileOutputStream certfos = new FileOutputStream("tsigncer.cer");
certfos.write(encodedCert);
//for writing signatures for ms crypto api
FileOutputStream sigfos = new FileOutputStream(targetPath + "/"
+ signatureName);
sigfos.write(realSig);
I believe that SunMSCAPI can resolve my problem, but I don't know when i import public key using MS Crypto API, It never import at at first stage (unless i change big endian to little endian byte order) below is my code for crypto API.
LPCSTR file = "tsigncer.cer";
//LPCSTR file = "omsign.p12";
BOOL crypt_res = FALSE;
HCRYPTPROV crypt_prov_hndl = NULL;
crypt_res = CryptAcquireContext(&crypt_prov_hndl, NULL, NULL, PROV_RSA_FULL, 0/*CRYPT_NEWKEYSET*/);
//crypt_res = CryptAcquireContext(&crypt_prov_hndl, NULL, NULL, PROV_DSS, CRYPT_VERIFYCONTEXT/*CRYPT_NEWKEYSET*/);
if (!crypt_res) {
HRESULT decode_hr = __HRESULT_FROM_WIN32(GetLastError());
return decode_hr;
}
// Load key file
HANDLE fileHandle = CreateFile(file, // name of the write
GENERIC_READ, // open for writing
0, // do not share
NULL, // default security
OPEN_EXISTING, // create new file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL); // no attr. template
if (fileHandle == INVALID_HANDLE_VALUE)
{
DWORD d = GetLastError();
return -1;
}
BYTE buffer[2056];
DWORD fileSize = 0;
DWORD fileSizeResult = GetFileSize(fileHandle, &fileSize);
DWORD numBytesRead = 0;
BOOL fileLoadResult = ReadFile(fileHandle, (PVOID)buffer, fileSizeResult, &numBytesRead, NULL);
// Import key
BOOL result = ImportKey(crypt_prov_hndl, (LPBYTE)buffer, numBytesRead);
//result is always false..
If you work with MSCAPI, it is assumed that you've added your key to the Microsoft Certificate store. You can check if the key is present by going to "Internet Properties" > "Content" > "Certificates" which gives you a list of certificates that are available. If your certificate isn't there, you can't use it. If it's there, you need this code:
SunMSCAPI providerMSCAPI = new SunMSCAPI();
Security.addProvider(providerMSCAPI);
KeyStore ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
From there on, the code is pretty standard. Please consult my book on digital signatures for more info (the book is free).
IMPORTANT ADDITION: I forgot to mention that SunMSCAPI isn't present in the 64-bit version of Java 6 (I don't know about Java 7). You can fix this by installing the 32-bit version.
Related
I am going to sign a PDF from the client site.
I will create a web service to generate the PDF hash using itext5.5 (Java), and then send to the client agent to sign that hash (using iTextSharp-5.5), and send back the signed hash to the web service for closing the signature state.
But the signed PDF got the following error when validate the signature:
Error during signature verification.
Error encountered while BER decoding:
in the server side (Java):
ExternalDigest externalDigest = new BouncyCastleDigest();
PdfPKCS7 sign = new PdfPKCS7(null, chain, "SHA1", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
byte[] digestHash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
ocsp = ocspClient.getEncoded((X509Certificate)chain[0], (X509Certificate)chain[1], null);
}
byte[] sh = sign.getAuthenticatedAttributeBytes(digestHash, signCal, ocsp, crlBytes, SIGN_TYPE);
this.hash = digestHash;
this.hashForSign = sh;
in C#, I using the following code to sign:
IExternalSignature es = new X509Certificate2Signature(cert, "SHA1");
byte[] signedHash = es.Sign(hashForSign);
Where this.hashForSign is the hash generate from Java, and the signedHash is signed in C#.
Please help!
Best regards,
Eric
I had a similar problem. This wasn't a Java language issue. One way to solve it is to do the following:
Go to the Windows Certificate Manager (certmgr.msc)
Try deleting all certificates under Personal
Recreate an ID in Adobe using Security settings.
I create a certificate with keytool:
keytool -genkeypair -alias sara -keyalg RSA -keysize 2048 -keypass
password -keystore "\Sviluppo\JavaKeyStore\keystore.jks" -storepass
12345678 -validity 360 -dname "cn=Sara, ou=***, o=***, l=Padova,
s=Italia, c=IT"
Then i want to sign a pdf with this certificate, i use Itextpdf to sign a pdf.
First I load private key and certificate from keystore:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
File fileKeyStore = new File(pathKeyStore);
FileInputStream fis = new FileInputStream(fileKeyStore);
keyStore.load(fis, "12345678".toCharArray());
final PrivateKey privateKey = (PrivateKey) keyStore.getKey("sara", "password".toCharArray());
final Certificate certificate = keyStore.getCertificate(certID);
then I open document for calculate hash to sign:
PdfReader reader = new PdfReader(new RandomAccessFileOrArray(pdfInputPath), null);
OutputStream pdfOutputStream = new FileOutputStream(pdfOutputPath);
PdfStamper stp = PdfStamper.createSignature(reader, pdfOutputStream, '\0', tempPathFile, true)
PdfSignatureAppearance sap = stp.getSignatureAppearance();
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setDate(dateNow);
sap.setCryptoDictionary(dic);
sap.setCrypto(privateKey, keyStore.getCertificateChain("sara"), null, PdfSignatureAppearance.SELF_SIGNED);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, (int) (6144 * 2 + 2));
sap.preClose(exc);
then calculate hash of sap.getRangeStream(), load certificate from keystore:
BufferedInputStream bis = new BufferedInputStream(sap.getRangeStream());
MessageDigest digest = MessageDigest.getInstance("SHA-256");
DigestInputStream dis = new DigestInputStream(bis, digest);
byte[] buff = new byte[512];
while (dis.read(buff) != -1) {
;
}
dis.close();
dis = null;
byte[] hashToSign= digest.digest();
bis.close();
I sign with certificate from keystore:
java.security.Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(hashToSign);
byte[] hashSigned = signature.sign();
at the end close pdf:
byte[] paddedSig = new byte[6144];
System.arraycopy(hashSigned, 0, paddedSig, 0, hashSigned.length);
PdfDictionary pdfDictionary = new PdfDictionary();
pdfDictionary.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
sap.close(pdfDictionary);
But the pdf signed is invalid: "There are errors in the formatting or information contained in this signature"
What's the problem, the certificare or signature?
Thanks for help
Sara
The cause for the "There are errors in the formatting or information contained in this signature" validation failure message is that your code puts a naked PKCS1 signature value where a full-fledged CMS signature container is expected:
You on the one hand use SELF_SIGNED in your sap.setCrypto which indicates that you want to create an adbe.x509.rsa_sha1 SubFilter signature, and on the other hand set the SubFilter to adbe.pkcs7.detached in your PdfSignature constructor.
This does not match at all, adbe.x509.rsa_sha1 uses naked PKCS1 signature values while adbe.pkcs7.detached uses CMS signature containers.
This should answer your question "What's the problem, the certificare or signature?"...
According to a comment you meanwhile found a solution in a copy of iText in Action, 2nd Edition.
Please be aware, though, that the iText signing API meanwhile has been substantially extended. If you are working with iText 5.5.x, you should download and read Digital Signatures for PDF Documents, a white paper by Bruno Lowagie (iText Software).
While the code from iText in Action, 2nd Edition, still focused on signatures according to ISO 32000-1:2008 (SOTA at the time of its publishing but not anymore), Digital Signatures for PDF Documents focuses on signatures according to PAdES which meanwhile have been included in ISO 32000-2:2017.
I'm unable to add a certificate with a 2048 bit key to a Bouncy Castle KeyStore. I've updated my version of JCE, both the JRE and JDK security folders, with UnlimitedJCEPolicyJDK7.zip. The code below indicates the error location. I'm using bcprov-jdk15on-149 but have tried bcprov-jdk15on-157 with the same results. There are a number of posts regarding symmetric encryption problems but fewer on PKE. I'm running Windows 10 Pro, JRE 7, JDK 1.7.0_51. I'd appreciate any suggestions.
char[] testPass = "changeit".toCharArray();
String testAlias = "express";
// -----------------------------------------------------------------
// Open source TrustStore and extract certificate and key
FileInputStream jksFis = new FileInputStream("G:\\testSrc.jks");
KeyStore jksKS = KeyStore.getInstance(KeyStore.getDefaultType());
jksKS.load(jksFis, testPass);
PrivateKey jksPK = (PrivateKey) jksKS.getKey(testAlias,testPass);
RSAKey rsaKey = (RSAKey)jksPK;
int rsaKeyLen = rsaKey.getModulus().bitLength();
System.out.printf("Key length is %d\n",rsaKeyLen); // 2048
X509Certificate[] jksCerts = new X509Certificate[1];
jksCerts[0] = (X509Certificate) jksKS.getCertificate(testAlias);
// -----------------------------------------------------------------
// Create new default type keystore and add certificate and key.
KeyStore jksDest = KeyStore.getInstance(KeyStore.getDefaultType());
jksDest.load(null,null);
jksDest.setKeyEntry(testAlias, jksPK, testPass, jksCerts);
FileOutputStream jfos = new FileOutputStream("G:\\testDest.jks");
jksDest.store(jfos, testPass);
jfos.close();
// -----------------------------------------------------------------
// Create Bouncy Castle KeyStore and add certificate and key
Security.addProvider(new BouncyCastleProvider());
KeyStore bksKS = KeyStore.getInstance("PKCS12","BC");
bksKS.load(null,null);
bksKS.setKeyEntry(testAlias, jksPK, testPass, jksCerts);
FileOutputStream bksFos = new FileOutputStream("G:\\testDest.bks");
// -----------------------------------------------------------------
// Next line gives this error:
// java.io.IOException: exception encrypting data -
// java.security.InvalidKeyException: Illegal key size
bksKS.store(bksFos, testPass); // This is the error line.
// Error on previous line.
The procedure for installing the JCE update seems pretty straightforward so one of my assumptions about the version I'm using may be mistaken. As Omikron pointed out in his helpful comment it shouldn't have mattered anyway. He did get me going in the right direction which led to the solution. I'm posting the revised code below. I'm not sure why the default keystore type worked in the first place and bouncy castle didn't. Maybe someone familiar with bouncycastle will share their thoughts. In the meantime I'm going to see if this works on Android too.
public static void main(String[] args) {
try{
// -----------------------------------------------------------------
// Anonymous recommendation I found here:
// http://suhothayan.blogspot.com/2012/05/how-to-install-java-cryptography.html
// This fixed my problem.
try {
Field field = Class.forName("javax.crypto.JceSecurity").
getDeclaredField("isRestricted");
field.setAccessible(true);
field.set(null, java.lang.Boolean.FALSE);
} catch (Exception ex) {
ex.printStackTrace();
}
// -----------------------------------------------------------------
// Check recommended by Omikron, who was correct: I assume I didn't
// install the JCE properly because it prints 128 for the max
// key allowd key length.
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
System.out.printf("max key len: %d\n",maxKeyLen);
// -----------------------------------------------------------------
char[] testPass = "changeit".toCharArray();
String testAlias = "express";
// -----------------------------------------------------------------
// Open source TrustStore and extract certificate and key
FileInputStream jksFis = new FileInputStream("G:\\testSrc.jks");
KeyStore jksKS = KeyStore.getInstance(KeyStore.getDefaultType());
jksKS.load(jksFis, testPass);
PrivateKey jksPK = (PrivateKey) jksKS.getKey(testAlias,testPass);
RSAKey rsaKey = (RSAKey)jksPK;
int rsaKeyLen = rsaKey.getModulus().bitLength();
System.out.printf("JKS key length is %d\n",rsaKeyLen); // 2048
X509Certificate[] jksCerts = new X509Certificate[1];
jksCerts[0] = (X509Certificate) jksKS.getCertificate(testAlias);
// -----------------------------------------------------------------
// Create new default type keystore and add certificate and key.
KeyStore jksDest = KeyStore.getInstance(KeyStore.getDefaultType());
jksDest.load(null,null);
jksDest.setKeyEntry(testAlias, jksPK, testPass, jksCerts);
FileOutputStream jfos = new FileOutputStream("G:\\testDest.jks");
jksDest.store(jfos, testPass);
jfos.close();
// -----------------------------------------------------------------
// Create Bouncy Castle KeyStore and add certificate and key
Security.addProvider(new BouncyCastleProvider());
KeyStore bksKS = KeyStore.getInstance("PKCS12","BC");
bksKS.load(null,null);
bksKS.setKeyEntry(testAlias, jksPK, testPass, jksCerts);
FileOutputStream bksFos = new FileOutputStream("G:\\testDest.bks");
bksKS.store(bksFos, testPass);
bksFos.close();
// -------------------------
// Open file and check key length:
bksKS = KeyStore.getInstance("PKCS12","BC");
FileInputStream bksFis = new FileInputStream("G:\\testDest.bks");
bksKS.load(bksFis, testPass);
PrivateKey bpk = (PrivateKey) bksKS.getKey(testAlias,testPass);
rsaKey = (RSAKey)bpk;
rsaKeyLen = rsaKey.getModulus().bitLength();
System.out.printf("BKS key length is %d\n",rsaKeyLen); // 2048
X509Certificate bkCert = (X509Certificate) bksKS.getCertificate(testAlias);
System.out.printf("Issuer name: %s", bkCert.getIssuerDN().getName());
}catch(Exception e){
e.printStackTrace();
}
}
I am currently trying to adapt a few scripts we use to sign an encrypt/decrypt xml files using OpenSSL and S/MIME using Java and BouncyCastle.
The command to sign and encrypt our file:
openssl smime -sign -signer Pub1.crt -inkey Priv.key -in foo.xml | openssl smime -encrypt -out foo.xml.smime Pub2.crt Pub1.crt
This generates a signed and encrypted smime-file containing our xml file. Currently this happens using a set of shell scripts under linux using the OpenSSL library. In the future we want to integrate this process into our Java application.
I've found out that this should be possible using the BouncyCastle library (see this post). The answer there provides two Java classes showing how to sign and encrypt an email using BouncyCastle and S/MIME. Comparing this to our OpenSSL command it seems that many of the things needed to sign an encrypt an email is not needed in our approach.
Some more meta information from our generated files:
Signed file
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha-256"; boundary="----709621D94E0377688356FAAE5A2C1321"
Encrypted file
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64
Is it even possible to sign and encrypt a simple file in the way we did it using OpenSSL? My current knowledge of signing and de/encryption is not very high at the moment so forgive me for not providing code samples. I guess what I am looking for is more input into what I need to do and maybe some expertise from people who have already done this. I hope this is the right place to ask this. If not, please correct me.
I had a similar question as you but I managed to solve it. I have to warn you, my knowledge about signing and encryption isn't that high either. But this code seemed to work for me.
In my case I used a personalsign pro 3 certificate from globalsign, Previously I just called openssl from within java. But the I wanted to clean my code and decided to use bouncy castle instead.
public static boolean signAllFiles(List<File> files) {
Boolean signingSucceeded = true;
KeyStore ks = null;
char[] password = null;
Security.addProvider(new BouncyCastleProvider());
try {
ks = KeyStore.getInstance("PKCS12");
password = "yourpass".toCharArray();
ks.load(new FileInputStream("full/path/to/your/original/certificate.pfx"), password);
} catch (Exception e) {
signingSucceeded = false;
}
// Get privatekey and certificate
X509Certificate cert = null;
PrivateKey privatekey = null;
try {
Enumeration<String> en = ks.aliases();
String ALIAS = "";
Vector<Object> vectaliases = new Vector<Object>();
while (en.hasMoreElements())
vectaliases.add(en.nextElement());
String[] aliases = (String[])(vectaliases.toArray(new String[0]));
for (int i = 0; i < aliases.length; i++)
if (ks.isKeyEntry(aliases[i]))
{
ALIAS = aliases[i];
break;
}
privatekey = (PrivateKey)ks.getKey(ALIAS, password);
cert = (X509Certificate)ks.getCertificate(ALIAS);
// publickey = ks.getCertificate(ALIAS).getPublicKey();
} catch (Exception e) {
signingSucceeded = false;
}
for (File source : files) {
String fileName = "the/path/andNameOfYourOutputFile";
try {
// Reading files which need to be signed
File fileToSign = source;
byte[] buffer = new byte[(int)fileToSign.length()];
DataInputStream in = new DataInputStream(new FileInputStream(fileToSign));
in.readFully(buffer);
in.close();
// Generate signature
ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
certList.add(cert);
Store<?> certs = new JcaCertStore(certList);
CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();
ContentSigner sha1signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
privatekey);
signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().build()).build(sha1signer, cert));
signGen.addCertificates(certs);
CMSTypedData content = new CMSProcessableByteArray(buffer);
CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();
// Write signature to Fi File
FileOutputStream envfos = new FileOutputStream(fileName);
byte[] outputString = Base64.encode(signeddata);
int fullLines = (int)Math.floor(outputString.length / 64);
for (int i = 0; i < fullLines; i++) {
envfos.write(outputString, i * 64, 64);
envfos.write("\r\n".getBytes());
}
envfos.write(outputString, fullLines * 64, outputString.length % 64);
envfos.close();
} catch (Exception e) {
signingSucceeded = false;
}
}
return signingSucceeded;
}
This is only the code to sign a file, I hope it helps.
I want to create a X509 certificate using Java language and then extract public key from it.
I have searched the internet and found many code examples, but all of them have errors (unknown variable or unknown type) or have many warnings that say something like : "the method ... from type ... is deprecated " etc.
For example, why the following code doesn't work:
PublicKey pk;
CertificateFactory cf = CertificateFactory.getInstance("X.509");
String PKstr = pk.toString();
InputStream PKstream = new ByteArrayInputStream(PKstr.getBytes());
X509Certificate pkcert = (X509Certificate)cf.generateCertificate(PKstream);
Can anyone show me how to create a certificate using pure Java or Bouncy Castle and then get a public key from that?
Thanks all.
For JDK versions <17, you can also generate a certificate using only JDK classes. The disadvantage is that you have to use two classes from the sun.security.x509 package.
The code would be:
KeyStore keyStore = ... // your keystore
// generate the certificate
// first parameter = Algorithm
// second parameter = signrature algorithm
// third parameter = the provider to use to generate the keys (may be null or
// use the constructor without provider)
CertAndKeyGen certGen = new CertAndKeyGen("RSA", "SHA256WithRSA", null);
// generate it with 2048 bits
certGen.generate(2048);
// prepare the validity of the certificate
long validSecs = (long) 365 * 24 * 60 * 60; // valid for one year
// add the certificate information, currently only valid for one year.
X509Certificate cert = certGen.getSelfCertificate(
// enter your details according to your application
new X500Name("CN=My Application,O=My Organisation,L=My City,C=DE"), validSecs);
// set the certificate and the key in the keystore
keyStore.setKeyEntry(certAlias, certGen.getPrivateKey(), null,
new X509Certificate[] { cert });
Retrieve the private key from the key store to encrypt or decrypt data.
Based on the code is from http://www.pixelstech.net/article/1408524957-Generate-cetrificate-in-Java----3
Yes, with BouncyCastle, creating an X509 cert from 2 public keys (the key for the cert and the one for the CA) is done here.
I turn the resulting certificate into PEM here.
For JDK >= 17, this is how to do it with BouncyCastle. The code example is in Kotlin, but it works exactly the same way in Java:
val keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair()
val subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.public.encoded)
val now = Instant.now()
val validFrom = Date.from(now)
val validTo = Date.from(now.plusSeconds(60L * 60 * 24 * 365))
val certBuilder = X509v3CertificateBuilder(
X500Name("CN=My Application,O=My Organisation,L=My City,C=DE"),
BigInteger.ONE,
validFrom,
validTo,
X500Name("CN=My Application,O=My Organisation,L=My City,C=DE"),
subPubKeyInfo
)
val signer = JcaContentSignerBuilder("SHA256WithRSA")
.setProvider(BouncyCastleProvider())
.build(keyPair.private)
val certificate = certBuilder.build(signer)
This is the gradledependency you need to pull:
implementation("org.bouncycastle:bcpkix-jdk18on:1.72")