I have a key created with OpenSSL from a previous app with the commands:
openssl req -nodes -newkey rsa:2048 -keyout root.key \
-out root.csr -config ./openssl.cnf
I changed it to a PKCS8 key since I need to use that key in Java with:
openssl pkcs8 -topk8 -nocrypt -in pkcs1_key_file -out pkcs8_key.pem
As far as I can tell, this works since I'm able to create a SSLContext with it. I'm having trouble recreating a KeyPair object in order to perform other operations with it. I've tried:
Path privateKeyPath = Paths.get("root.key.pem");
File privateKeyFile = new File( System.getProperty("user.dir") + File.separator + "ue.key.pem");
byte[] bytes = Files.readAllBytes(privateKeyPath);
PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(bytes);
BufferedReader br = new BufferedReader(new FileReader(privateKeyPath.toFile()));
PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile));
PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject(); // ?????
I've seen other code like Read an encrypted private key with bouncycastle/spongycastle, where they do pemParser.readObject, the object is of type PEMEncryptedKeyPair, or they use the converter to getKeyPair(), but when I call readObject, my object is of type PrivateKeyInfo so I cannot call getKeyPair either.
Is there a step somewhere I'm missing in either the changing to PKCS8 key with the OpenSSL command, or in trying to reconstruct the KeyPair?
Related
I generated a ca-certificate and a key with openssl.
openssl genrsa -aes256 -out $CANAME.key 4096
openssl req -x509 -new -nodes -key $CANAME.key -sha256 -days 1826 -out $CANAME.crt -subj '/CN=MyOrg Root CA/C=AT/ST=Vienna/L=Vienna/O=MyOrg'
Now I want to sign CSRs with those.
I found this question, but I can't use the accepted answer, because the class PKCS10CertificationRequestHolder seems not to exist anymore.
Based on the seconds answer I created this service.
public class SignService {
private PrivateKey privateKey;
private X509CertificateHolder certificateHolder;
public SignService(
String caPathKey,
String caCert,
String caKeyPassword
) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
loadPrivateKey(caPathKey, caKeyPassword);
PEMParser pemParser = new PEMParser(new FileReader(caCert));
this.certificateHolder = (X509CertificateHolder) pemParser.readObject();
}
private void loadPrivateKey(String caPathKey, String caKeyPassword) throws IOException {
PEMParser pemParser = new PEMParser(new FileReader(caPathKey));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
if (object instanceof PEMEncryptedKeyPair)
{
// Encrypted key - we will use provided password
PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) object;
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(caKeyPassword.toCharArray());
kp = converter.getKeyPair(ckp.decryptKeyPair(decProv));
}
else
{
// Unencrypted key - no password needed
PEMKeyPair ukp = (PEMKeyPair) object;
kp = converter.getKeyPair(ukp);
}
this.privateKey = kp.getPrivate();
}
public String signCRT(String crs_str) throws NoSuchProviderException, IOException, KeyStoreException, NoSuchAlgorithmException, OperatorCreationException, CMSException {
PemReader p = new PemReader(new StringReader(crs_str));
PemObject pemObject = p.readPemObject();
PKCS10CertificationRequest csr = new PKCS10CertificationRequest(pemObject.getContent());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
X500Name issuer = certificateHolder.getIssuer();
BigInteger serial = new BigInteger(32, new SecureRandom());
Date from = new Date();
Date to = new Date(System.currentTimeMillis() + (365 * 86400000L));
X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo().getEncoded()));
certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(this.certificateHolder.getSubject())), certificateHolder.getSerialNumber()));
ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(this.privateKey.getEncoded()));
X509CertificateHolder holder = certgen.build(signer);
byte[] certencoded = holder.toASN1Structure().getEncoded();
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
signer = new JcaContentSignerBuilder("SHA1withRSA").build(this.privateKey);
generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, this.certificateHolder));
generator.addCertificate(new X509CertificateHolder(certencoded));
generator.addCertificate(new X509CertificateHolder(this.certificateHolder.getEncoded()));
CMSTypedData content = new CMSProcessableByteArray(certencoded);
CMSSignedData signeddata = generator.generate(content, true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write("-----BEGIN PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
byte[] cert_encoded = Base64.encode(signeddata.getEncoded());
for (int i = 0; i < cert_encoded.length; i++) {
if (i > 0 && i % 63 == 0) {
out.write('\n');
}
out.write(cert_encoded[i]);
}
out.write("\n-----END PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
out.close();
return new String(out.toByteArray(), "ISO-8859-1");
}
}
The method signCrt does not throw any exceptions and returns a string (I am not sure what).
I can't verify it or even show any information about the certificate.
This openssl command works with the same csr file, I want to do the same thing from java.
openssl x509 -req -in test.csr -CA $CANAME.crt -CAkey $CANAME.key -CAcreateserial -out openssl-signed.crt -days 730 -sha256
The certificate created with openssl is much smaller the of my java method (Don't know of this is a useful information).
The answer you linked tells you what the code returns: "a valid PEM-encoded signedData object containing a signed Certificate chain (of the type that can be imported by keytool)". Actually it's not quite valid: (1) it doesn't put any linebreaks in the base64 as PEM officially requires -- which you fixed, although you did it at 63 not 64 as standard; (2) it uses BEGIN/END PKCS #7 SIGNED DATA whereas the standard is BEGIN/END PKCS7 -- or in most cases BEGIN/END CMS because CMS is basically a superset of PKCS7. keytool ignores both of these flaws when reading a 'CA reply' (i.e. -importcert to an existing privateKey entry), but other software doesn't -- like BouncyCastle and OpenSSL. You already added line breaks; if you change the BEGIN/END labels to PKCS7 and put the result in a file you can read it with among other things
openssl pkcs7 -in $file -print_certs # add -text for expanded details
However, calling this 'the type that can be imported by keytool' implies it is the only type which is false. keytool can read PKCS7 containing certs -- either PEM or DER -- but it can also read 'bare' X.509 certificates in either PEM or DER, and that is usually more convenient. To do that, replace everything from CMSSignedDataGenerator onward with something like for DER:
Files.write(Paths.get("outputfile"), certencoded)
or for PEM since you have Bouncy:
try( PemWriter w = new PemWriter(new FileWriter("outputfile")) ){
w.writeObject(new PemObject("CERTIFICATE",certencoded));
}
or if you prefer to do it yourself:
try( Writer w = new FileWriter("outputfile") ){
w.write("-----BEGIN CERTIFICATE-----\r\n"
+ Base64.getMimeEncoder().encodeToString(certencoded)
// MimeEncoder does linebreaks at 76 by default which
// is close enough and less work than doing it by hand
+ "\r\n-----END CERTIFICATE-----\r\n");
}
Note: you may not need the \r -- PEM doesn't actually specify CRLF vs LF linebreaks -- but MimeEncoder uses them on the interior breaks and I like to be consistent.
This -- just the certificate -- is what your openssl x509 -req -CA* alternative produces, and that's why it's much smaller -- one cert is smaller than three certs plus some overhead.
Several more points:
PEMParser can handle CSR just fine; you don't need the PemReader+PemObject detour
using the entire SubjectPublicKeyInfo for the SubjectKeyIdentifier extension is wrong. OTOH nothing uses the SKI extension in a leaf cert anyway, so simplest to just delete this rather than fix it.
in certgen.build() if you use JcaContentSignerBuilder instead of the BcRSA variant, you can pass cakey directly without going through PrivateKeyFactory and the signature scheme directly without going through two AlgorithmIdentifierFinders -- as the /*cms-sd*/generator.addSignerInfoGenerator later in the code already does.
speaking of the signature scheme, don't use SHA1 for cert signatures (or most others). It was already deprecated in 2013 when that answer was written, and in 2017 was publicly broken for collision (see https://shattered.io) after which many systems either reject it entirely or nag mercilessly for such use, and some related ones like git. In fact many authorities and checklisters now prohibit it for any use, even though some (like HMAC and PBKDF) aren't actually broken. Your openssl x509 -req alternative used SHA256 which is fine*.
and lastly, issuing a cert is NOT 'sign[ing] {the|a} CSR'. You can just look at the cert contents and see it's not at all the same as the CSR or even the CSR body. You create (or generate as used in the Bouncy names) a cert and sign the cert -- in response to a CSR.
* at least for now; if and when quantum cryptanalysis works a lot of currently used schemes, including probably SHA256-RSA signature, will be in trouble and will have to be replaced by 'post-quantum' schemes which people are now working to develop. But if this happens, you'll see it on every news channel and site in existence.
I generate a private key with openssl, create a csr, send the csr to a server with the CA private key, sign it and generate an X509 which gets sent back to the client. The client puts the X509 into a cert store. The client signs some dummy data and tries to verify it with the public key in the cert. Verification fails. However when I generate a public key directly from the private key, verification succeeds with that public key.
Private key generation on the client
openssl genrsa -out ${keydir}/sign.key 2048 2>/dev/null
CSR
openssl req -new -sha256 -batch -key /tmp/sign.key -subj '/O=TableSafe/OU=secureroom/CN=sign.autoprov.tablesafe.com' -out /tmp/sign.csr
Send the CSR to the CA signer (see the java code below for how it handles the CSR bytes).
Then test with the following commands.
echo abcdefghijklmnopqrstuvwxyz > myfile.txt #generate some data to sign
openssl dgst -sha256 -sign sign.key -out sha256.sign myfile.txt #sign the data with the private key
openssl x509 -pubkey -noout -in Sign.crt > pubkey.pem #extract the public key from the certificate
openssl dgst -sha256 -verify pubkey.pem -signature sha256.sign myfile.txt #verify
Verification Failure
openssl rsa -in sign.key -pubout -out pubkey.pem #generate public key from private key directly
openssl dgst -sha256 -verify pubkey.pem -signature sha256.sign myfile.txt
Verified OK
It looks like my java code is suspect. Help appreciated.
public byte[] calculateX509(byte[] csrBytes) throws Exception {
Clock clock = Clock.systemUTC();
Date notBefore = Date.from(clock.instant());
Duration expDuration = Duration.ofDays(3650);
Date notAfter = Date.from(clock.instant().plus(expDuration));
String csrStr = new String(csrBytes);
csrStr = csrStr.replace("-----BEGIN CERTIFICATE REQUEST-----", "");
csrStr = csrStr.replace("-----END CERTIFICATE REQUEST-----", "");
csrStr = csrStr.replaceAll("\n", "");
byte[] csrDecode = Base64.getDecoder().decode(csrStr.trim());
PKCS10CertificationRequest decodedCsr = new PKCS10CertificationRequest(csrDecode);
X500Name issuer = X500Name.getInstance(decodedCsr.getSubject().getEncoded());
X509v3CertificateBuilder caBuilder = new X509v3CertificateBuilder(issuer,
BigInteger.valueOf(clock.millis()),
notBefore,
notAfter,
decodedCsr.getSubject(),
decodedCsr.getSubjectPublicKeyInfo())
.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));
if (Lunaks.containsAlias(strDAUTOPROVK))
{
char[] pw = {};
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("Sha256withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
AsymmetricKeyParameter foo = PrivateKeyFactory.createKey(Lunaks.getKey(strDAUTOPROVK, pw).getEncoded());
ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(foo);
JcaX509CertificateConverter providerConverter = certificateConverter.setProvider(new BouncyCastleProvider());
X509CertificateHolder holder = caBuilder.build(sigGen);
X509Certificate cert = providerConverter.getCertificate(holder);
byte[] encoded = cert.getEncoded();
StringWriter writer = new StringWriter();
PemWriter pemWriter = new PemWriter(writer);
PemObject pemObject = new PemObject("CERTIFICATE", encoded);
pemWriter.writeObject(pemObject);
pemWriter.close();
String strBytes = writer.toString();
return strBytes.getBytes();
}
return null;
}
To convert a pem file containing a x509 certificate + private key into a pkcs12 (.p12) file, the following command is being used:
openssl pkcs12 -export -inkey cert_pkey.pem -in cert_pkey.pem -out cert.p12
I am trying to accomplish the same programatically using Java with BouncyCastle library. I am able to extract the X509Cert from the PEMObject but the Private key has been confusing.
Any help in piecing together the steps is appreciated:
Open cert_pkey.pem file stream using PEMParser
Get the X509 Certificate from PemObject (done)
Get the private key from the PemObject (how?)
Create KeyStore of instance type PKCS12 with password
Finally got around how to get the cert and key separately - not sure why it worked out the way it worked out:
PEMParser pemParser = new PEMParser(new BufferedReader(new InputStreamReader(certStream)));
Object pemCertObj = pemParser.readObject();
PemObject pemKeyObj = pemParser.readPemObject();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pemKeyObj.getContent());
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privKey = kf.generatePrivate(privKeySpec);
Security.addProvider(new BouncyCastleProvider());
X509CertificateHolder certHolder = (X509CertificateHolder)pemCertObj;
X509Certificate x509cert = (new JcaX509CertificateConverter()).setProvider("BC").getCertificate(certHolder);
I got the hint when I looked up the .getType() on permCertObj and permKeyObj and got RSA CERT and RSA PRIVATE KEY respectively returned.
Couldn't figure out the difference between readObject() and readPemObject()
The PEMParser class will parse just about anything from PEM format. You can read the object from the file using that parser - if you'll print the class of that object you'l;l see it's a PEMKeyPair. That can be converted to a regular KeyPair using JcaPEMKeyConverter.
public KeyPair importKeyFromPemFile(String filePath)
{
try (FileReader reader = new FileReader(filePath))
{
PEMParser pemParser = new PEMParser(reader);
PEMKeyPair pemKeyPair = (PEMKeyPair)pemParser.readObject()
return new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
}
catch (IOException | PEMException e)
{
throw new RuntimeException(e)
}
}
I am following the code in: https://stackoverflow.com/a/18161536/1753951 but I am getting an Exception in the following line:
FileInputStream fis = new FileInputStream(priv);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int)priv.length()];
dis.readFully(keyBytes);
dis.close();
javax.crypto.EncryptedPrivateKeyInfo encryptPKInfo = new EncryptedPrivateKeyInfo(keyBytes);
//Exception:
org.apache.harmony.security.asn1.ASN1Exception: Wrong content length
I am trying to read a .key/.pem PKCS8 file which is:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK Info: AES-256-CBC,8AFF348907C84F2F6370A216DC0D55D9
1VIjJD3dZ5/wYnIm0mtp8d22RC24yGcY9LXgeHUDbyPJQa8PjupubFqKrpOodvQx
dPfE1F3XeY8oVG42ZfR4287X4V16n++BQCeDiuvyrwacLMAuQz6PFLT4b/Py89Cm
761UZpaWnH0PHfJqB9CHqC+pGAGfRF5vj7UtdNchCwBmo+7gvU5iGyYXNRJ/hPnU
V+8QDzro4kFIMOlDzHaJ3KN1Ftbb9LDjDNE/NShbRrAFAWJMZSY/ZjF8mfqggkoZ
%%%%% SKIPPED MOST OF IT %%%%%%%%%%
BMIl0y5XVgPwkApA30EdgV4YAZEJ+wQLnYIZfCklqzvCfyjxHFViVW6d41WNm8bx
wl28v4QJKlnf7KNcmmGwSmjKo7BEASSZ+XVYRu0R6FaE+Job5YzPrtUI+p/kf7et
Y+jUDbZ4BPvB8j2ZscNRs+pJkEXxPt5JKW/oQMQZPlbTtSV5K1IqiuVcRi9TbCzk
nWDSfI/wxt6cK3X9XvyOpOZDCDPchkIhDhCzfitd7fzkM1VBekwsliJwjgc1bwbc
nI4AhQcNb8li7oX1M2osyeR3zF25BDb2A04Zm1lMrWkFrypb24DKkSJxYEH33Gpu
-----END RSA PRIVATE KEY-----
After looking long time for a solution I stumbled with a library that helps me and works on android.
Not-Yet-Commons
http://juliusdavies.ca/commons-ssl/
FileInputStream in = new FileInputStream( "/path/to/pkcs8_private_key.der" );
PKCS8Key pkcs8 = new PKCS8Key( in, "changeit".toCharArray() );
byte[] decrypted = pkcs8.getDecryptedBytes();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec( decrypted );
// A Java PrivateKey object is born.
PrivateKey pk = null;
if ( pkcs8.isDSA() )
{
pk = KeyFactory.getInstance( "DSA" ).generatePrivate( spec );
}
else if ( pkcs8.isRSA() )
{
pk = KeyFactory.getInstance( "RSA" ).generatePrivate( spec );
}
// For lazier types (like me):
pk = pkcs8.getPrivateKey();
javax.crypto.EncryptedPrivateKeyInfo expects a DER-encoded input, while the contents of your file are obviously in the PEM encoding.
The relation between PEM and DER is this:
The DER is the actual ASN.1-encoded data as a sequence of bytes.
The PEM is text-based with all these -----BEGIN SOMETHING----- and -----END SOMETHING----- headers and Base64-encoded data inside. Basically, PEM is header+Base64(DER)+footer.
You need to convert your key into DER format, for example using the OpenSSL pkey command:
openssl pkey -in key.pem -outform DER -out key.der
When trying to read a RSA private key from a file using the method
public PrivateKey getPrivateKey()
throws NoSuchAlgorithmException,
InvalidKeySpecException, IOException {
final InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream("privatekey");
byte[] privKeyBytes = null;
try {
privKeyBytes = IOUtils.toByteArray(inputStream);
} catch (final IOException exception) {
LOGGER.error("", exception);
IOUtils.closeQuietly(inputStream);
}
LOGGER.debug("privKeyBytes: {}", privKeyBytes);
String BEGIN = "-----BEGIN RSA PRIVATE KEY-----";
String END = "-----END RSA PRIVATE KEY-----";
String str = new String(privKeyBytes);
if (str.contains(BEGIN) && str.contains(END)) {
str = str.substring(BEGIN.length(), str.lastIndexOf(END));
}
KeyFactory fac = KeyFactory.getInstance("RSA");
EncodedKeySpec privKeySpec =
new PKCS8EncodedKeySpec(Base64.decode(str.getBytes()));
return fac.generatePrivate(privKeySpec);
}
I get the exception
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:200) ~[na:1.6.0_23]
at java.security.KeyFactory.generatePrivate(KeyFactory.java:342) ~[na:1.6.0_23]
at the fac.generatePrivate(privKeySpec) call.
What does this error mean?
Thanks
Dmitri
I was having this same issue, and the format of the key was NOT the actual problem.
All I had to do to get rid of that exception was to call
java.security.Security.addProvider(
new org.bouncycastle.jce.provider.BouncyCastleProvider()
);
and everything worked
It means your key is not in PKCS#8 format. The easiest thing to do is to use the openssl pkcs8 -topk8 <...other options...> command to convert the key once. Alternatively you can use the PEMReader class of the Bouncycastle lightweight API.
You must make your PCKS8 file from your private key!
private.pem => name of private key file
openssl genrsa -out private.pem 1024
public_key.pem => name of public key file
openssl rsa -in private.pem -pubout -outform PEM -out public_key.pem
private_key.pem => name of private key with PCKS8 format! you can just read this format in java
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt