Can't reproduce openssl command with BouncyCastle - java

I am trying, for already a couple of weeks to reproduce some openssl commands using bouncycastle and java.
After following a lot of samples and trying a lot of examples from Stackoverflow, I still can't make it work, thats why I'm asking for help now.
The openssl commands I have to reproduce are :
openssl smime -sign -in fileToSign.eml -out signedFile.step2 -passin pass:« password» -binary -nodetach -signer myprivatecert.pem -certfile mypubliccert.pem
This first commands takes 3 files, the file to sign, a private certificate and a public certificate.
It returns a file looking like :
MIME-Version: 1.0 Content-Disposition: attachment;
filename="smime.p7m" Content-Type: application/x-pkcs7-mime;
smime-type=signed-data; name="smime.p7m" Content-Transfer-Encoding:
base64
MIJAYAYJKoZIhvcNAQcCoIJAUTCCQE0CAQExDzANBglghkgBZQMEAgEFADCCNTUG
CSqGSIb3DQEHAaCCNSYEgjUiQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7
CmJvdW5kYXJ5PSItLS0tPUxPR0lQT0xfTUlGXzE1NDY4NTAwNDc4MTYiCi0tLS0t
LT1MT0dJUE9MX01JRl8xNTQ2ODUwMDQ3ODE2DQpDb250ZW50LVR5cGU6IHRleHQv
WE1MOw0KbmFtZT0iUERBX1A5MDAxMjZfMDA1XzIwMTkwMTA3LjA5MzIwMF8wMDAw
MV9JTklULnhtbCI7IGZpbGVuYW1lPSJQREFfUDkwMDEyNl8wMDVfMjAxOTAxMDcu
MDkzMjAwXzAwMDAxX0lOSVQueG1sIg0KQ29udGVudC1UcmFuc2Zlci1FbmNvZGlu
ZzogYmFzZTY0DQoNClBEOTRiV3dnZG1WeWMybHZiajBpTVM0d0lpQmxibU52Wkds
dVp6MGlWVlJHTFRnaVB6NDhUVWxHVmtGUFNXNW1iMGx1YVhScFlXeGwNClBnbzhT
VzVtYjNNK0NqeFdaWEp6YVc5dVBqSXVPVHd2Vm1WeWMybHZiajRLUEVodmNtOWtZ
WFJsUGpJd01Ua3RNREV0TURkVU1EazYNCk16UTZNRGM4TDBodmNtOWtZWFJsUGdv
OFUyRnBjMmxsU0c5eWIyUmhkR1UrTWpBeE9TMHdNUzB3TjFRd09Ub3pNam93TUR3...
Second command I have to use is :
openssl smime -encrypt -in signedFile.step2 -out encryptedFile.P7M -outform DER -binary anotherpubliccertificate.pub.pem
This command takes 2 files, the file signed by the previous command and a public certificate, different than the one used in the previous command.
This returns a binary file, an encrypted file generated from step 2.
Any of the examples I found over the internet helped me to get a file looking like these previous ones, not even close.
I hope someone can help
Edit
A few examples of what I tried, or refered to, until now
sign file with bouncy castle in java -> This returned a signed file that do not correspond to the signed file generated with openssl
AES encrypt/decrypt with Bouncy Castle provider -> Again, this isn't working, the result doesn't correspond to the encrypted file I generate with openssl
https://studylibfr.com/doc/3898805/cryptographie-avec-bouncy-castle---zenk -> Have been following the whole tutorial, not getting the expected result
X509 RSA bouncy castle sign and verify plain text in Java -> Signed file not corresponding too
https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/CreateSignedMultipartMail.java -> This class generate something that looks alike to what I'm trying to get, but I couldn't test it's validity as I have to encrypt it and still can't make encrytion work
https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/ReadSignedMail.java -> Same as previous class
https://github.com/bcgit/bc-java/blob/master/mail/src/main/java/org/bouncycastle/mail/smime/examples/SendSignedAndEncryptedMail.java -> This method of encryption isn't returning the same result as openssl, so it isn't working
For sure, I've tried to keep working on these samples classes from bouncycastle, but without success.
Any help would be appreciated
Edit 2
The answer of the following question Sign and encrypt a file using S/MIME returns a Base64 encoded file that might correspond to what I generate with openssl. But the problem is that my entry file is about 25kb and the signed file generated is only 3kb, I don't understand why, I noticed that at this line :
CMSTypedData content = new CMSProcessableByteArray(buffer);
CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();
the getEncoded() method returns me a byte array much smaller than the buffer I send to the CMSSignedData.
Does anyone knows the reason ?

