The BouncyCastle cryptography APIs allow for creating and verifying digital signatures using the regular java.security package objects, such as java.security.PublicKey, java.security.PrivateKey and their container java.security.KeyPair.
Suppose I use OpenSSL to create a .pem (or, if easier, a .der file) containing the elliptic curve private key I want to use in my application. For example, it looks like this:
-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIDzESrZFmTaOozu2NyiS8LMZGqkHfpSOoI/qA9Lw+d4NoAcGBSuBBAAK
oUQDQgAE7kIqoSQzC/UUXdFdQ9Xvu1Lri7pFfd7xDbQWhSqHaDtj+XY36Z1Cznun
GDxlA0AavdVDuoGXxNQPIed3FxPE3Q==
-----END EC PRIVATE KEY-----
How do I use the BouncyCastle APIs to obtain a java.security.KeyPair containing both this private key and a corresponding public key?
Please note I want to use the APIs available in BouncyCastle 1.50 (which is current at the time of writing) and no deprecated APIs. This unfortunately excludes the PEMReader class used in other SO answers. Furthermore, this question is specific to the format of elliptic curves; they contain additional parameters when compared RSA or DSA key files.
In addition to the standard JCE approach shown by divanov as long as you give it the correct input (see my comment thereto), or just using JCE in the first place like your selfanswer, BouncyCastle 1.48 up DOES still contain the old PEMReader functionality just organized a bit differently and for this case you can use something like:
static void SO22963581BCPEMPrivateEC () throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Reader rdr = new StringReader ("-----BEGIN EC PRIVATE KEY-----\n"
+"MHQCAQEEIDzESrZFmTaOozu2NyiS8LMZGqkHfpSOoI/qA9Lw+d4NoAcGBSuBBAAK\n"
+"oUQDQgAE7kIqoSQzC/UUXdFdQ9Xvu1Lri7pFfd7xDbQWhSqHaDtj+XY36Z1Cznun\n"
+"GDxlA0AavdVDuoGXxNQPIed3FxPE3Q==\n"+"-----END EC PRIVATE KEY-----\n");
Object parsed = new org.bouncycastle.openssl.PEMParser(rdr).readObject();
KeyPair pair = new org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter().getKeyPair((org.bouncycastle.openssl.PEMKeyPair)parsed);
System.out.println (pair.getPrivate().getAlgorithm());
}
In Java this will be pretty much the same code. After striping guarding strings away and decoding Base64 data give it to this utility method:
public static PrivateKey keyToValue(byte[] pkcs8key)
throws GeneralSecurityException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8key);
KeyFactory factory = KeyFactory.getInstance("ECDSA");
PrivateKey privateKey = factory.generatePrivate(spec);
return privateKey;
}
Since I only need this for a quick and dirty demo, I solved it in the following way (in Scala). First, I generate a public private key pair in the REPL and print out its data:
Security.addProvider(new BouncyCastleProvider)
val SignatureScheme = "some signature scheme, eg ECDSA"
val RandomAlgorithm = "some random algorithm, eg SHA1PRNG"
val keygen = KeyPairGenerator.getInstance(SignatureScheme)
val rng = SecureRandom.getInstance(RandomAlgorithm)
rng.setSeed(seed)
keygen.initialize(KeySize, rng)
val kp = keygen.generateKeyPair()
println(kp.getPublic.getEncoded.toSeq) // toSeq so that Scala actually prints it
println(kp.getPrivate.getEncoded.toSeq)
Then using the generated data,
val hardcodedPublic = Array[Byte]( /* data */ )
val hardcodedPrivate = Array[Byte]( /* data */ )
val factory = KeyFactory.getInstance(SignatureScheme)
val publicSpec = new X509EncodedKeySpec(hardcodedPublic)
val publicKey = factory.generatePublic(publicSpec)
val privateSpec = new PKCS8EncodedKeySpec(hardcodedPrivate)
val privateKey = factory.generatePrivate(privateSpec)
The key thing you need to know here is that by default public key data uses X509 encoding and private key data uses PKCS8 encoding. It should be possible to get OpenSSL to output these formats and parse them manually, but I did not check how.
I used information from this blog post about SpongyCastle (which is Android's BouncyCastle alias) quite helpful. It is unfortunate that documentation is fragmented like this, and that BouncyCastle's wiki was down at the time of this question.
Update: the BouncyCastle wiki is up, and you can find the documentation here.
Related
I'm looking at reading one or more private keys from PEM-encoded DER files and I'm wondering if it can be done robustly without just trying all possible algorithms (which is ugly and brute-forcey) or writing an ASN.1 parser?
I'm trying to do this without any dependencies other than the standard Java library. I'm sure BouncyCastle can do this, but I'm interested in what is possible with out-of-the-box Java.
A PEM-encoded DER file is the somewhat familiar file that looks like this:
-----BEGIN EC PRIVATE KEY-----
[base64-encoded DER data]
-----END EC PRIVATE KEY-----
In the example above, the text indicates the type of key: it's an elliptic-curve key, so we can ask Java for an EC key factory:
byte[] keyBytes = // get DER bytes
KeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey key = kf.generateKey(spec);
But it's also possible that the file does not advertise its contents, like this:
-----BEGIN PRIVATE KEY-----
[base64 encoded DER data]
-----END PRIVATE KEY-----
In this case, I see only two obvious possibilities given the API:
Loop through all supported algorithms, trying them all and ignoring certain errors
Implement an ASN.1 parser to determine the actual type of the key, then use the right KeyFactory
Looping seems straightforward, but IMO distasteful:
byte[] keyBytes = // get DER bytes
KeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey key = null;
for(String algorithm : new String[] { "EC", "RSA", "DSA" }) {
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
key = kf.generatePrivateKey(keyBytes);
} catch (....) {
// Ignore "wrong algorithm" exceptions
}
}
// "key" is either null (couldn't read/interpret) or non-null and OK
This is also potentially time-consuming, though typically key setup is a "rare" operation, so performance isn't strictly important.
The only other option I can think of is inspecting the data to find out exactly what the data represent, and then use the correct type:
byte[] keyBytes = // get DER bytes
KeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
ASN1Parser a1p = new ASN1Parser();
String algorithm = a1p.parse(keyBytes).getAlgorithm();
KeyFactory kf = KeyFactory.getInstance(algorithm);
PrivateKey key = fk.generatePrivateKey(spec);
Contrast this with reading an X.509 Certificate, where you don't have to know anything about the algorithm to load the certificate:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
cf.generateCertificate(new ByteArrayInputStream(certBytes));
Am I missing something in the API that would allow this kind of "just do it for me" mechanics, or is it strictly necessary to know the algorithm before loading the key material?
Writing an ASN.1 parser isn't trivial and though they exist in third-party libraries, I'd like to limit the dependencies as much as possible.
I am using the prime256v1 curves for generating key pairs and sign using nodejs with the default crypto module.
Using crypto
let crypto = require('crypto');
let e = crypto.createECDH('prime256v1');
e.generateKeys();
privateKey = e.getPrivateKey();
privateKeyHex = privateKey.toString('hex');
publicKey = e.getPublicKey();
publicKeyHex = publicKey.toString('hex');
I obtain a publickey which looks like the hex string below:
'049a6b0ac242afe41128cf59736412686ca83c9e902ee3fa0f13810b9d59ebfe5e49204427c23b630be12ae33815b0bda6ed8d0603386c6ea5f1906cdb0e731286'
Usign jsrsasign
let jsrsa = require('jsrsasign');
let KEYUTIL = jsrsa.KEYUTIL;
let kp = KEYUTIL.generateKeypair("EC", "prime256v1");
let pkHex = kp.pubKeyObj.pubKeyHex
which returns
'04f36e41189420db05dd8a73e3cb310b0c55809190bdedd89bf19769ac8df3cd06c1380f646e9e65e31c24affff79e43516b37e0186c3753cfdfd29894c2becc84'
Converting the PublicKey Hex to PublicKey object in Java
I want to use these publicKeys and convert it into a PublicKey object in java. Using the EC KeyFactory, I convert the hex to a byte[] and try to construct the PublicKey object in java which expects a X.509 format encoding.
public PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pub = kf.generatePublic(publicKeySpec);
return pub;
}
To convert the hex string to a byte[] I use the following:
public byte[] hexStringToByteArray(String hexString) {
byte[] bytes = new byte[hexString.length() / 2];
for(int i = 0; i < hexString.length(); i += 2) {
String sub = hexString.substring(i, i + 2);
Integer intVal = Integer.parseInt(sub, 16);
bytes[i / 2] = intVal.byteValue();
String hex = "".format("0x%x", bytes[i / 2]);
}
return bytes;
}
Trying to do the same using the test case as follows results in an InvalidKeySpecException:
#Test
public void pkConversionTest() throws NoSuchAlgorithmException, InvalidKeySpecException {
ECDSA.setDebug(true);
byte[] pk = hexStringToByteArray("049a6b0ac242afe41128cf59736412686ca83c9e902ee3fa0f13810b9d59ebfe5e49204427c23b630be12ae33815b0bda6ed8d0603386c6ea5f1906cdb0e731286");
PublicKey pub = ECDSA.getPublicKey(pk);
System.out.println(pub);
}
returns
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=26, too big.
I am however able to generate a KeyPair using java and use the publicKey hex obtained with nodejs to perform signature verify. A sample publickey hex generated from java looks as follows:
3059301306072a8648ce3d020106082a8648ce3d0301070342000425a321d5a1a74e6c04a6e3cab030401f3dbc04d5242f9bc629175c3d3988799175eb80cd96d7e76ea924630a8d86b93c54dec7cb965b58de31705eb3343846a1
How do I format the publicKey generated by nodejs in an X.509 format to be used on the java's side?
Edit:
3059301306072a8648ce3d020106082a8648ce3d030107034200 seems to be a common prefix for the publicKey hexes generated using java. By Prefixing this to the hex values of the PublicKey obtained using nodejs since the length is smaller seems to solve the problem. But can someone explain why?
Thank you.
But can someone explain why?
Java encodes public keys in "X.509" format or more exactly the SubjectPublicKeyInfo structure (SPKI) defined by X.509/PKIX; see rfc5280, rfc3279, and for ECC specifically rfc5480. That's why the data you pass to the key factory is in a class named X509EncodedKeySpec. This ASN.1 structure contains an AlgorithmIdentifier which identifies the algorithm used and its parameters (which for ECC is the curve/group used, in your case an OID identifying prime256 aka P-256 aka secp256r1) plus a BIT STRING type containing the actual encoded publickey value (which for ECC is the point in X9.62 format, which has several variants; here you are using uncompressed; according to the doc nodejs.crypto also supports compressed).
Your "prefix" is the DER encoding of the ASN.1 outer SEQUENCE, AlgorithmIdentifier, and tag length and padcount which begin the BIT STRING to contain the publickey point.
Basically dupe:
* How can I get a PublicKey object from EC public key bytes?
* Loading raw 64-byte long ECDSA public key in Java (Maarten's answer is effectively what you did)
* How can I generate a valid ECDSA EC key pair? (disclosure: mine)
FYI: Effectively the same issue also occurs for RSA and there are more Qs on that.
And there are also similar issues with privatekeys in generic PKCS8 format compared to algorithm-specific formats, but since publickeys are usually exchanged with other systems and/or programs while privatekeys usually aren't interoperability of privatekey encoding is less often a concern.
I am trying to implement Diffie-Hellman key exchange in Java, but I'm having a hard time understanding the specification:
Complete the Diffie-Hellman key exchange process as a local mechanism
according to JWA (RFC 7518) in Direct Key Agreement mode using curve
P-256, dT and QC to produce a pair of CEKs (one for each direction)
which are identified by Transaction ID. The parameter values supported
in this version of the specification are:
“alg”: ECDH-ES
“apv”: SDK Reference Number
“epk”: QC, in JSON Web Key (JWK) format
{“kty”:”EC” “crv”:“P-256”}
All other parameters: not present
CEK: “kty”:oct - 256 bits
Create a JSON object of the following data as the JWS payload to be
signed:
{“MyPublicKey”: “QT”, “SDKPublicKey”:” QC”}
Generate a digital signature of the full JSON object according to JWS
(RFC 7515) using JWS Compact Serialization. The parameter values
supported in this version of the specification are:
“alg”: PS256 or ES256
“x5c”: X.5C v3: Cert(MyPb) plus optionally chaining certificates
From my understanding, ECDH will produce a secret key. After sharing my ephemeral public key (QT), the SDK produces the same secret key, so we can later exchange JWE messages encrypted with the same secret key.
The JSON {“MyPublicKey”: “QT”, “SDKPublicKey”:” QC”} will be signed and sent, but I do not understand how I will use apv and epk since these header params are used in JWE and not in the first JWS to be shared.
On the same specification, they talk about these JWE messages, but they do not have these apv and epk parameters.
Encrypt the JSON object according to JWE (RFC 7516) using the same
“enc” algorithm used by the SDK, the CEK obtained identified by “kid”
and JWE Compact Serialization. The parameter values supported in this
version of the specification are:
“alg”: dir
“enc”: either A128CBC-HS256 or A128GCM
“kid”: Transaction ID
All other parameters: not present
I also read the example in RFC 7518 where I can see the header params apv and epk being used but I'm not sure which header params, JWE's or JWS's ?
Any thought on how this could be implemented using nimbus-jose-jwt or any other java library would be really helpful. Thanks
Both apv (Agreement PartyVInfo) and epk (Ephemeral Public Key) are optional, so they can be used in multiple ways. You can use apv to reflect SDK version for example. They are added to the JWE header.
You can read more about JWE in Nimbus here
An example using Nimbus JOSE would be:
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.nimbusds.jose.*;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
public class Security {
public void generateJWE() throws JOSEException, URISyntaxException {
JWEHeader jweHeader = buildJWEHeader(new Base64URL("202333517"), buildECKey());
JWEObject jweObject = new JWEObject(jweHeader, new Payload("Hello World!"));
}
private JWEHeader buildJWEHeader(Base64URL apv, ECKey epk) {
JWEHeader.Builder jweBuilder = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A128GCM);
jweBuilder.agreementPartyVInfo(apv);
jweBuilder.ephemeralPublicKey(epk);
return jweBuilder.build();
}
private ECKey buildECKey() throws URISyntaxException {
Set<KeyOperation> keyOperations = new HashSet<>();
keyOperations.add(KeyOperation.ENCRYPT);
String transactionID = "73024831";
URI x5u = new URI("https//website.certificate");
KeyStore keystore = null; //initialize it
List<Base64> x5c = new ArrayList<>();
return new ECKey(Curve.P_256, new Base64URL("x"), new Base64URL("y"), KeyUse.ENCRYPTION, keyOperations, Algorithm.NONE, transactionID, x5u, new Base64URL("x5t"), new Base64URL("x5t256"), x5c, keystore);
}
}
Instead of EncryptionMethod.A128GCM you can use EncryptionMethod.A128CBC-HS256 as in your specification. apv and epk are added to JWEHeader inside builder.
Other parameters can be chosen in constructors of JWEHeader.Builder and ECKey. I used ECDH-ES algorithm, A128GCM encryption method, P-256 curve (elliptic curve is default in ECKey generation),
transaction ID is a string. I chose other parameters without any clear pattern. Initialization of KeyStore would be too broad for the example. Encryption is only one thing you can do with JWE, among signature and others.
Nimbus (as well as Jose4j) is not the best choice for implementing the specification (I guess it's 3D secure 2.x.x).
These libraries do not return content encrypion key but use it to encrypt or decrypt messages as per JWE spec.
I found that Apache CXF Jose library does its job well as a JWE/JWS/JWK implementation. Except generating ephemeral key pairs. But it's can easily be done with Bouncy Castle:
Security.addProvider(new BouncyCastleProvider());
ECGenParameterSpec ecGenSpec = new ECGenParameterSpec(JsonWebKey.EC_CURVE_P256);
KeyPairGenerator g = KeyPairGenerator.getInstance(ALGORITHM_SIGNATURE_EC, "BC");
g.initialize(ecGenSpec, new SecureRandom());
KeyPair keyPair = g.generateKeyPair();
Content encrypion key can be produced with this code:
byte[] cek = JweUtils.getECDHKey(privateKey, publicKey, null, SDKReferenceNumber, "", 256);
And used to encrypt messages getting JWE compact representation:
JweEncryption jweEncryption = JweUtils.getDirectKeyJweEncryption(cek, contentAlgorithm);
JweHeaders head = new JweHeaders();
head.setHeader(JoseConstants.HEADER_KEY_ID, keyId);
String encrypted = jweEncryption.encrypt(contentToEncrypt.getBytes(StandardCharsets.UTF_8), head);
Or decrypt:
JweCompactConsumer compactConsumer = new JweCompactConsumer(encrypted);
JweHeaders head = compactConsumer.getJweHeaders();
JweDecryption jweDecryption = JweUtils.getDirectKeyJweDecryption(cek, head.getContentEncryptionAlgorithm());
JweDecryptionOutput out = jweDecryption.decrypt(encrypted);
String decrypted = out.getContentText();
I able to generate the Secret Key for A128CBC-HS256. However still not succeed in A128GCM. Adding the working sample for A128CBC-HS256 with help nimbus-jose library. This below code is only for key generation.
Please also note that I override the nimbus-jose classes for my usage. Hope it helps.
private void generateSHA256SecretKey(AREQ areq, ECKey sdkPubKey, KeyPair acsKeyPair) throws Exception {
// Step 4 - Perform KeyAgreement and derive SecretKey
SecretKey Z = CustomECDH.deriveSharedSecret(sdkPubKey.toECPublicKey(), (ECPrivateKey)acsKeyPair.getPrivate(), null);
CustomConcatKDF concatKDF = new CustomConcatKDF("SHA-256");
String algIdString = "";
String partyVInfoString = areq.getSdkReferenceNumber();
int keylength = 256; //A128CBC-HS256
byte[] algID = CustomConcatKDF.encodeDataWithLength(algIdString.getBytes(StandardCharsets.UTF_8));
byte[] partyUInfo = CustomConcatKDF.encodeDataWithLength(new byte[0]);
byte[] partyVInfo = CustomConcatKDF.encodeDataWithLength(partyVInfoString.getBytes(StandardCharsets.UTF_8));
byte[] suppPubInfo = CustomConcatKDF.encodeIntData(keylength);
byte[] suppPrivInfo = CustomConcatKDF.encodeNoData();
SecretKey derivedKey = concatKDF.deriveKey(
Z,
keylength,
algID,
partyUInfo,
partyVInfo,
suppPubInfo,
suppPrivInfo);
System.out.println("Generated SHA256 DerivedKey : "+SecureUtils.bytesToHex(derivedKey.getEncoded()));
}
I need to implement AES Encryption Algorithm in Cryptographic Message Syntax (CMS) standard to encrypt my data in Windows Universal App (found reference here). I have it implemented on Java using Bouncy Castle library using the following code(I need the same functionality in C# UWP):
private static final ASN1ObjectIdentifier CMS_ENCRYPTION_ALGO = CMSAlgorithm.AES256_CBC;
private byte[] encrypt(byte[] key, byte[] dataToBeEncrypted) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, CMSException {
final KeySpec keySpec = new X509EncodedKeySpec(key);
final KeyFactory factory = KeyFactory.getInstance("RSA");
final PublicKey publicKey = factory.generatePublic(keySpec);
final SubjectKeyIdentifier subjectKeyIdentifier = new JcaX509ExtensionUtils().createSubjectKeyIdentifier(publicKey);
final RecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(subjectKeyIdentifier.getEncoded(), publicKey);
final CMSEnvelopedDataGenerator generator = new CMSEnvelopedDataGenerator();
generator.addRecipientInfoGenerator(recipientInfoGenerator);
final OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder(CMS_ENCRYPTION_ALGO).build();
final CMSProcessableByteArray content = new CMSProcessableByteArray(dataToBeEncrypted);
final CMSEnvelopedData envelopedData = generator.generate(content, encryptor);
return envelopedData.toASN1Structure().getEncoded(ASN1Encoding.DER);
}
Now I have Referenced Bouncy Castle V 1.8.1 in my UWP App, but I found many differences (some libraries used in Java but not exist in Windows) and couldn't implement such functionality in C#.
So kindly either guide me to implement the same using native UWP Cryptography Library Windows.Security.Cryptography (Preferred),
Or tell me how can I implement same functionality using Bouncy Castle 1.8.1 in C# UWP app.
Update:
Based on the following diagram from here, I understand that the required steps are:
1- Get the data and generate Symmetric Key to encrypt the data using algorithm AesCbcPkcs7.
2- Encrypt Symmetric Key using the public key
3- Generate Digitally enveloped message.
So I did the first two steps based on my understanding using the following c# code (Please correct me if I'm wrong) and I need help to do the third step:
public string EncryptAndEnvelope(string openText, string p_key)
{
// Step 1 Get the data and generate Symmetric Key to encrypt the data using algorithm AesCbcPkcs7
IBuffer cBuffer = CryptographicBuffer.GenerateRandom(32);
SymmetricKeyAlgorithmProvider provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);
CryptographicKey m_key = provider.CreateSymmetricKey(cBuffer);
IBuffer bufferMsg = CryptographicBuffer.ConvertStringToBinary(AsciiToString(StringToAscii(openText)), BinaryStringEncoding.Utf8);
IBuffer bufferEncrypt = CryptographicEngine.Encrypt(m_key, bufferMsg, null);
// Step 2 Encrypt Symmetric Key using the public key
IBuffer publicKey = CryptographicBuffer.DecodeFromBase64String(p_key);
AsymmetricKeyAlgorithmProvider asym = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
CryptographicKey ckey = asym.ImportPublicKey(publicKey, CryptographicPublicKeyBlobType.X509SubjectPublicKeyInfo);
IBuffer cbufferEncrypt = CryptographicEngine.Encrypt(ckey, cBuffer, null);
// Step 3 Generate Digitally enveloped message
// I need help here
}
private byte[] StringToAscii(string s)
{
byte[] retval = new byte[s.Length];
for (int ix = 0; ix < s.Length; ++ix)
{
char ch = s[ix];
if (ch <= 0x7f) retval[ix] = (byte)ch;
else retval[ix] = (byte)'?';
}
return retval;
}
private string AsciiToString(byte[] bytes)
{
return string.Concat(bytes.Select(b => b <= 0x7f ? (char)b : '?'));
}
Note: While I was looking for solution, I've found that the answer is available using the library System.Security.Cryptography
(but it is not supported in Universal Apps) and I'm pretty sure that
the implementation is available using Bouncy Castle (there are
tons of documentation for Java but unfortunately there is no
documentation at all for C#).
OK, this is another of those "I have no real idea where to start" questions, so hopefully the answer is simple. However, I don't really know what to search for, and my attempts so far haven't turned up much of use.
I want to read a private key from a (currently on-disk) file. Ultimately the key will reside in a database, but this will be good enough for the moment and that difference should have no real bearing on parsing the key material. I have been able to create a Credential instance that holds the public part of the key (confirmed by debugger), but I can't seem to figure out how to read the private part. The key pair was generated as:
openssl genrsa 512 > d:\host.key
openssl req -new -x509 -nodes -sha1 -days 365 -key d:\host.key > d:\host.cert
(Yes, I know that 512 bit RSA keys were broken long ago. However, for trying to get the API to work, I see no reason to exhaust the system entropy supply needlessly.)
The code thus far is:
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.x509.BasicX509Credential;
private Credential getSigningCredential()
throws java.security.cert.CertificateException, IOException {
BasicX509Credential credential = new BasicX509Credential();
credential.setUsageType(UsageType.SIGNING);
// read public key
InputStream inStream = new FileInputStream("d:\\host.cert");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
credential.setEntityCertificate(cert);
// TODO: read private key
// done.
return credential;
}
But how do I read the file host.key into the private key portion of credential, so I can use the generated Credential instance to sign data?
BasicX509Credential is not part from standard Java; I suppose you are talking about org.opensaml.xml.security.x509.BasicX509Credential from OpenSAML.
You want a PrivateKey which you will set with credential.setPrivateKey(). To get a PrivateKey, you must first convert the private key into a format that Java can read, namely PKCS#8:
openssl pkcs8 -topk8 -nocrypt -outform DER < D:\host.key > D:\host.pk8
Then, from Java:
RandomAccessFile raf = new RandomAccessFile("d:\\host.pk8", "r");
byte[] buf = new byte[(int)raf.length()];
raf.readFully(buf);
raf.close();
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(buf);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privKey = kf.generatePrivate(kspec);
and voilà! you have your PrivateKey.
By default, openssl writes key in its own format (for RSA keys, PKCS#8 happens to be a wrapper around that format), and it encodes them in PEM, which Base64 with a header and a footer. Both characteristics are unsupported by plain Java, hence the conversion to PKCS#8. The -nocrypt option is because PKCS#8 supports optional password-based encryption of private key.
Warning: you really really want to use a longer RSA key. 512 bits are weak; a 512-bit RSA key was broken in 1999 with a few hundred computers. In 2011, with 12 years of technological advances, one should assume that a 512-bit RSA key can be broken by almost anybody. Therefore, use 1024-bit RSA keys at least (preferably, 2048-bit; the computational overhead when using the key is not that bad, you will still be able to perform hundred of signatures per second).
This question is related to SAML and is also relevant for someone who wants to retrieve a private key for signing an XMLObject. The answer above works great and it also possible to retrieve a private key from a keystore as well:
Properties sigProperties = new Properties();
sigProperties.put("org.apache.ws.security.crypto.provider","org.apache.ws.security.components.crypto.Merlin");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.type","jks");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.password","keypass");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.alias","keyalias");
sigProperties.put("org.apache.ws.security.crypto.merlin.keystore.file","keystore.jks");
Crypto issuerCrypto = CryptoFactory.getInstance(sigProperties);
String issuerKeyName = (String) sigProperties.get("org.apache.ws.security.crypto.merlin.keystore.alias");
//See http://ws.apache.org/wss4j/xref/org/apache/ws/security/saml/ext/AssertionWrapper.html 'signAssertion' method
// prepare to sign the SAML token
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
cryptoType.setAlias(issuerKeyName);
X509Certificate[] issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
if (issuerCerts == null) {
throw new WSSecurityException(
"No issuer certs were found to sign the SAML Assertion using issuer name: "
+ issuerKeyName);
}
String password = ADSUnitTestUtils.getPrivateKeyPasswordFromAlias(issuerKeyName);
PrivateKey privateKey = null;
try {
privateKey = issuerCrypto.getPrivateKey(issuerKeyName, password);
} catch (Exception ex) {
throw new WSSecurityException(ex.getMessage(), ex);
}
BasicX509Credential signingCredential = new BasicX509Credential();
signingCredential.setEntityCertificate(issuerCerts[0]);
signingCredential.setPrivateKey(privateKey);
signature.setSigningCredential(signingCredential);
This is more code than the original query requested, but it looks they are trying to get at a BasicX509Credential.
Thanks,
Yogesh