I'm trying to make use of unofficial API documentation. I need to sign all of the requests with my own certificate, but the docs came up only with Java code to use, here is it:
public class EncryptionUtils {
private static final String ALGORITHM_NAME = "SHA1withRSA";
private static final String CERT_TYPE = "pkcs12";
private static final String CONTAINER_NAME = "LoginCert";
private static final String PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06";
public static String signContent(byte[] contents, final InputStream cert) throws IOException, GeneralSecurityException, NullPointerException {
final KeyStore instance = KeyStore.getInstance(CERT_TYPE);
instance.load(cert, PASSWORD.toCharArray());
final PrivateKey privateKey = (PrivateKey) instance.getKey(CONTAINER_NAME, PASSWORD.toCharArray());
final Signature instance2 = Signature.getInstance(ALGORITHM_NAME);
instance2.initSign(privateKey);
instance2.update(contents);
return Base64.getEncoder().encodeToString(instance2.sign());
}}
I came up with this code
private static string password = "CE75EA598C7743AD9B0B7328DED85B06";
public static string Sign(string text, string cert)
{
X509Certificate2 certificate = new X509Certificate2(DecodeCrt(cert), password, X509KeyStorageFlags.Exportable);
RSA provider = (RSA)certificate.PrivateKey;
// Hash the data
var hash = HashText(text);
// Sign the hash
var signature = provider.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
public static byte[] HashText(string text)
{
SHA1Managed sha1Hasher = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1Hasher.ComputeHash(data);
return hash;
}
public static byte[] DecodeCrt(string crt)
{
return Convert.FromBase64String(crt);
}
but the output differs from the Java's version.
I've tried to temporarily run java task from c# so I would know if it even works, and it is.
Is there any way to write this in c#?
Both codes use for the signature RSA with Pkcs#1 v1.5 padding and SHA1, which is specified in the Java code via SHA1withRSA and in the C# code explicitly in the RSA#SignHash method via the 2nd and 3rd parameter. Since the Pkcs#1 v1.5 variant for signatures (RSASSA-PKCS1-v1_5) is deterministic, both codes must provide the same signature (assuming the same data to be signed and the same private key).
If this isn't the case, there are actually only two reasonable explanations:
First, the encoding used: In the Java code, the data are transferred as byte[], so that it's not possible to determine which encoding is used based on the posted code. In the C# code, in contrast, the data are passed as a string and converted into a byte[] in HashText. For this the standard-ctor of UnicodeEncoding is used, which applies UTF16LE and a byte order mark (BOM). For UTF8 (without BOM) UTF8Encoding would have to be used. It's crucial that the same encoding is applied in the C# code as in the Java code, otherwise different signatures are usually generated.
Second, the pfx/p12 file: pfx/p12 files can contain several certificates. In the Java code, KeyStore#getKey references the private key with its alias (the same applies to the certificate), in the C# code, X509Certificate2(Byte[], String, X509KeyStorageFlags) references the first certificate in the container, see also here. To ensure that the same certificate/key is referenced in both codes, the pfx/p12 file may therefore contain only exactly one certificate including the corresponding private key.
If this is taken into account, both codes generate the same signature on my machine (assuming the same data to be signed and the same pfx/p12 file).
Finally, it should be noted that in both codes cert is used for different objects. In the Java code, it denotes the InputStream, in the C# code the Base64 encoded binary data of the pfx/pf12 file.
Related
As such, the following code seems to correctly generate a PGP keyring with EC keys (as in: it can be parsed with Bouncycastle). However, both Thunderbird and GnuPG do have issues with it. Here is the code, which is based on an already working RSA/Elgamal based implementation of mine (I included only the relevant methods for the sake of readability):
private static final Provider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
private static final ASN1ObjectIdentifier CURVE_OID = CryptlibObjectIdentifiers.curvey25519;
private static final int MASTER_KEY_ALGORITHM = ECDSA;
private static final int SUB_KEY_ALGORITHM = ECDH;
private static final int MASTER_KEY_FLAGS = AUTHENTICATION | CERTIFY_OTHER | SIGN_DATA | ENCRYPT_STORAGE | ENCRYPT_COMMS;
private static final int SUB_KEY_FLAGS = ENCRYPT_COMMS | ENCRYPT_STORAGE;
private static final int[] PREFERRED_HASH_ALGORITHMS = {SHA256, SHA1, SHA384, SHA512, SHA224};
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = {AES_256, AES_192, AES_128};
public PGPKeyRingGenerator createPGPKeyRingGenerator(String identity, String passphrase, int keySize)
throws PGPException, InvalidAlgorithmParameterException {
PGPKeyPair masterKeyPair = generateEcPgpKeyPair(MASTER_KEY_ALGORITHM);
PGPKeyPair subKeyPair = generateEcPgpKeyPair(SUB_KEY_ALGORITHM);
PGPDigestCalculator sha1Calc = new BcPGPDigestCalculatorProvider().get(SHA1);
PGPDigestCalculator sha256Calc = new BcPGPDigestCalculatorProvider().get(SHA256);
PGPSignatureSubpacketVector masterKeySubPacket = generateMasterkeySubpacket(MASTER_KEY_FLAGS);
PGPSignatureSubpacketVector subKeySubPacket = generateSubkeySubpacket(SUB_KEY_FLAGS);
PGPKeyRingGenerator keyRingGenerator =
new PGPKeyRingGenerator(POSITIVE_CERTIFICATION, masterKeyPair, identity, sha1Calc, masterKeySubPacket,
null,
new JcaPGPContentSignerBuilder(masterKeyPair.getPublicKey().getAlgorithm(), SHA256)
.setProvider(BOUNCY_CASTLE_PROVIDER),
new JcePBESecretKeyEncryptorBuilder(AES_256, sha256Calc)
.setProvider(BOUNCY_CASTLE_PROVIDER)
.build(passphrase.toCharArray()));
keyRingGenerator.addSubKey(subKeyPair, subKeySubPacket, null);
return keyRingGenerator;
}
private AsymmetricCipherKeyPair generateEcKeyPair(ASN1ObjectIdentifier curveOid) {
X9ECParameters curve = CustomNamedCurves.getByOID(curveOid);
ECNamedDomainParameters ecDomainParameters = new ECNamedDomainParameters(curveOid, curve.getCurve(), curve.getG(), curve.getN(), curve.getH(), curve.getSeed());
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.init(new ECKeyGenerationParameters(ecDomainParameters, new SecureRandom()));
return keyPairGenerator.generateKeyPair();
}
private BcPGPKeyPair generateEcPgpKeyPair(int algorithm)
throws InvalidAlgorithmParameterException, PGPException {
return new BcPGPKeyPair(algorithm, generateEcKeyPair(CURVE_OID), new Date());
}
private PGPSignatureSubpacketVector generateMasterkeySubpacket(int keyFlags) {
PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
subpacketGen.setKeyFlags(false, keyFlags);
subpacketGen.setPreferredSymmetricAlgorithms(false, PREFERRED_SYMMETRIC_ALGORITHMS);
subpacketGen.setPreferredHashAlgorithms(false, PREFERRED_HASH_ALGORITHMS);
return subpacketGen.generate();
}
private PGPSignatureSubpacketVector generateSubkeySubpacket(int keyFlags) {
PGPSignatureSubpacketGenerator subpacketGen = new PGPSignatureSubpacketGenerator();
subpacketGen.setKeyFlags(false, keyFlags);
return subpacketGen.generate();
}
So, what do I get here and what are the issues:
When I pick SHA1 as hashing alg. for the master key, GnuPG actually does read the keyring, but the subkey is missing the "E" (encryption) key capability (and also we get the perfectly correct warning about SHA1 being insufficient):
>> gpg ./ec-sha1.asc
gpg: WARNING: no command supplied. Trying to guess what you mean ...
gpg: ECDSA key 8DF084241E957FFF requires a 256 bit or larger hash (hash is SHA1)
gpg: ECDSA key 8DF084241E957FFF requires a 256 bit or larger hash (hash is SHA1)
pub cv25519 2021-09-17 [SCA]
553E322AB50692F67E23FE7B8DF084241E957FFF
uid Foo Bar <foo#bar.loc>
sub cv25519 2021-09-17 []
With SHA256, the keyring cannot be parsed at all:
>> gpg ./ec.asc
gpg: WARNING: no command supplied. Trying to guess what you mean ...
gpg: Fatal: _gcry_mpi_ec_add_points: Montgomery not yet supported
Can anyone spot the issue with the code, so that both GnuPG and Thunderbird will be able to correctly parse the keyring?
If you are fine with using Bouncy Castle via a wrapper, then PGPainless is your friend.
Generating keys with PGPainless boils down to
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("Foo Bar <foo#bar.loc>", "passphrase");
(shameless plug, author here).
Turns out, the keyring generation as such is alright -- the issue was with the chosen curve resp. the master key generation. To fix the key generation:
One either can keep the code as is and pick a different curve which works with the ECDSA scheme e.g. secp256r1 instead of Curve25519:
private static final ASN1ObjectIdentifier CURVE_OID = SECObjectIdentifiers.secp256r1;
However, if Curve25519 is to be used (N.B. which is a good choice, for further information please refer to https://safecurves.cr.yp.to/), then we have to use EdDSA with Ed25519 (see: RFC 8032) for the master key. The generation of the subkey/encryption key, however, stays the same. Ed25519 is an EdDSA (as opposed to ECDSA) signature scheme with edwards25519 as the curve. To implement it the correct way, two things about the code above have to be changed:
private static final int MASTER_KEY_ALGORITHM = PublicKeyAlgorithmTags.EDDSA;
private static final ASN1ObjectIdentifier MASTER_CURVE_OID = EdECObjectIdentifiers.id_Ed25519;
private static final ASN1ObjectIdentifier SUB_CURVE_OID = CryptlibObjectIdentifiers.curvey25519;
And the master key pair generation, which is as follows:
public AsymmetricCipherKeyPair generateEd25519KeyPair() {
Ed25519KeyPairGenerator keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.init(new Ed25519KeyGenerationParameters(new SecureRandom()));
return keyPairGenerator.generateKeyPair();
}
I am using secp256k1 to sign hash of a text in server and verify the signature on client side .
Now, I need to send actual text as well to the client but my main constraint is bandwidth and I can't add text separately. Therefore, I need to be able to put the actual text inside signature and extract it during verification?
Here is my code
Signing
static final X9ECParameters curve = SECNamedCurves.getByName ("secp256k1");
static final ECDomainParameters domain = new ECDomainParameters(curve.getCurve (), curve.getG (), curve.getN (), curve.getH ());
public byte[] sign (byte[] hash) throws CryptoException
{
if ( priv == null )
{
throw new CryptoException (ErrorCode.KEY_NOT_FOUND, "Need private key to sign");
}
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
signer.init (true, new ECPrivateKeyParameters(priv, domain));
BigInteger[] signature = signer.generateSignature (hash);
ByteArrayOutputStream s = new ByteArrayOutputStream();
try
{
DERSequenceGenerator seq = new DERSequenceGenerator(s);
seq.addObject (new ASN1Integer(signature[0]));
seq.addObject (new ASN1Integer(signature[1]));
seq.close ();
return s.toByteArray ();
}
catch ( IOException e )
{
}
return null;
}
Verification
public static boolean verify (byte[] hash, byte[] signature, byte[] pub)
{
ASN1InputStream asn1 = new ASN1InputStream(signature);
try
{
ECDSASigner signer = new ECDSASigner();
signer.init (false, new ECPublicKeyParameters(curve.getCurve ().decodePoint (pub), domain));
DLSequence seq = (DLSequence) asn1.readObject ();
BigInteger r = ((ASN1Integer) seq.getObjectAt (0)).getPositiveValue ();
BigInteger s = ((ASN1Integer) seq.getObjectAt (1)).getPositiveValue ();
return signer.verifySignature (hash, r, s);
}
catch ( Exception e )
{
// threat format errors as invalid signatures
return false;
}
finally
{
try
{
asn1.close ();
}
catch ( IOException e )
{
}
}
}
ECDSA requires a hash over the message to be secure. So all information is lost. Some signature schemes use a hash + part of the message. This is for instance the case for RSA in the ISO/IEC 9796 signature schemes giving (partial) message recovery - which is the technical term of what you (and Dave Thompson) are talking about.
However ECDSA signatures are just big enough to contain a single hash (and usually not even that); there is not much you can do to add data to the hash value. Trying to use EC signatures for partial message recovery is an exercise in futility (also because how verification is performed).
However if you want to use fewer bits you can do still things:
use the most dense representation of message and signature (e.g. just R and S concatenated for ECDSA signatures);
use the smallest key size possible (check https://keylength.com/ for info);
switch to a signature scheme that uses smaller signatures, for instance the BLS signature scheme over elliptic curves - it will halve the signature size compared to ECDSA (using the most efficient encoding).
I've put the BLS scheme last because it is usually not in generic libraries; I consider it the expert way out. You may hit a learning curve.
Signatures themselves don't compress well, by the way, unless you encode them badly. The other disadvantage of compression is that it is usually tricky to calculate a maximum size for all possible messages.
A digital signature, ECDSA or RSA, generally doesn't contain the message (see #dave_thompson_085 comment). An "attached signature" refers to a file that basically contains the message and the digital signature, which can be encoded in a CMS or XMLDsig format.
So if you need to send the message and the signature, just send them together, using a custom or a known format, and compress it additionally to reduce the size
AttachedSignature = zip(format(message + signature))
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()));
}
Public key is generated into WinRT application using the code below
AsymmetricKeyAlgorithmProvider asymmetricKeyAlgorithmProvider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.get_RsaPkcs1());
CryptographicKey cryptographicKey = asymmetricKeyAlgorithmProvider.CreateKeyPair(1024u);
IBuffer buffer = cryptographicKey.Export(3);
IBuffer buffer2 = cryptographicKey.ExportPublicKey(3);
byte[] inArray;
CryptographicBuffer.CopyToByteArray(buffer, ref inArray);
byte[] inArray2;
CryptographicBuffer.CopyToByteArray(buffer2, ref inArray2);
CommonMethods.PrivateKey = Convert.ToBase64String(inArray);
CommonMethods.PublicKey = Convert.ToBase64String(inArray2);
In ExportPublic Key 3 refers to CapiPublicKey. If I want to use the base64 encode public key string into java application how can I do that.
Java do not have any thing like CapiPublic Key.
I could not find any direct method to do that. I first converted CapiPublicKey to X509 public key supported by java in WinRT itself.
then I used the generated public key into java.
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.