For the signing, you were fairly close with org.bouncycastle.mail.smime.examples.CreateSignedMultipartMail except that
it does multipart data, which openssl smime doesn't do; start from CreateSignedMail instead
it does multipart signing, aka clear-signing, which openssl smime also defaults to, but -nodetach changes this to embedded aka encapsulated
it includes a full cert chain, but a self-generated one of length only 2 whereas nearly all 'real' certs are longer, whereas openssl by default includes only the signer cert
by default it uses some signedattributes different than openssl
For the encryption (or more exactly enveloping) openssl smime -outform der in spite of the name doesn't do SMIME at all, it does CMS (originally and still also known as PKCS7). Bouncy uses the full OO goodness of Java to put CMS and SMIME, which are very similar but not the same, into different classes that are related but not the same, so you need the CMS class(es).
Putting these together (plus a minimal test harness) I present to you:
// for test, (own) signing key+certchain and (peer) encryption cert in file
KeyStore ks = KeyStore.getInstance("JKS"); ks.load(new FileInputStream(args[0]),args[1].toCharArray());
PrivateKey signkey = (PrivateKey) ks.getKey(args[2], args[1].toCharArray());
Certificate[] signcert = ks.getCertificateChain(args[2]);
Certificate encrcert = ks.getCertificate(args[3]);
// and data in file
byte[] data = Files.readAllBytes(new File(args[4]).toPath());
// adapted from org.bouncycastle.mail.smime.examples.CreateSignedMail
// OpenSSL uses this rather silly capability list; may not be needed
SMIMECapabilityVector caps = new SMIMECapabilityVector();
caps.addCapability(SMIMECapability.aES256_CBC);
caps.addCapability(SMIMECapability.aES192_CBC);
caps.addCapability(SMIMECapability.aES128_CBC);
caps.addCapability(SMIMECapability.dES_EDE3_CBC);
caps.addCapability(SMIMECapability.rC2_CBC, 128);
caps.addCapability(SMIMECapability.rC2_CBC, 64);
caps.addCapability(SMIMECapability.dES_CBC);
caps.addCapability(SMIMECapability.rC2_CBC, 40);
ASN1EncodableVector signedAttrs = new ASN1EncodableVector();
signedAttrs.add(new SMIMECapabilitiesAttribute(caps));
// Bouncy default adds RFC6211 in addition to standard ctype, stime, mdgst
// and changing this is complicated; recipient _should_ ignore unneeded attr
SMIMESignedGenerator gen = new SMIMESignedGenerator();
gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder()//.setProvider("BC") not needed
.setSignedAttributeGenerator(new AttributeTable(signedAttrs))
.build("SHA1withRSA", signkey, (X509Certificate) signcert[0]) );
// change sigalg if key not RSA and/or want better hash
// OpenSSL by default includes only signer cert; recipient may want more
gen.addCertificates(new JcaCertStore (Arrays.asList (new Certificate[]{signcert[0]}) ));
MimeBodyPart msg = new MimeBodyPart();
msg.setText(new String(data, "ISO-8859-1")); // OpenSSL doesn't know charsets
ByteArrayOutputStream temp = new ByteArrayOutputStream();
gen.generateEncapsulated(msg).writeTo(temp); // OpenSSL -nodetach is encapsulated
// Bouncy uses BER here (unlike OpenSSL DER)
// and I don't see a simple way to change it but it _should_ not matter
byte[] signedblob = temp.toByteArray();
// now CMS (not SMIME) enveloping
CMSEnvelopedDataGenerator edgen = new CMSEnvelopedDataGenerator();
edgen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator((X509Certificate) encrcert));
CMSEnvelopedData edmsg = edgen.generate( new CMSProcessableByteArray(signedblob),
new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build() );
byte[] encrblob = edmsg.toASN1Structure().getEncoded(ASN1Encoding.DER); // OpenSSL is DER though std doesn't require it
// for test, write to a file
Files.write(new File(args[5]).toPath(), encrblob);
On, and 'does anyone know the reason'
CMSSignedData signedData = signGen.generate(content, false);
byte[] signeddata = signedData.getEncoded();
is smaller than the content? See the javadoc -- with encapsulate (the second parameter) set to false you told it to not include the content in the signature (more exactly SignedData), and it did as you demanded.

Related

Create X509Certificate from PKCS7 PEM formatted certificate using BouncyCastle in Java

I have a PKCS7 certificate chain in PEM format. Basically I’d like to perform following openssl command in Java using BouncyCastle library.
openssl pkcs7 -in client-certificate_pkcs7.pem -out client-certificate_chain.pem
I was following this pdf provided from BouncyCastle (https://www.bouncycastle.org/fips-java/BCFipsIn100.pdf), but couldn’t really find anything that works on my need.
I’ve found that CMSSignedData is used for pkcs7 related operations in BouncyCastle so I tried using it, but I was getting an error and I assume it’s because I was using a raw String.
String pkcs7Pem = "-----BEGIN PKCS7-----\nMIIR...WTEA\n-----END PKCS7-----\n";
CMSSignedData data = new CMSSignedData(pkcs7Pem.getBytes());
Store certStore = data.getCertificates();
SignerInformationStore signerInfos = data.getSignerInfos();
Collection<SignerInformation> signers = signerInfos.getSigners();
List<X509Certificate> x509Certificates = new ArrayList<>();
for (SignerInformation signer : signers) {
Collection<X509CertificateHolder> matches = certStore.getMatches(signer.getSID());
for (X509CertificateHolder holder : matches) {
x509Certificates.add(new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder));
}
}
This is the error I got on new CMSSignedData(pkcs7Pem.getBytes());
org.bouncycastle.cms.CMSException: IOException reading content.
I also have tried using PEMParser, but parser.readObject() below returns null.
String pkcs7Pem = "-----BEGIN PKCS7-----\nMIIR...WTEA\n-----END PKCS7-----\n";
PEMParser parser = new PEMParser(new StringReader(pkcs7Pem));
parser.readObject();
Any help would be appreciated!
openssl pkcs7 -in client-certificate_pkcs7.pem -out client-certificate_chain.pem
That command only copies its input (if it is PKCS7 PEM) to its output without any change. That's not useful, nor do you need BouncyCastle to do the same in Java. I suspect you actually meant
openssl pkcs7 -print_certs -in (pkcs7) -out (chain)
which extracts the individual certificate(s), as a sequence of separate (PEM) objects.
I’ve found that CMSSignedData is used for pkcs7 related operations in BouncyCastle
Only some -- there are lots of PKCS7/CMS formats and operations that can be done, and lots of different BC classes to do them. But the PKCS7/CMS format used to carry a certificate chain, often labelled p7b or p7c, is the SignedData type and is implemented by the BouncyCastle CMSSignedData class.
I also have tried using PEMParser, but parser.readObject() below returns null.
It shouldn't, if you give it valid PEM input not the mangled version you posted. For me if I give it a valid PEM PKCS7 containing a p7b/c cert chain I get a ContentInfo which can then be decoded as follows (I use file I/O for convenience, but any Java Reader and Writer will work the same):
static void SO70048115PKCS7Certs (String[] args) throws Exception {
PEMParser p = new PEMParser(new FileReader(args[0]));
CMSSignedData sd = new CMSSignedData( (ContentInfo)p.readObject() );
p.close();
JcaPEMWriter w = new JcaPEMWriter(new FileWriter(args[1]));
for( X509CertificateHolder ch : sd.getCertificates().getMatches(null) ){
// optionally put subject,issuer as 'comments' like OpenSSL does
w.writeObject( new PemObject("CERTIFICATE",ch.getEncoded()) );
}
w.close();
}
Note a p7b/c by convention does not contain any signature(s) (in PKCS7/CMS terms, SignerInfo(s)) so the code you posted -- which is designed to find the certificate(s) associated with the signature(s) -- is inappropriate and does not work. You need to simply use the certificate(s) and not expect any SignerInfo(s).

CMS sign - OpenSSL produced .zip.p7 extractable by zip tool but not the one by Java BouncyCastle

I have a .zip file that is CMS signed using BouncyCastle
CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey);
cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder()
.setProvider("BC")
.build()
).build(contentSigner, signingCertificate));
cmsGenerator.addCertificates(certs);
CMSSignedData cms = cmsGenerator.generate(data, true);
signedMessage = cms.getEncoded();
return signedMessage;
However, in this way, the zip entries of the generated bytes are not readily extractable by a normal zip tool (an extra conversion step is needed), whereas it could be extractable if OpenSSL is used:
openssl cms -sign -inkey key -signer key -nodetach -binary -in data.zip -out out.zip.p7 -outform DER
Here the out.zip.p7 could be unzipped by a zip tool, whereas in the BouncyCastle case, zip tool gives "70 extra bytes in the beginning or within zipfile. Bad zipfile offset (local header sig): 70" while unzipping.
What am I missing here when using BouncyCastle? The one produced by OpenSSL is desired, because no PKCS7 conversion to zip is needed before unzipping.
And by the way, I am using Apache's commons-compress to produce the zip file.
In order to achieve similar output, type of encoding should be specified as "DER".
Refer to Document https://www.bouncycastle.org/docs/pkixdocs1.4/org/bouncycastle/cms/CMSSignedData.html#getEncoded(java.lang.String).

java.security.InvalidKeyException: invalid key format while generating public, private key from PEM file

