I have a big and strange problem with PKCS7 securities files.
I create a p7s file like what is suggested in http://www.thatsjava.com/java-tech/85019/ where it creatse the file and validates it using only sun libraries. It works fine.
The problem starts when I want to validate this file. It returns this exception:
java.security.SignatureException: Signature encoding error
at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:185)
at java.security.Signature$Delegate.engineVerify(Signature.java:1140)
at java.security.Signature.verify(Signature.java:592)
at sun.security.pkcs.SignerInfo.verify(SignerInfo.java:374)
at sun.security.pkcs.PKCS7.verify(PKCS7.java:494)
at sun.security.pkcs.PKCS7.verify(PKCS7.java:511)
at sun.security.pkcs.PKCS7.verify(PKCS7.java:533)
at firma.FirmaDigitalImpl.firmarCadenaSun(FirmaDigitalImpl.java:553)
at firma.FirmaDigitalImpl.firmarCadena(FirmaDigitalImpl.java:249)
at firma.FirmaDigitalImpl.firmarCadena(FirmaDigitalImpl.java:147)
at firma.TestFirma.main(TestFirma.java:75)
Caused by: java.io.IOException: Sequence tag error
at sun.security.util.DerInputStream.getSequence(DerInputStream.java:280)
at sun.security.rsa.RSASignature.decodeSignature(RSASignature.java:209)
at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:174)
... 10 more
But the problems don't appear always, just only with a type of certificates used to sign. I will explain better.
I have two certificates (stored into smartcard) and the first works fine; I create the p7s and next I validate correctly, but the second certificate allows me to create the p7s, file but when I validate it, it returns the SignatureException. I thought the p7s file was wrong, but I tested it with others' applications, and it looks correct. Besides, this file is sent throws webservice and it returns that it is ok!
And for more information, if the p7s file is created with different tools (.net and capicom) and the same certificate, then I can validate correctly.
I have looked for the solution, but nothing. I have found the same exception in similar situations, but either the solution suggested doesn't work for me, or it doesn't appear.
Any advice to find the solution will be very appreciated.
It might be encoding problem: text vs. binary. PKCS7 can be in either DER-encoded binary format or PEM-format (which is base64 encoded DER). Open the files in a text editor (e.g. notepad) and see whether the one which works is binary or text.
After more than two weeks, I have noticed what was the problem, although I didn't find the full solution.
First, I extracted the problem out of the PKCS7 file, and I reproduced the exception with the following source code:
import diz.firma.keyStore.SmartCard;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
public class TestFirma3 {
public static void main(String args[]) throws Exception {
//Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
SmartCard sm = new SmartCard();
KeyStore ks = sm.loadKeyStore("1234");
//KeyPair keyPair = generateKeyPair(999);
byte[] data = "original".getBytes("UTF-8");
//byte[] data = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
//byte[] digitalSignature = signData(data, keyPair.getPrivate());
byte[] digitalSignature = signData(data,
(PrivateKey)ks.getKey(sm.getAlias(), null),ks.getProvider());
boolean verified;
//verified = verifySig(data, keyPair.getPublic(), digitalSignature);
verified = verifySig(data, ks.getCertificate(sm.getAlias()).
getPublicKey(),ks.getProvider(), digitalSignature);
System.out.println("verified:" + verified) ;
//keyPair = generateKeyPair(888);
//verified = verifySig(data, keyPair.getPublic(), digitalSignature);
//System.out.println(verified);
}
public static byte[] signData(byte[] data, PrivateKey key,Provider p) throws Exception {
Signature signer = Signature.getInstance("SHA1withRSA",p);
//Signature signer = Signature.getInstance("SHA1withRSA",Security.getProviders()[10]);
signer.initSign(key);
signer.update(data);
return (signer.sign());
}
public static boolean verifySig(byte[] data, PublicKey key, Provider p, byte[] sig) throws Exception {
Signature signer = Signature.getInstance("SHA1withRSA",p);
//Signature signer = Signature.getInstance("SHA1withRSA");
signer.initVerify(key);
signer.update(data);
boolean valido = false;
try{
valido = signer.verify(sig);
}catch(Exception e){
e.printStackTrace();
valido = false;
}
return valido;
}
public static KeyPair generateKeyPair(long seed) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("DSA");
SecureRandom rng = SecureRandom.getInstance("SHA1PRNG", "SUN");
rng.setSeed(seed);
keyGenerator.initialize(1024, rng);
return (keyGenerator.generateKeyPair());
}
}
In this code, (extracted from an example in the net), I changed the generators key and took it from my card. I got the exception again, without using PKCS7 files.
Looking at all providers (with Security.getProviders() you can get all of them; they are defined in java.security file, but it can be added to or taken from in runtime), and I used everyone to sign and validate, I found out that:
If no one provider is selected when Signature is instantiated, it uses SunRsaSign by default.
My Signature is validated with 2 providers:
a) With SunMSCAPI provider, which uses Microsoft Crypt API.
b) With the provider created by smart card reader, in my case Siemens.
Reading another smart card that does not belong to Siemens, it is validated with SunRsaSign and SunJSSE.
At this moment, I find out the problem and a workarournd using SunMSCAPI to validate, but I have to validate into an IBM machine. Siemens software provides an option to install into Unix, but I cannot get it to work. And using SunMSCAPI, I suppose it isn't available for IBM.
So, I have to find out a provider that validates correctly in IMB the Signature that SunRsaSign returns SignatureException.
Related
I am loading a public key in java using bouncy castle library but always getting error Invalid point encoding 0x45.
The public key is generated at client side using C# CNG APIs.
Java method 1:
public PublicKey loadPublicKey(String encodedPublicKey)
throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keybytes = java.util.Base64.getDecoder().decode(encodedPublicKey);
Security.addProvider(new BouncyCastleProvider());
ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
ECPublicKeySpec keySpec = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
return new BCECPublicKey("ECDH", keySpec, BouncyCastleProvider.CONFIGURATION);
}
Method 2
public PublicKey loadPublicKey(String pKey) throws Exception {
byte[] keybytes = java.util.Base64.getDecoder().decode(pKey);
Security.addProvider(new BouncyCastleProvider());
ECParameterSpec params = ECNamedCurveTable.getParameterSpec("P-256");
ECPublicKeySpec pubKey = new ECPublicKeySpec(params.getCurve().decodePoint(keybytes), params);
KeyFactory kf = KeyFactory.getInstance("ECDH", "BC");
return kf.generatePublic(pubKey);
}
Exception
java.lang.IllegalArgumentException: Invalid point encoding 0x45
at org.bouncycastle.math.ec.ECCurve.decodePoint(ECCurve.java:443)
Below method to create public key
public static (byte[] publicKey, byte[] privateKey) CreateKeyPair()
{
using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(
// need to do this to be able to export private key
CngKey.Create(
CngAlgorithm.ECDiffieHellmanP256,
null,
new CngKeyCreationParameters
{ ExportPolicy = CngExportPolicies.AllowPlaintextExport })))
{
cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
cng.HashAlgorithm = CngAlgorithm.Sha256;
// export both private and public keys and return
var pr = cng.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
var pub = cng.PublicKey.ToByteArray();
return (pub, pr);
}
}
Public Key generated RUNLMSAAAAHddHI6TOEDG/Ka7naBbLQH0u/DSFfbKJI2w0WSoxrmFkwKm1tktz4wD0rqnwkZp8FwdHJ+8OVrTcpDMmxrwvS6
The key which I am receiving at java is of 72 bytes. But I think bouncy castle java supports 64 bytes of key.
I was also looking into this but did not get any help
The C# code exports the public key as a Base64 encoded EccPublicBlob whose format is described in the link given in the question:
The first 4 bytes 0x45434B31 denote in little endian order a public ECDH key for curve P-256, the following 4 bytes are in little endian order the key length in bytes (0x20000000 = 32), the rest are the x and y coordinates of the EC point i.e. the public key, 32 bytes each.
It is striking that in the key you posted, the second 4 bytes are 0x20000001, but the x and y coordinates are 32 bytes each. Possibly there is a copy/paste error here. Anyway, with the posted C# code, I cannot reproduce a key that has a value other than 0x20000000 in the second 4 bytes.
Java/BC does not directly support importing an EccPublicBlob (which is MS proprietary), but it does support importing an uncompressed public key. This results when the x and y coordinates are concatenated and 0x04 is used as prefix. The import with Java/BC is then possible as follows:
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
...
public static PublicKey getPubKeyFromCurve(byte[] uncompRawPubKey, String curveName) throws Exception {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName);
ECNamedCurveSpec params = new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), uncompRawPubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pubKey;
}
Test (assuming EccPublicBlob is Base64 encoded like the posted one):
import java.util.Base64;
...
String publicKeyBlob = "RUNLMSAAAAAFzw4IGY4N8PKVt0MGF38SAKU5ixJhptVUdrWzuPhFDOcj/2k4SlGRN1RpRMbar9Iu7Uvcx7Vtm8Wa0HSzWJdE";
byte[] rawPublic = new byte[65];
rawPublic[0] = 0x04;
System.arraycopy(Base64.getDecoder().decode(publicKeyBlob), 8, rawPublic, 1, 64);
PublicKey pub = getPubKeyFromCurve(rawPublic, "P-256");
System.out.println(Base64.getEncoder().encodeToString(pub.getEncoded())); // MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBc8OCBmODfDylbdDBhd/EgClOYsSYabVVHa1s7j4RQznI/9pOEpRkTdUaUTG2q/SLu1L3Me1bZvFmtB0s1iXRA==
The test imports the EccPublicBlob and exports it as a Base64 encoded DER key in X.509/SPKI format. This can be read with an ASN.1 parser, e.g. https://lapo.it/asn1js/, and thus be verified.
Note that C# also supports the export of other formats. However, this depends on the version. E.g. as of .NET Core 3.0 there is the method ExportSubjectPublicKeyInfo() that exports the public key in X.509/SPKI format, DER encoded. This format and encoding can be imported directly into Java using X509EncodedKeySpec (even without BouncyCastle).
In other versions of C#, BouncyCastle for C# can be used for the export, which also supports the X.509/SPKI format.
Since you didn't post your .NET version, it's unclear what specific alternatives exist for you.
Keep in mind that an ECDH key for P-256 can also be created more simply with:
ECDiffieHellmanCng cng = new ECDiffieHellmanCng(ECCurve.NamedCurves.nistP256)
or cross-platform with
ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256)
Currently i'm updating my x.509 certificate library to support ECC. Most of the builders that are implemented take a publicKey and derive the algorithm and such from the key. In RSA this is simple, you check the algorithm of the key and you can verify the bit length. However with ECC the key is based on a curve, and the curve name (of course) needs to be specified in the certificate (as OID).
The issue i'm working on right now is finding a way to come from either a java.security.interfaces.ECPublicKey or a org.bouncycastle.jce.interfaces.ECPublicKey to a curve name. (Both implementations are completely different from each other...)
One way i can think of is getting the key's ECPoint and validate that it is on a given curve. This way i can test all supported curves, this however feels cumbersome at runtime and possibly error prone if there are points overlapping 2 or more curves.
Another way is to get the ECCurve (bc implementation) or the EllipticCurve (jre implentation) and compare the curve details with the supported implementations. This also involves stepping through every known curve.
Does anybody know a better way of finding the curve name based on curve or publicKey details using jre(8/9) and bc only. And what is your feeling about the first solution, how likely would it be to get false hits.
From your description it appears what you really need is the OID, not the name. If so, that's easier, since the curve OID is present in the "X.509" encoding of an EC public key, which is actually the SubjectPublicKeyInfo structure from X.509 (replicated in PKIX, see rfc5280 #4.1 and rfc3279 #2.3.5 but skip the parts about explicit parameters, everybody uses the namedCurve=OID option) which is the encoding for JCA public keys, for both Sun/Oracle/OpenJDK and BC implementations (and all algorithms not just ECC). BC additionally provides good support to parse this structure:
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
gen.initialize(new ECGenParameterSpec("secp256r1"));
ECPublicKey jcekey = (ECPublicKey) gen.generateKeyPair().getPublic();
//KeyFactory fact = KeyFactory.getInstance("EC", "BC");
//org.bouncycastle.jce.interfaces.ECPublicKey bckey = (org.bouncycastle.jce.interfaces.ECPublicKey)fact.generatePublic(new X509EncodedKeySpec(jcekey.getEncoded()));
// with Bouncy
byte[] enc = jcekey.getEncoded(); //enc = bckey.getEncoded();
SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(enc));
AlgorithmIdentifier algid = spki.getAlgorithm();
if( algid.getAlgorithm().equals(X9ObjectIdentifiers.id_ecPublicKey)){
ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) algid.getParameters();
System.out.println (oid.toString()); // curve OID, use as needed
}else System.out.println ("not EC?");
and for completeness even without Bouncy it's not hard if you don't use the largest curves and you're willing to cheat (which Java increasingly discourages):
import sun.security.util.DerInputStream;
import sun.security.util.ObjectIdentifier;
final ObjectIdentifier x9_id_ec = new ObjectIdentifier("1.2.840.10045.2.1");
int off = (4+2)+enc[(4+1)];
if( enc[0]==0x30 && enc[1]>0 && enc[2]==0x30 && enc[4]==6
&& new ObjectIdentifier(new DerInputStream(enc,4,off-4)).equals((Object)x9_id_ec)
&& enc[off] == 6 ){
byte[] oidenc = Arrays.copyOfRange(enc,off,off+2+enc[off+1]);
// that's the DER-encoded OID of the curve
ObjectIdentifier oid = new ObjectIdentifier(new DerInputStream(oidenc));
System.out.println (oid.toString()); // and the display form
}else System.out.println ("not EC or too big?");
I'd also note if you're building a certificate, PublicKey.getEncoded() is already the entire subjectPublicKeyInfo field, which is the only place you need to put the curve OID and except for self-signed the only place you put this key's algorithm OID.
I think i've found a valid solution using the EC5Util class for the jre type specifications. All of the double class instances with the same name make it a bit messy, however the functions are now accessible and useable.
public static final String deriveCurveName(org.bouncycastle.jce.spec.ECParameterSpec ecParameterSpec) throws GeneralSecurityException{
for (#SuppressWarnings("rawtypes")
Enumeration names = ECNamedCurveTable.getNames(); names.hasMoreElements();){
final String name = (String)names.nextElement();
final X9ECParameters params = ECNamedCurveTable.getByName(name);
if (params.getN().equals(ecParameterSpec.getN())
&& params.getH().equals(ecParameterSpec.getH())
&& params.getCurve().equals(ecParameterSpec.getCurve())
&& params.getG().equals(ecParameterSpec.getG())){
return name;
}
}
throw new GeneralSecurityException("Could not find name for curve");
}
public static final String deriveCurveName(PublicKey publicKey) throws GeneralSecurityException{
if(publicKey instanceof java.security.interfaces.ECPublicKey){
final java.security.interfaces.ECPublicKey pk = (java.security.interfaces.ECPublicKey) publicKey;
final ECParameterSpec params = pk.getParams();
return deriveCurveName(EC5Util.convertSpec(params, false));
} else if(publicKey instanceof org.bouncycastle.jce.interfaces.ECPublicKey){
final org.bouncycastle.jce.interfaces.ECPublicKey pk = (org.bouncycastle.jce.interfaces.ECPublicKey) publicKey;
return deriveCurveName(pk.getParameters());
} else throw new IllegalArgumentException("Can only be used with instances of ECPublicKey (either jce or bc implementation)");
}
public static final String deriveCurveName(PrivateKey privateKey) throws GeneralSecurityException{
if(privateKey instanceof java.security.interfaces.ECPrivateKey){
final java.security.interfaces.ECPrivateKey pk = (java.security.interfaces.ECPrivateKey) privateKey;
final ECParameterSpec params = pk.getParams();
return deriveCurveName(EC5Util.convertSpec(params, false));
} else if(privateKey instanceof org.bouncycastle.jce.interfaces.ECPrivateKey){
final org.bouncycastle.jce.interfaces.ECPrivateKey pk = (org.bouncycastle.jce.interfaces.ECPrivateKey) privateKey;
return deriveCurveName(pk.getParameters());
} else throw new IllegalArgumentException("Can only be used with instances of ECPrivateKey (either jce or bc implementation)");
}
Actually (at least for keys generated by SunEC provider) OID can be extracted much easier than other answers suggest:
import java.security.Key;
import java.security.AlgorithmParameters;
import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec;
public static String curveOid(Key key) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
params.init(((ECKey) key).getParams());
return params.getParameterSpec(ECGenParameterSpec.class).getName();
}
I have an RSA key pair that I generated in Java and I need to programmatically write the private key to the same format that openssl does when I run this command (and enter the appropriate data for the prompts, namely a passphrase to protect the private key):
openssl req -out request.csr -newkey rsa:2048 -keyout privkeyfile
The Java code to generate the key pair is pretty standard:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.genKeyPair();
A sample output of running that openssl command (on my Windows machine) is:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI1c9lGdEA388CAggA
MBQGCCqGSIb3DQMHBAgKdWZEOyS2XgSCBMgI7b/vAeE6yz136BZkOzOPLv0uTz/Z
5mP4xO8IdAybE+PHJ71Mro4kgz+EMN39dk0ZWxbNnPpHGD+a6LfNKxos8fJL+dbz
dgc7I4fH9UQFLnYM64Xmq4aG66fIehuhqXBUUru+PJdBf5bfPDJDYAVEUsZ2J8bW
n4pLeS62Orwe+hhe/i/Y4gmgGAxGhUlDGc7T/N4RvhWUVNXQKGCGynj9Mv+LRzW6
rkGbBALQAKnj2tuihGSKhhR3WoNxTFqU9HsRGkzbJ5AiYhyBk7ObQw285TyIQS6k
OH25byIeaqzQ3Xn/wB6VrOQrsCvbWim1DZEIGRp6B+RKd0vUrkxFZRBsLdGXN1w/
bCM4dmhUJng2O+a9tSif7CC0emJqXgvkE2lGA9RMZltfOK+Kohi4L0WKxIX8TQqP
KzDhecSOaOkdXI0Tpho1QZDS6D+nvN2OiXlswgB0By3pkLdx4j7ZtjhqH/cg4rlE
8R1HzcilIrSlMK579UNGieis2wHaWobeinJqP6ruHK3HiAvG/WLFQ4TKexFa4/gy
EdXPaV9owRi+9nyRZGT8NsfzUDg5oTBLcg08uOlNHr8z8pF6a7l2sr4bzgcYFlko
BCIunMJpXYp/lUnL9daElbOPbGgeLNa8KfU7tXnzYsCg3iUx79fQUoql2pn2wMc3
0vQVTZ7/Enzl8cM2srl0uf1JMxMGOJ2kbdYZ8VwxaaMHnghN97eBsp+aRFCuAN4x
+D90ABBxRcBwzBOf8sT77vYXvQZNqUnzl5GJh2hlXCB5upNFqbSGaa6Yk+y4cw5e
3tB3/BHwZop2AAnPexnnQuCsn+SpCiLF+/agMouph61oWWJYQMwmUemNy/5G6AoP
KdBGqBAXonRSk8pBNqglHl0GOiBITl45+Bk4JBGM6+NcEpQ8B3OA+Vkj0n/aF/Iw
66Fo+UyA64fboC3q6DLxHZuTAY/giytwUW2QM4yFkEOm1v1WisTf0MO3Zt+ghuBn
8DG9MXGxP0XA9QzHAjCcDD8DK/hXsxaBg6xOV4bV+HhhJXsyWQAqcqKQro9Ik3L9
YvdJNU9BWyzKV79j5gYkDgLZgcA8QrGDArFZ5Hr9HdepBu8Njk09YDKJsfVMmk4E
6NbzxqHgPyYY3QtANLKg3EImBfuRHwfgfbaamrmYE0fSyh/QJMK0zDtDpkgiiTre
A8b16rBdBxZBSaO/J+Oje0pePLBRRhwX4WxcPsZeN5fO6S2NTECrWsf0jDG4D6pa
cannasXB4LoBifAYhKKTXFbQRY74wOxVfI7gw0qEjB7Jb1M2zCMwddgumOiCzGpu
d9voABJdGMdhwZ/FLbuxcr0y3p8Y5N9vW8ffSlxEtvhbPlszpgTPi2WWTNE+wTUQ
so4cvWFq9SP3Mg6Te6AStjdN1Mnhj2fb7ogxa5rsNxVrE/guRVgly4i9vG7Mi2Wd
bhT1vQypyL9g97nq0rRznDAjAtLenOagK4h+WJgZN2RpUhkWmO1trLGao/PrhgvD
8mOMCnZIQGMk5vS55druRoakPjsx4yZpzZvw5gPBXJ0H1KmbFUO1aSy/6N4nVBW+
Khr+ZHxboPD0zxJMzANjuOIJ/C46Hx5Wb/VP49NDmOLzLAi3+YSAhi3PB9D8vzxQ
MwM=
-----END ENCRYPTED PRIVATE KEY-----
EDIT Changed the sample output from openssl
EDIT I tried to read the openssl generated private key file with Java using the code below to try and get some of the parameters but I ended up getting the following exception:
Exception in thread "main" java.io.IOException: ObjectIdentifier() -- data isn't an object ID (tag = 48)
at sun.security.util.ObjectIdentifier.<init>(Unknown Source)
at sun.security.util.DerInputStream.getOID(Unknown Source)
at com.sun.crypto.provider.PBES2Parameters.engineInit(PBES2Parameters.java:267)
at java.security.AlgorithmParameters.init(Unknown Source)
at sun.security.x509.AlgorithmId.decodeParams(Unknown Source)
at sun.security.x509.AlgorithmId.<init>(Unknown Source)
at sun.security.x509.AlgorithmId.parse(Unknown Source)
at javax.crypto.EncryptedPrivateKeyInfo.<init>(EncryptedPrivateKeyInfo.java:95)
at crypto.ReadOpensslKey.main(ReadOpensslKey.java:35)
Java code to read file:
package crypto;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
public class ReadOpensslKey {
public static void main(String[] args) throws Exception {
String encrypted = new String(Files.readAllBytes(Paths.get("<insert path to openssl generated privkeyfile>")));
//Create object from encrypted private key
encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.decode(encrypted)); // exception is thrown here
System.out.println(pkInfo.getAlgName());
PBEKeySpec keySpec = new PBEKeySpec("abcde".toCharArray()); // password
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(pbeKeyFactory.generateSecret(keySpec));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey encryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
}
}
First, do you really need this specific format, or just a format OpenSSL (and programs using OpenSSL like Apache httpd and nginx and curl and PHP and many more) can use? If the latter, there are several other options that are easier and better. But you didn't ask that, so I won't answer it.
Second, you must have a really old OpenSSL. Since release 1.0.0 in 2010, req -newkey -keyout writes PKCS8 format, not traditional aka legacy format.
Third, this format is PEM not DER; there is a traditional DER format but it cannot be encrypted. (PKCS8 can be encrypted in either DER or PEM.)
Fourth, if you can use BouncyCastle, it can do this directly; from (any recent version of) bcpkix use org.bouncycastle.openssl.jcajce.JcaMiscPEMGenerator on your standard-JCE PrivateKey and a JcePEMEncryptorBuilder specifying DES-EDE3-CBC to create a PEMObject in memory, and then PEMWriter to write that out.
Even if you can't actually use BC, it is open source (and pretty well designed IMO, though mostly lightly commented) and it may help you to look at their code.
Those said, what you asked is documented (barely) by the man page for PEM_write_RSAPrivateKey (which should be on your system but since your version is old you might better use the web copy) at the section headed 'PEM ENCRYPTION FORMAT' near the end, combined with the referenced man page for EVP_BytesToKey. Specifically:
construct the 'traditional' encoding which is RSAPrivateKey from PKCS1 (currently rfc8017), not the PKCS8/rfc5208 PrivateKeyInfo encoding returned by JCE PrivateKey.getEncoded(). The PKCS8 encoding does include the PKCS1 encoding as a portion (PKCS8 is an algorithm-generic wrapper around any number of algorithm-specific encodings) so you can either extract the PKCS1 from the PKCS8 (as BC does) or construct the PKCS1 directly from the key components n,e,d,p,q,dmp1,dmq1,qinvp. (The otherPrimeInfos is only for so-called 'multiprime' RSA which means more than 2 factors of n and which almost noone actually uses.)
derive the actual encryption key from the password using one(!) iteration of MD5 applied to password||salt where salt is a copy of the random IV (8-bytes for 3DES) plus (since that is not enough) MD5(firstblock||password||salt) then truncate the total to 24 bytes.
encrypt with 3DES (which JCE calls DESEDE) with CBC (with IV as above) and PKCS5 padding. (For 3DES or DES the low bit of each byte of key is nominally parity, but you don't need to set them because JCE doesn't implement them.)
convert to base64 with linebreaks every 64 characters, and add the BEGIN and END lines and the header lines including, as you correctly guessed, the IV in hex
I ended up getting it to work using the following code:
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.util.io.pem.PemObject;
public class WriteOpensslKey {
public static void main(String[] args) throws Exception {
// provider is needed for the encryptor builder
Security.addProvider(new BouncyCastleProvider());
// generate key pair
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(2048, new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
// construct encryptor builder to encrypt the private key
JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(PKCS8Generator.AES_256_CBC);
encryptorBuilder.setRandom(new SecureRandom());
encryptorBuilder.setPasssword("password".toCharArray());
OutputEncryptor encryptor = encryptorBuilder.build();
// construct object to create the PKCS8 object from the private key and encryptor
JcaPKCS8Generator pkcsGenerator = new JcaPKCS8Generator(keyPair.getPrivate(), encryptor);
PemObject pemObj = pkcsGenerator.generate();
StringWriter stringWriter = new StringWriter();
try (JcaPEMWriter pemWriter = new JcaPEMWriter(stringWriter)) {
pemWriter.writeObject(pemObj);
}
// write PKCS8 to file
String pkcs8Key = stringWriter.toString();
FileOutputStream fos = new FileOutputStream("<path to output file>");
fos.write(pkcs8Key.getBytes(StandardCharsets.UTF_8));
fos.flush();
fos.close();
}
}
I was then able to use this private key with openssl to sign a file as a quick test that it works.
Huge thanks to #dave_thompson_085 for pointing me in the right direction!
I'm currently creating a form of challenge-response authentication for a project I need for my Master thesis in computer science.
For this purpose, I need to create an RSA-PSS signature with a private key that is authenticated by a fingerprint so that it can only be used to create a signature when the owner of the device is physically present.
To achieve this, I use the Android KeyStore (backed by Keymaster/Gatekeeper in ARM TrustZone) to generate an RSA key pair (KEY_ALGORITHM_RSA) for use with the RSA-PSS signature algorithm (SIGNATURE_PADDING_RSA_PSS) for creating and verifying signatures (PURPOSE_SIGN | PURPOSE_VERIFY). I also require user authentication by setting the corresponding property to true.
Later, to create the signature over a buffer final byte[] message, I ...
obtain an instance of the FingerprintManager service
create an instance of the SHA512withRSA/PSS signature algorithm (Signature object)
initialize the Signature algorithm for signing with the private key (initSign(...))
wrap the Signature object into a CryptoObject
(perform some additional checks)
authenticate(...) the CryptoObject using the instance of FingerprintManager, passing (among others) a FingerprintManager.AuthenticationCallback to be called after the key has been authenticated by the user (by touching the fingerprint sensor on his/her device)
Inside the callback, use of the key is authenticated, so I ...
extract the Signature object from the CryptoObject wrapper again
use the update(...) method on the Signature object to stream the data to be signed (message) into the signature algorithm
use the sign() method on the Signature object to obtain the signature
encode that signature as Base64 and println(...) it out to StdErr so it appears in adb logcat
I created a sample code which is about as minimal as it gets.
package com.example.andre.minimalsignaturetest;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Enumeration;
/*
* Sample code to test generation of RSA signature authenticated by fingerprint.
*/
public final class MainActivity extends AppCompatActivity {
private final String tag;
/*
* Creates a new main activity.
*/
public MainActivity() {
this.tag = "MinimalSignatureTest";
}
/*
* Generate a 4096-bit key pair for use with the RSA-PSS signature scheme and store it in Android key store.
*
* (This is normally done asynchronously, in its own Thread (AsyncTask), with proper parametrization and error handling.)
*/
public void generate(final View view) {
/*
* Generate RSA key pair.
*/
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder("authKey", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
builder.setKeySize(4096);
builder.setDigests(KeyProperties.DIGEST_SHA512);
builder.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
builder.setUserAuthenticationRequired(true);
KeyGenParameterSpec spec = builder.build();
generator.initialize(spec);
KeyPair pair = generator.generateKeyPair();
PublicKey publicKey = pair.getPublic();
byte[] publicKeyBytes = publicKey.getEncoded();
String publicKeyString = Base64.encodeToString(publicKeyBytes, Base64.NO_WRAP);
Log.d(this.tag, "Public key: " + publicKeyString);
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
Log.d(this.tag, "Key generation failed!", e);
}
}
/*
* Returns the private key stored in the Android key store.
*/
private PrivateKey getPrivateKey() {
/*
* Fetch private key from key store.
*/
try {
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
Enumeration<String> enumeration = store.aliases();
String alias = null;
/*
* Find the last key in the key store.
*/
while (enumeration.hasMoreElements())
alias = enumeration.nextElement();
/*
* Check if we got a key.
*/
if (alias == null)
return null;
else {
Key key = store.getKey(alias, null);
/*
* Check if it has a private part associated.
*/
if (key instanceof PrivateKey)
return (PrivateKey) key;
else
return null;
}
} catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | UnrecoverableKeyException e) {
Log.d(this.tag, "Obtaining private key failed!", e);
return null;
}
}
/*
* Create an RSA-PSS signature using a key from the Android key store.
*/
public void sign(final View view) {
final byte[] message = new byte[0];
final PrivateKey privateKey = this.getPrivateKey();
Context context = this.getApplicationContext();
FingerprintManager manager = (FingerprintManager)context.getSystemService(Context.FINGERPRINT_SERVICE);
/*
* Create RSA signature.
*/
try {
Signature rsa = Signature.getInstance("SHA512withRSA/PSS");
rsa.initSign(privateKey);
/*
* Check if we have a fingerprint manager.
*/
if (manager == null)
Log.d(this.tag, "The fingerprint service is unavailable.");
else {
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(rsa);
CancellationSignal signal = new CancellationSignal();
/*
* Create callback for fingerprint authentication.
*/
FingerprintManager.AuthenticationCallback callback = new FingerprintManager.AuthenticationCallback() {
/*
* This is called when access to the private key is granted.
*/
#Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
FingerprintManager.CryptoObject cryptoObject = result.getCryptoObject();
Signature rsa = cryptoObject.getSignature();
/*
* Sign the message.
*/
try {
rsa.update(message);
byte[] signature = rsa.sign();
String signatureString = Base64.encodeToString(signature, Base64.NO_WRAP);
Log.d(tag, "Signature: " + signatureString);
} catch (SignatureException e) {
Log.d(tag, "Signature creation failed!", e);
}
}
};
/*
* Check if we have a fingerprint reader.
*/
if (!manager.isHardwareDetected())
Log.d(this.tag, "Your device does not have a fingerprint reader.");
else {
/*
* Check if fingerprints are enrolled.
*/
if (!manager.hasEnrolledFingerprints())
Log.d(this.tag, "Your device does not have fingerprints enrolled.");
else
manager.authenticate(cryptoObject, signal, 0, callback, null);
}
}
} catch (NoSuchAlgorithmException | InvalidKeyException | SecurityException e) {
Log.d(this.tag, "Signature creation failed!", e);
}
}
/*
* This is called when the user interface initializes.
*/
#Override protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
}
}
(It's still ~200 LOC long, but fingerprint-authenticated cryptography needs a bit of code to make it work, so I can't seem to get this any smaller/simpler.)
To test it, just create a project with a single activity in Android Studio. Insert two buttons into this activity, one for generating a key (i. e. labelled Generate) and one for creating a signature (i. e. labelled Sign).
Then insert the sample code into your main activity and link the onclick events from the Generate button to the public void generate(final View view) method and from the Sign button to the public void sign(final View view) method.
Finally, insert the following into your AndroidManifest.xml, inside the top-level <manifest ...> ... </manifest> tag.
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
Run the project and let adb logcat run alongside it.
After you hit the Generate button, you should see an output like this in the logs.
07-04 14:46:18.475 6759 6759 D MinimalSignatureTest: Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICC...
This is the public key of the key pair that has been generated.
(You will also see some complaints about key generation taking place in the main thread, however, this is just to keep the sample code simple. The actual application performs key generation in its own thread.)
Then, first hit Sign, then touch the sensor. The following error will occur.
keymaster1_device: Update send cmd failed
keymaster1_device: ret: 0
keymaster1_device: resp->status: -30
SoftKeymaster: system/keymaster/openssl_err.cpp, Line 47: error:00000000:invalid library (0):OPENSSL_internal:invalid library (0)
SoftKeymaster: system/keymaster/openssl_err.cpp, Line 88: Openssl error 0, 0
MinimalSignatureTest: java.security.SignatureException: android.security.KeyStoreException: Signature/MAC verification failed
MinimalSignatureTest: at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineSign(AndroidKeyStoreSignatureSpiBase.java:333)
MinimalSignatureTest: at java.security.Signature$Delegate.engineSign(Signature.java:1263)
MinimalSignatureTest: at java.security.Signature.sign(Signature.java:649)
MinimalSignatureTest: at com.example.andre.minimalsignaturetest.MainActivity$1.onAuthenticationSucceeded(MainActivity.java:148)
MinimalSignatureTest: at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:855)
MinimalSignatureTest: at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:803)
MinimalSignatureTest: at android.os.Handler.dispatchMessage(Handler.java:102)
MinimalSignatureTest: at android.os.Looper.loop(Looper.java:154)
MinimalSignatureTest: at android.app.ActivityThread.main(ActivityThread.java:6186)
MinimalSignatureTest: at java.lang.reflect.Method.invoke(Native Method)
MinimalSignatureTest: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
MinimalSignatureTest: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
MinimalSignatureTest: Caused by: android.security.KeyStoreException: Signature/MAC verification failed
MinimalSignatureTest: at android.security.KeyStore.getKeyStoreException(KeyStore.java:676)
MinimalSignatureTest: at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
MinimalSignatureTest: at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineSign(AndroidKeyStoreSignatureSpiBase.java:328)
System.err: ... 11 more
This is where I'm stuck.
The weird thing is that I get Signature/MAC verification failed as the message of the SignatureException. Note that it says verification failed, while I'm actually signing (not verifying) and the entire stack trace shows, that only signSomething(...) functions are called.
I've tried this on the LG Nexus 5X with both the official firmware (Android 7.1.1, N2G47W) and different (up-to-date) LineageOS nightlies and they all fail at this point. However, when I consider the API documentation, it seems as if I'm doing the right stuff, and - to be honest - there's not a lot of stuff you could actually do differently. It actually seems to be pretty obvious how it works.
Note that, as long as I do not require user authentication - and therefore don't create the signature in the callback method, but outside, right after the initSign(...) - it works fine - even with hardware-backed key storage by Keymaster/Gatekeeper in TrustZone. But as soon as I require authentication, - and therefore do the update(...) and sign() calls on the Signature object inside the callback - it all breaks apart.
I tried to trace down the error in the OpenSSL library or to find out, what that -30 response code means, but both to no avail.
Any suggestions? I've gone a long way and implemented a ton of stuff, both server-side and on Android, to get this project going forward, but now I'm stuck and seem unable to perform user authentication that's cryptographically sound.
I tried replacing KeyProperties.SIGNATURE_PADDING_RSA_PSS with KeyProperties.SIGNATURE_PADDING_RSA_PKCS1 and SHA512withRSA/PSS with SHA512withRSA, then KeyProperties.DIGEST_SHA512 with KeyProperties.DIGEST_SHA256 and SHA512withRSA with SHA256withRSA. I also tried a smaller key size - 2048 bit instead of 4096 bit - all to no avail.
I also tried to shift commands from the initSign(...), update(...), sign() procedure from the outside of the callback to the inside or the other way round, however, this is the only combination, that's supposed to work. When I move initSign(...) inside the callback as well, the call to authenticate(...) fails with java.lang.IllegalStateException: Crypto primitive not initialized. When I move update(...) and sign() outside the callback, the call to sign() fails with java.security.SignatureException: Key user not authenticated. So initSign(...) has to be outside and sign() has to be inside. Where update(...) happens, appears to be uncritical, however, from a semantic point of view, it makes sense to keep it together with the call to sign().
Any help is really appreciated.
Change your getPrivateKey method to:
private PrivateKey getPrivateKey() {
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
return (PrivateKey) keyStore.getKey("authKey", null));
}
In your code, you iterate through all keys and grep the last one, which isn't necessarily the one that you want - or even worse: return null if that key doesn't have a private key...
If you want to check whether a key exist:
if (store.containsAlias(keyName)) {
...
}
I finally found a solution.
There were actually two problems at hand here.
I tried to use SHA-512 as a mask-generation function for RSA/PSS, which is "probably" unsupported by the cryptographic library that Android uses.
I tried to sign an empty (0-byte) message, which somehow appears to be "problematic".
When I both changed the MGF to SHA-256 and made the message 64 bytes long, signature generation succeeded.
Now, both "requirements" appear to be a bit "weird".
First, you can indeed use SHA-512 as an MGF for RSA/PSS, as long as you setUserAuthenticationRequired(false), so it has to be supported by the cryptographic library. It's only when you enable authentication that it suddenly fails and you have to fall back to SHA-256. I did not perform extensive testing which hash functions work as MGFs for RSA/PSS with authentication and which do not. I just found that SHA-512 does not work but SHA-256 does, so the choice of MGF is somehow "restricted" when authentication is enabled.
Second, your message needs to have a certain minimal size in order for it to be signed with authentication enabled. For example, you cannot sign an empty buffer. This makes no sense to me at all since the first step in RSA/PSS is to apply a cryptographic hash function to the message, the output of which is fixed length, so the signature scheme really shouldn't care how long or short the message is, but apparently it does. Like before, I didn't perform extensive testing to find the exact cutoff point where the message becomes "long enough" for signing. However, I found that a 64 byte message can be signed, while an empty (0 byte) message cannot, so the minimal length is somewhere within [1; 64] bytes, both limits inclusive.
Note that, as of now, this seems to be documented nowhere and also the exception thrown is of no use. It just says "signature verification failed" (yes, it says "verification" even though we're actually generating a signature), so you have no idea that you have to change the MGF and the length of the message to be signed.
Due to this, there might be more to it that I haven't found. I just found this parametrization by "trial and error" and thus have no idea what the actual constraints of the cryptographic library look like.
I have two text files, one with a "-----BEGIN CERTIFICATE-----" header, one with a "-----BEGIN RSA PRIVATE KEY-----" header. I need to use CXF ClientBuilder to make a REST service call to a remote host.
I think my loading of the cert file is ok, but I can't figure out how to handle the private key file properly.
I have the following tentative code (which doesn't quite compile yet, as you'll see) to initialize the Client object (with some minor elisions):
private Certificate buildCertFromFile(String fileName) throws CertificateException {
return CertificateFactory.getInstance("X.509").generateCertificate(ClassLoaderUtils.getResourceAsStream(fileName, <ThisClass>.class));
}
#PostConstruct
public void init() {
try {
KeyStore trustStore = KeyStore.getInstance("jks");
trustStore.load(null, null);
trustStore.setCertificateEntry("cert", buildCertFromFile("<path to cert file>"));
KeyStore keyStore = KeyStore.getInstance("jks");
keyStore.load(null, "abc".toCharArray());
// Need something here.
keyStore.setKeyEntry("key", key, "abc", chain);
ClientBuilder builder = ClientBuilder.newBuilder();
builder.trustStore(trustStore);
builder.keyStore(keyStore, "abc");
builder.hostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String host, SSLSession session) {
try {
Certificate[] certs = session.getPeerCertificates();
return certs != null && certs[0] instanceof X509Certificate;
}
catch (SSLException ex) {
return false;
}
}
});
client = builder.build();
}
catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) {
ex.printStackTrace();
}
}
Update:
I'm experimenting with the following method to load the private key file:
public static PrivateKey getPrivateKey(String filename) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
String contents = IOUtils.toString(ClassLoaderUtils.getResourceAsStream(filename, TguardService.class), "UTF-8");
contents = contents.replaceAll("-----[A-z ]+-----", "").trim();
System.out.println("contents[" + contents + "]");
byte[] bytes = Base64.getDecoder().decode(contents);
System.out.println("decoded[" + new String(bytes) + "]");
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
However, this is giving me the following error:
Caused by: java.lang.IllegalArgumentException: Illegal base64 character a
at java.util.Base64$Decoder.decode0(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at java.util.Base64$Decoder.decode(Unknown Source)
at com.att.detsusl.tguardrest.TguardService.getPrivateKey(TguardService.java:58)
The debugging output shows that "contents" looks somewhat like this:
contents[MIIEp....
...
...
...kdOA=]
Update:
Ok, I managed to figure out that I had to remove ALL the newlines from the encoded string, so now it gets through base64 decode, but now it fails on the call to "generatePrivate()", with the following:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(Unknown Source)
at java.security.KeyFactory.generatePrivate(Unknown Source)
at com.att.detsusl.tguardrest.TguardService.getPrivateKey(TguardService.java:63)
I've seen some notes that imply that I must have a "PKCS#1" format, instead of "PKSC#8" and talk about calling "openssl" to convert the file. I'd really rather not do that. Is there a straightforward way to do this conversion (assuming this is what I need) in Java? I intend this code to execute only once at application startup.
Update:
Ok, after considering the alternatives, I used openssl to convert the file to PKCS#8 format. After that, I was able to complete the construction of the Client object, but I get another error while trying to make a connection. I considered it possible that this is a related but different problem (and I wanted to reduce the number of "Updates" on a single posting), so I posted that as a separate question at CXF REST client call with 2-way auth failing with "unable to find valid certification path to requested target" .
Also note that I've converted the key file from PKCS#1 to PKCS#8, but I never did anything with the cert file.
In the meantime, I could use a little more background on PKCS#1 vs. PKCS#8. How "legacy" is PKCS#1? I note that the original name of the key file that was given to me used an acronym representing the previous name of our organization, which changed several years ago.