I have gone through many similar threads but no luck!!
I want to generate public and private keys using a PEM file. Following is the code I am using for the same:
String pemFileNme = "C:\\Users\\amitmm\\Desktop\\clean\\key.pem";
File pubKeyFile = new File(pemFileNme);
File privKeyFile = new File(pemFileNme);
// read public key DER file
DataInputStream dis = new DataInputStream(new
FileInputStream(pubKeyFile));
byte[] pubKeyBytes = new byte[(int)pubKeyFile.length()];
dis.readFully(pubKeyBytes);
dis.close();
// read private key DER file
dis = new DataInputStream(new FileInputStream(privKeyFile));
byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
dis.read(privKeyBytes);
dis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// decode public key
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubKeyBytes);
RSAPublicKey pubKey = (RSAPublicKey)
keyFactory.generatePublic(pubSpec);
// decode private key
PKCS8EncodedKeySpec privSpec = new
PKCS8EncodedKeySpec(privKeyBytes);
RSAPrivateKey privKey = (RSAPrivateKey)
keyFactory.generatePrivate(privSpec);
Exception:
Exception in thread "main" java.security.spec.InvalidKeySpecException:
java.security.InvalidKeyException: invalid key format
at
sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
at main.java.me.txedo.security.Main2.f1(Main2.java:47)
at main.java.me.txedo.security.Main2.main(Main2.java:20)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:387)
PEM File Content:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwnEEodFEf86+Ae+wYyI//u1kekIWnA3RfzbAwWD77uG7D9Ci
9vVNbPO4XT2hKL03/q7d7KTgrA1sjBltfaOzVfA56x1S/0cYVk4xI440dpLo0F+m
RIqRw5fh8IuUlUIr3I4A7ESkDQQsZbDpdgCiNbrlADqLotcZyB4rU4uURW8QUI/W
eqsD6TOQs4bI+3o3xAKkky2kXujSaaa3tDxgUPTmSQ0Buk7Hx/IVzwyV7qjWiR4U
C46rHpnWxfF0DWuJUOYgJmBQ8xFOQwt4Ec/u+0m8top8cqQF+gpBn9iLXpbtahA3
pqyvLuNXRH9yn8mlEneBrjjl6U0H3W/AV7/dGwIBAwKCAQEAgaCtwTYtqonUAUp1
l2wqqfOYUYFkaAk2VM8rK5X9SevSCosXT04znffQPikWGyjP/x8+ncNAcrOdsrue
U8J3jqAmnL43VNoQOYl2F7Qi+bdF4D/ELbG2gmVBSwe4Y4FykwlV8thtXgLIQ8tG
TqsWznyYqtGybI9mhWlyN7Ji2POMDZP5Lwx7M01pMezwpnsZSmPVL9TgVrtWv4xt
C0vPyuy9THlFWtkOdHItNK+vOTcpuHn29rFUJI/D3R+SQjcdqj3aaqljOtdeBxgd
yDl2/Z4rUyetgzcZMfNTt/NRT0hOJ6R6/2S7gFCTtxMHBh3vVCH+pLLnQyJvcPQu
AsORSwKBgQDhOPr1x/8BioqaasoXvO9NsGktCgPDjbC4d3jR8n6lCa42X/eIahaD
xi1VGWyQhdO7aMXiDmzOtox7xHcMRh+a5ySIs9gTsHkMB2hqwIUNg25INRkQ3Vr3
eWnoTBGsfJqC1TEME3ocKwmyz57ZAe4yyR/ZRdDX5DUt9qCCFeA8uQKBgQDdAzbq
7BlJkbTYfdlIRNJEJAO3wWqQTx8X0ttCMMwDluOT9l+RR/KuUxl85ph+kwJci6E/
ixfeMTW1NcsMY/lB6mTP0oooalU1MP7gpPSu+24zhLXnUHZotbNbv9nk6w/1WWhz
FBt5w2DG4kQPFK6LSySqcVuzIGQyvWD5PbpGcwKBgQCWJfyj2qoBBwcRnIa6ffTe
dZtzXAKCXnXQT6XhTFRuBnQkP/pa8WRX2XOOEPMLA+J88IPsCZ3fJF2n2E9dhBUR
722wd+VidaYIBPBHKwNeV57azhC16OdPpkaa3WEdqGcB43YIDPwSx1vMimnmAUl3
ML/mLos6mCNz+cBWuUAoewKBgQCTV3nx8ruGYSM6/pDa2IwtbVfP1kcK32oP4eeB
dd1Xue0NTupg2qHJjLuombr/DKw9smt/sg/pdiPOI9yy7VDWnEM1NwbFnDjOIKnr
GKMfUkl3rc6aNaRFzneSf+aYnLVOO5r3Yrz715XZ7C1fYx8Hh23G9j0iFZgh05X7
fnwu9wKBgHyC0X26KZQ0ukan5jDSiz4dapUp2d3F+vnRzZa2AOsmo995gsXLdfsJ
n0o4Z3LsQJUDRI3tQ4dXe/5jS4oFrOdxALOAw6YmvEv/3oHwsCYPDhqLNfIJ9I6m
Dt3yG61pUJiCArhPaYG17NQoCxF6Xi6GUajRsECbr8DdyGMAu5eE
-----END RSA PRIVATE KEY-----
I have tried removing file header and footer manually. I tried code from bouncycastle, no luck, same error.
Python code which works with this file:
def t2e_enc(plaintext, pk_pem_file = './2017-12-04T062008Z.pem'):
'''
Function for encryption of Track2 credit card data.
This function uses private key to derivate public part used for encryption
'''
with open(pk_pem_file, 'rb') as pk:
private_key = serialization.load_pem_private_key(pk.read(),
password=None, backend=default_backend())
public_key = serialization.load_pem_public_key(
private_key.public_key().public_bytes(
serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo),
backend=default_backend()
)
ciphertext = public_key.encrypt(
plaintext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
b64ciphertext=base64.b64encode(ciphertext)
return b64ciphertext
I am doing this for the first time, so bear with me if there is any silly mistake.
Partial dupe Load RSA public key from file
So, you 'wrote' (I assume, copied) code that clearly says you need two files, in DER form, containing PKCS8 and 'X509' encodings. (What Java calls X.509 here is really the SubjectPublicKeyInfo component of X.509.) You give it one file, in PEM form not DER, containing a PKCS1 encoding not PKCS8 or X509 -- and you're surprised it doesn't work? Python works because it calls OpenSSL, and OpenSSL supports over a dozen encodings and formats for privatekeys, including this one; Java supports only one (outside of keystores) which isn't this one. (Bare) publickeys are a little better; internally libcrypto supports multiple forms, but in practice only two of them are used, and one of them matches Java -- although many publickeys are distributed, stored, and used in the form of X.509 certificates, which provides several more forms to worry about.
There are approximately 7 solutions to your situation:
the simplest is to use OpenSSL commandline to convert your one file to the two files Java wants:
# (corrected! pkey is inconsistent!)
openssl pkcs8 -topk8 -nocrypt -in input.pem -outform der -out private.der
openssl pkey -in input.pem -pubout -outform der -out public.der
# or for very old versions (should not be needed now)
openssl rsa -in input.pem -pubout -outform der -out public.der
Those files can now be read by the code you posted (except with the filenames separated). Note this conversion doesn't have to be done on the same system; if necessary you can do it elsewhere and copy the files, if you use a method that works for binary files i.e. NOT cut&paste.
if you really want only one file, but it can be converted, create the private.der file as above and read it with only the privatekey-related parts of your code, then do:
RSAPrivateCrtKey priv2 = (RSAPrivateCrtKey)privKey;
PublicKey pubkey = keyFactory.generatePublic(new RSAPublicKeySpec(priv2.getModulus(), priv2.getPublicExponent()));
you could convert the files to PKCS8 and 'X509' PEM by omitting -outform der from the above conversions, then read those files and manually 'de-PEM' by removing the header and trailer lines and converting the base64 to binary (removing or skipping the linebreaks); this results in binary PKCS8 and X509 encodings you can run through your existing code. This is as much work on the openssl side and more work on the Java side so there is no apparent advantage, except that PEM files are valid text and can be cut&pasted if necessary.
combining these, you could convert to PKCS8 PEM only, read that per bullet 3 (de-PEM then the privatekey parts of your code), then extract publickey from privatekey per bullet 2
one way to use the format you have (unconverted) in plain Java is to de-PEM per bullet 3 giving you a PKCS1 encoding, then manually construct the PKCS8 encoding, then proceed as before to run the PKCS8 through the KeyFactory and extract publickey per bullet 2. See my answer at Java: Convert DKIM private key from RSA to DER for JavaMail for a really ugly way to do this (including one de-PEM method). There is a better way if you use BouncyCastle (which has a class for this ASN.1 type), but if you use BouncyCastle it's better not to use this method at all, see below.
another way to use the unconverted format in plain Java is to de-PEM per bullet 3, then parse the ASN.1 structure of PKCS1 and construct an RSAPrivateCrtKeySpec which you can run through your KeyFactory instead of a PKCS8 encoding then extract publickey per bullet 2. This is even more complicated, although I think I have seen it somewhere; will add if I find it. Again BouncyCastle can improve this method, but doesn't need to, see below.
finally, if you have BouncyCastle it's dead easy. You don't say what you tried with 'no luck', but the following BouncyCastle code is all you need and does work:
try( Reader r = new FileReader(filename) ){
KeyPair pair = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)new PEMParser(r).readObject());
}
Note this gives you a KeyPair with both privatekey and publickey objects from the one file.

Java - Get Key failed: java.security.InvalidKeyException: Invalid RSA private key and DerInputStream.getLength(): Redundant length bytes found

I am having some trouble with XML digital signature with a PFX file. I get an exception when I run this code:
KeyStore ks = KeyStore.getInstance("PKCS12");
fs = new FileInputStream(file);
ks.load(fs, "password".toCharArray());
// this line!
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("alias", new KeyStore.PasswordProtection("password".toCharArray()));
This is the exception:
java.security.UnrecoverableKeyException: Get Key failed:
java.security.InvalidKeyException: Invalid RSA private key
at sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:435)
at sun.security.pkcs12.PKCS12KeyStore.engineGetEntry(PKCS12KeyStore.java:1306)
at java.security.KeyStore.getEntry(KeyStore.java:1521)
at app.ubl.xml.GenerateSignature.makeSignatureXML(GenerateSignature.java:88)
Caused by: java.io.IOException: DerInputStream.getLength(): Redundant length bytes found
at sun.security.util.DerInputStream.getLength(DerInputStream.java:606)
at sun.security.util.DerInputStream.getLength(DerInputStream.java:569)
at sun.security.util.DerInputStream.getPositiveBigInteger(DerInputStream.java:220)
at sun.security.rsa.RSAPrivateCrtKeyImpl.parseKeyBits(RSAPrivateCrtKeyImpl.java:205)
The real problem is that the code works in java 1.8.0_111, but any higher version the error shows.
Exception when running keytool
Another issue that I found is that when I run this command using keytool:
keytool -list -keystore file.pfx -storepass password -storetype PKCS12 -v
to show the details of the PFX file then again it only works on java 1.8.0_111. Otherwise I get this exception:
java.util.IllegalFormatConversionException: d != java.lang.String
at java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4302)
at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2793)
at java.util.Formatter$FormatSpecifier.print(Formatter.java:2747)
at java.util.Formatter.format(Formatter.java:2520)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)
at sun.security.tools.keytool.Main.withWeak(Main.java:3076)
at sun.security.tools.keytool.Main.printX509Cert(Main.java:3125)
at sun.security.tools.keytool.Main.doPrintEntry(Main.java:1924)
at sun.security.tools.keytool.Main.doPrintEntries(Main.java:2236)
at sun.security.tools.keytool.Main.doCommands(Main.java:1123)
at sun.security.tools.keytool.Main.run(Main.java:366)
at sun.security.tools.keytool.Main.main(Main.java:359)
I don't know if this is relevant, but that is all I got.
Any thoughts?
PD: Sorry for my bad english.
Stricter DER checking in 8u121
This is a change that was introduced in the Java versions after Java 8u111. The next version was Java 8u121.
As a result some encodings in the underlying ASN.1 format are no longer being accepted.
And this change is mentioned in the 8u121 release notes as such:
security-libs
More checks added to DER encoding parsing code
More checks are added to the DER encoding parsing code to catch various encoding errors. In addition, signatures which contain constructed indefinite length encoding will now lead to IOException during parsing. Note that signatures generated using JDK default providers are not affected by this change.
JDK-8168714 (not public)
Reason: CVE
This specific change was because of a vulnerability: CVE-2016-5546.
Relevant source code change
The error messages you saw (Redundant length bytes found) was introduced as part this changeset. The summary was this:
8168714: Tighten ECDSA validation
Summary: Added additional checks to DER parsing code
Unfortunately as of now (2018) the bug mentioned in the changeset "Bug 8168714" is STILL marked private. (You won't be able to view it without a login.)
But here's another bug that is public: OpenJDK: JDK-8175251:
Failed to load RSA private key from pkcs12. (A less complete version of this bug is also on java.com.)
And this bug links to the source code changeset mentioned above.
Workaround: Run through OpenSSL washing machine to clean up P12 file
The encoding in some P12 files seems to be broken somehow. A workaround is to unwrap the P12's contents to PEM with OpenSSL and then rewrap them into a new P12 file. (The real solution would be to fix/update the software that generates the P12 file.)
The following command is mentioned in the Java Bug ticket:
Openssl is able to remove the redundant 0s when extracting the private key. We can use the following 2 commands to normalize an affected pkcs12 file:
openssl pkcs12 -in pkcs12-file -out key-and-cert -nodes -passin pass:abcXYZ
openssl pkcs12 -in key-and-cert -export -out new-pkcs12-file -passout pass:abcXYZ
Further reading: Bouncy Castle Mailing List
Update 2018-02-12Mon: I just realized that the issue discussed on the Bouncy Castle mailing list deals with a DIFFERENT type of encoding error. Namely: in ASN.1 you have T-L-V (Tag-Length-Value) encoding.
And OP's question had to do with a non-standard/faulty encoding of the LENGTH attribute. (Error message: Redundant length bytes found.)
While the issue discussed on the BC-ML had to do with a non-standard/faulty encoding of the VALUE attribute. (Error message: Invalid encoding: redundant leading 0s.)
But both of these issues are related to unnecessarily (and therefore illegally) padding with 0x00.
This change was discussed on the mailing list for the Java "Bouncy Castle" cryptography library:
dev-crypto#bouncycastle.org mailing list, 2017-06-27, Strict Public Key checking leads to broken certificates (Archived here. Official archive here.)
And as a result a special configuration parameter (org.bouncycastle.asn1.allow_unsafe_integer) was introduced in BC version 1.58.
(See release notes section 2.2.3.)
Related questions
2017-06-27, SO: Unable to open keystore in AndroidStudio - "Redundant length bytes found"
2017-03-17, SO: Signing android app throws IOException: Redundant length bytes found
To work-around this issue, you can re-encode the signature bytes using the ASN1 stuff from Bouncycastle.
BigInteger r, s;
// This works because bouncycastle isn't picky like the updated JCE is.
try(ASN1InputStream is = new ASN1InputStream(origSigBytes)) {
ASN1Sequence seq = (ASN1Sequence)is.readObject();
ASN1Integer r_int = (ASN1Integer)seq.getObjectAt(0);
ASN1Integer s_int = (ASN1Integer)seq.getObjectAt(1);
r = r_int.getValue();
s = s_int.getValue();
}
DERSequence out = new DERSequence(new ASN1Encodable[] {new DERInteger(r), new DERInteger(s)});
byte[] sigBytes = out.getEncoded();
This works if you're in a context where you have the signature as a byte[] and are about to call validate().
add BouncyCastleProvider provider to java.security.Security
Security.addProvider(new BouncyCastleProvider());
and it will work

get X509Certificate serial number

I need to get serial number of x509 certificate. The result of usage "certificate.getSerialNumber()" differs from the expected. As I see X509 certificate file specs, it should go in following format:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version shall be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version shall be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL
-- If present, version shall be v3
}
And I couldn't find in the begining of the file the value that is provided by certificate.getSerialNumber() method.
And related question: When trying to display the serial with openssl it takes right value from file but adds '3' after each number.
So my question is: How can I get the stored serial value? And where to read why and how openssl and java modifies this data.
OPENSSL
Run with:
openssl x509 -serial -noout -inform DER -in mycert.cer
Result:
serial=3030303031303030303030313030373439323639
JAVA
Code:
InputStream in = new FileInputStream("mycert.cer");
BouncyCastleProvider provider = new BouncyCastleProvider();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509", provider);
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(in);
BigInteger serialNum = certificate.getSerialNumber();
System.out.println(serialNum);
Output:
275106190557734483187066766755592068430195471929
FILE
And viewing the file, I see:
0...0..r.......000010000001007492690
. *.H..
..
which seems to be the serial, provided by openssl but openssl mix it with '3'(after each number).
I had the same problem with ruby and found the answer here in java X509 serial number using java
For those who wants the solution in ruby
serial = 275106190557734483187066766755592068430195471929
serial.to_s(16)
this will output 3030303031303030303030313030373439323639
Java doesn't modify this data. I'd be amazed if openssl did either. Presumably your expectations are incorrect.

Categories

Resources