How to find the matching curve name from an ECPublicKey - java

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();
}

Related

Java Bouncy Castle : Invalid point encoding 0x45

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)

How to create an OpenSSH compatible ED25519 key with Bouncy Castle?

How can you create an OpenSSH ED25519 private key that can be used for SSH? The goal would be to have a key file in the same format same like you would have in .ssh/id_ed25519 for your OpenSSH client.
This is my current approach, which does not create a compatible:
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.OpenSSHPrivateKeyUtil;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.StringWriter;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
public class Test {
static {
Security.removeProvider("BC");provider
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
public static String createCurve25519PEM() {
try {
X9ECParameters curveParams = CustomNamedCurves.getByName("Curve25519");
ECParameterSpec ecSpec = new ECParameterSpec(curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH(), curveParams.getSeed());
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
kpg.initialize(ecSpec);
KeyPair keypair = kpg.generateKeyPair();
AsymmetricKeyParameter akp = PrivateKeyFactory.createKey(keypair.getPrivate().getEncoded());
byte[] content = OpenSSHPrivateKeyUtil.encodePrivateKey(akp);
PemObject o = new PemObject("OPENSSH PRIVATE KEY", content);
StringWriter sw = new StringWriter();
PemWriter w = new PemWriter(sw);
w.writeObject(o);
w.close();
Log.d("createCurve25519PEM", "key: " + sw.toString());
return sw.toString();
} catch (Exception e) {
Log.d("createCurve25519PEM", e.toString());
}
return null;
}
}
The output looks like this:
-----BEGIN OPENSSH PRIVATE KEY-----
MIIBTwIBAQQgA8BjYjSjUgM4PahSZQx3i9DWcEdGiGnBoA0tXCUENzKggeEwgd4C
AQEwKwYHKoZIzj0BAQIgf////////////////////////////////////////+0w
RAQgKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmEkUoUQEIHtCXtCXtCXtCXtC
XtCXtCXtCXtCXtCXtCYLXpx3EMhkBEEEKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
qqqqqqqtJFogrhmhuKCGtOAe3Sx3SNFMkj1Nfm18YbIp6cWifs7T2QIgEAAAAAAA
AAAAAAAAAAAAABTe+d6i95zWWBJjGlz10+0CAQihRANCAARl0Kc+dO0Er1dpu6mh
/lZmTw3/DMKPLTzjosX2u7hQswV+U9o0WOYFd1JOqsGdkLfYuGmdZzWdk74dvV1O
+w5T
-----END OPENSSH PRIVATE KEY-----
.. but is unfortunately not accepted by SSH.
Using "Curve25519" that way gives it to you in the short Weierstrass form used by X9; this is not usable for Ed25519, which is defined to use twisted-Edwards form. Moreover cramming it through JCA's unfortunate ECParameterSpec class gives the original X9-defined 'explicit' representation which is now obsolete and almost never used even for algorithms which do use Weierstrass curves. As a result the data you create is not correct for PEM type OPENSSH PRIVATE KEY; it is valid for OpenSSL's 'traditional' PEM type EC PRIVATE KEY, and OpenSSL (which does still support, though not prefer, "param_enc explicit") is able to read that content with that type, though it can't be used to interoperate with anything else.
You need to either use JCA with algorithm "ED25519" (not "EC" "ECDSA" "ECDH" "ECMQV" etc which are all X9/SECG) something like this:
KeyPair pair = KeyPairGenerator.getInstance("ED25519","BC") .generateKeyPair();
AsymmetricKeyParameter bprv = PrivateKeyFactory.createKey(pair.getPrivate().getEncoded());
// then proceed as you already have; I have simplified for my test environment
byte[] oprv = OpenSSHPrivateKeyUtil.encodePrivateKey(bprv);
PemWriter w = new PemWriter(new OutputStreamWriter(System.out));
w.writeObject(new PemObject("OPENSSH PRIVATE KEY", oprv)); w.close();
// BTW if you pass an actual Provider to .getInstance (rather than a String, or letting it search)
// you don't actually need to have put that Provider in the searchlist
// Also in Java 15 up you can use the SunEC provider (normally searched by default)
// but since you still need other Bouncy pieces why bother
or since you're already dependent on Bouncy use the lightweight API:
AsymmetricCipherKeyPairGenerator gen = new Ed25519KeyPairGenerator();
gen.init(new KeyGenerationParameters(new SecureRandom(), 255));
AsymmetricKeyParameter bprv = gen.generateKeyPair().getPrivate();
// ditto

Create CipherOutputStream with PGP Bouncy Castle dependency

I want to create a OutputStream from another OutputStream in which the new OutputStream will automatically encrypt the content I write to that OutputStream. I want to use Bouncy Castle since I am already using that dependency for other functionality.
I see various questions over the internet how to encrypt data with Bouncy Castle, but the answers either encrypt a given File (I don't use files, I use OutputStreams) or have a huge amount of code I need to copy paste. I can not believe it must be that difficult.
This is my setup:
I am using this Bouncy Castle dependency (V1.68)
I am using Java 8
I have a public and private key generated by https://pgpkeygen.com/. The algorithm is RSA and the keysize is 1024.
I saved the public key and private key as a file on my machine
I want to make sure the test below passes
I have some code commented out, the init function on Cipher (the code compiles, but the test fails). I don't know what I should put in as second argument in the init function.
The read functions are from: https://github.com/jordanbaucke/PGP-Sign-and-Encrypt/blob/472d8932df303d6861ec494a3e942ea268eaf25f/src/SignAndEncrypt.java#L272. Only the testEncryptDecryptWithoutSigning is writting by me.
Code:
#Test
void testEncryptDecryptWithoutSigning() throws Exception {
// The data will be written to this property
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Security.addProvider(new BouncyCastleProvider());
PGPSecretKey privateKey = readSecretKey(pathToFile("privatekey0"));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
//cipher.init(Cipher.ENCRYPT_MODE, privateKey);
CipherOutputStream os = new CipherOutputStream(baos, cipher);
// I also need to use a PrintWriter
PrintWriter printWriter =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(
os,
StandardCharsets.UTF_8.name())));
// This is an example of super secret data to write
String data = "Some very sensitive data";
printWriter.print(data);
printWriter.close();
// At this point, the data is 'inside' the byte array property
// Assert the text is encrypted
if (baos.toString(StandardCharsets.UTF_8.name()).equals(data)) {
throw new RuntimeException("baos not encrypted");
}
PGPSecretKey publicKey = readSecretKey(pathToFile("publickey0"));
//cipher.init(Cipher.DECRYPT_MODE, publicKey);
ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
// Decrypt the stream, but how?
if (!decrypted.toString(StandardCharsets.UTF_8.name()).equals(data)) {
throw new RuntimeException("Not successfully decrypted");
}
}
static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
{
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
Iterator keyRingIter = pgpSec.getKeyRings();
while (keyRingIter.hasNext())
{
PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();
Iterator keyIter = keyRing.getSecretKeys();
while (keyIter.hasNext())
{
PGPSecretKey key = (PGPSecretKey)keyIter.next();
if (key.isSigningKey())
{
return key;
}
}
}
throw new IllegalArgumentException("Can't find signing key in key ring.");
}
static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException
{
InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
PGPSecretKey secKey = readSecretKey(keyIn);
keyIn.close();
return secKey;
}
static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException
{
InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
PGPPublicKey pubKey = readPublicKey(keyIn);
keyIn.close();
return pubKey;
}
/**
* A simple routine that opens a key ring file and loads the first available key
* suitable for encryption.
*
* #param input data stream containing the public key data
* #return the first public key found.
* #throws IOException
* #throws PGPException
*/
static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
{
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
Iterator keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext())
{
PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
Iterator keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext())
{
PGPPublicKey key = (PGPPublicKey)keyIter.next();
if (key.isEncryptionKey())
{
return key;
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
As a preliminary, that website doesn't generate a keypair, but three. Historically in PGP there has long been some ambiguity between actual cryptographic keys and keypairs, and what PGP users call keys, because it is common for a given user (or entity or role etc) to have one 'master' or 'primary' key and one or more subkey(s) tied to that masterkey. For DSA+ElG keys it was technically necessary to use a subkey (and not the masterkey) for encryption; for RSA it is considered good practice to do so because it is often better to manage (e.g. potentially revoke) these keys separately. Some people also consider it good practice to use a subkey rather than the masterkey for signing data, and use the masterkey only for signing keys (which PGP calls certifying - C), but some don't. When PGP users and documents talk about a 'key' they often mean the group of a masterkey and (all) its subkey(s), and they say masterkey or subkey (or encryption subkey or signing subkey) to mean a specific actual key.
When you choose RSA that website generates a masterkey (keypair) with usage SCEA -- i.e. all purposes -- AND TWO subkeys each with usage SEA -- all purposes valid for a subkey. This is nonsensical; if the masterkey supports Signing and Encryption most PGP programs will never use any subkey(s), and even if it didn't or you override it, there is no meaningful distinction between the subkeys and no logical way to choose which to use.
And BouncyCastle exacerbates this by changing the terminology: most PGP programs use key for either an actual key or a group of masterkey plus subkeys as above, and 'public' and 'secret' key to refer to the halves of each key or group, and 'keyring' to refer to all the key group(s) you have stored, typically in a file, which might be for many different people or entities. Bouncy however calls the group of a masterkey with its subkeys (in either public or secret form) a KeyRing, and the file containing possibly multiple groups a KeyRingCollection, both of them in Public and Secret variants. Anyway ...
Your first problem is you have it backwards. In public key cryptography we encrypt with the public key (half) and decrypt with the private key (half) which PGP (and thus BCPG) calls secret. Further, because private/secret keys in PGP are password-encrypted, to use it we must first decrypt it. (The same is true in 'normal' JCA keystores like JKS and PKCS12, but not necessarily in others.)
Your second problem is the types. Although a (specific) PGP key for a given asymmetric algorithm is semantically just a key for that algorithm, plus some metadata (identity, preference, and trust/signature information), the Java objects (classes) in BCPG for PGP keys are not in the type hierarchy of the objects used for keys in Java Crypto Architecture (JCA). In simpler words, org.bouncycastle.openpgp.PGPPublicKey is not a subclass of java.security.PublicKey. So these key objects must be converted to JCA-compatible objects to be used with JCA.
With those changes and some additions, the following code works (FSVO work):
static void SO66155608BCPGPRawStream (String[] args) throws Exception {
byte[] plain = "testdata".getBytes(StandardCharsets.UTF_8);
PGPPublicKey p1 = null;
FileInputStream is = new FileInputStream (args[0]);
Iterator<PGPPublicKeyRing> i1 = new JcaPGPPublicKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
for( Iterator<PGPPublicKey> j1 = i1.next().getPublicKeys(); j1.hasNext(); ){
PGPPublicKey t1 = j1.next();
if( t1.isEncryptionKey() ){ p1 = t1; break; }
}
is.close();
if( p1 == null ) throw new Exception ("no encryption key");
PublicKey k1 = new JcaPGPKeyConverter().getPublicKey(p1);
Cipher c1 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c1.init(Cipher.ENCRYPT_MODE, k1);
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
CipherOutputStream s1 = new CipherOutputStream(b1,c1);
s1.write(plain);
s1.close();
byte[] cipher = b1.toByteArray();
long id = p1.getKeyID();
System.out.println("keyid="+Long.toString(id,16)+" "+Arrays.toString(cipher));
if( Arrays.equals(cipher,plain) ) throw new Exception ("didn't encrypt!");
PGPSecretKey p2 = null;
is = new FileInputStream (args[1]);
Iterator<PGPSecretKeyRing> i2 = new JcaPGPSecretKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
for( Iterator<PGPSecretKey> j2 = i2.next().getSecretKeys(); j2.hasNext(); ){
PGPSecretKey t2 = j2.next();
if( t2.getKeyID() == id ){ p2 = t2; break; }
}
is.close();
if( p2 == null ) throw new Exception ("no decryption key");
PGPPrivateKey p3 = p2.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().build(args[2].toCharArray()));
PrivateKey k2 = new JcaPGPKeyConverter().getPrivateKey(p3);
Cipher c2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c2.init(Cipher.DECRYPT_MODE, k2);
ByteArrayInputStream b2 = new ByteArrayInputStream(cipher);
CipherInputStream s2 = new CipherInputStream(b2,c2);
byte[] back = new byte[cipher.length]; // definitely more than needed
int actual = s2.read(back);
s2.close();
System.out.println ("Result->" + new String(back,0,actual,StandardCharsets.UTF_8));
}
(I find it clearer to have the code in one place in execution sequence, but you can break it out into pieces as you had it with no substantive change.)
I kept your logic (from Bouncy examples) of choosing the first encryption-capable public key either master or sub from the first group having one which per above Bouncy miscalls a KeyRing; since per above the website you used gives the masterkey SCEA this is always the masterkey. It isn't possible to similarly select a secret/private key depending on whether it allows encryption, and in any case there is no guarantee that the public key file will always be in the same order, so the correct way to choose the decryption key is to match the keyid from the key that was used for encryption.
Also, modern encryption algorithms (both asymmetric like RSA and symmetric like AES or '3DES') produce data that is arbitrary bit patterns, and in particular mostly NOT valid UTF-8, so 'decoding' those bytes as UTF-8 to compare to the plaintext is generally going to corrupt your data; if you want this (unnecessary) check you should instead compare the byte arrays as I show.
Finally, in case you don't know, asymmetric algorithms are not normally used to encrypt data of large or variable size, which is what you would normally use Java streams for; this is also explained in the wikipedia article. This approach, using RSA PKCS1-v1_5 directly, with a 1024-bit key as you have, can only handle 117 bytes of data (which may be fewer than 117 characters, depending).
And if you expect the result to be compatible or interoperable with any real PGP implementation, it definitely isn't -- which means the effort of converting from PGP key format is wasted, because you could have simply generated JCA-form keys directly in the first place, following the basic tutorials on the Oracle website or hundreds of examples here on Stack. If you want to interoperate with GPG or similar, you need to use the BCPG classes for PGP-format encryption and decryption, which can layer on plain byte streams, but are completely different from and incompatible with JCA's Cipher{Input,Output}Stream.

How to extend/add a Bouncycastle digest algorithm for CMS?

With Java I want to cryptographically sign given byte[] hash with RSA and return byte[] signature in the Cryptographic Message Syntax (CMS) format.
I am using the Bouncycastle Java API (BC) for this purpose and face the problem of the not existing NONEwithRSA signature type. (java.lang.IllegalArgumentException: Unknown signature type requested: NONEWITHRSA).
I, unfortunately, cannot change the input nor the required output format so I am required to extend/add to the existing BC algorithm so that I can use NONEwithRSA instead of all the other existing (e.g. SHA256withRSA). How do I do this? I have not found any example in the BC documentation.
My desired usage would be similar to this
byte[] doSigningCMS(byte[] data, X509Certificate cert, PrivateKey key) throws Exception {
CMSSignedDataGenerator signGen = new CMSSignedDataGenerator();
CMSTypedData content = new CMSProcessableByteArray(data);
ContentSigner signer = new JcaContentSignerBuilder("NONEwithRSA").build(key);
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().build();
signGen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(dcp).build(signer, cert));
return signGen.generate(content, false).getEncoded();
}
What I have tried so far
Adding my own provider for the JCA with "copying" a bunch of BC code and "fake" SHA256withRSA to basically return a NoneDigest instead of SHA256Digest. But this seems to be very wrong.
Adding my own algorithm to BC with ((BouncyCastleProvider) Security.getProvider("BC")).addAlgorithm(ALG_ALIAS, NoneDigest.class.getName()) which does not seem to work (algorithm is not found)
I hope for some advice and guidance to do this (if even possible) "the right way". Thanks.
"NoneWithRSA" is a RSA digital signature without adding the OID of the digest algorithm. The cryptographic provider does not make the digest either. Basically it is a PKCS#1_v15 encryption with the private key.
You can see how it works in this OpenJDK test https://github.com/ddopson/openjdk-test/blob/master/java/security/Signature/NONEwithRSA.java
Since Bouncycastle seems does not support it, I think you can supply your own ContentSigner implementation using the default provider instead of using JcaContentSignerBuilder
It is supposed that the input data is already hashed, so if you are doing a PKCS#1 signature, I think you need to provide the signature algorithm. Looking at the RFC3477 it depends on the hash algorithm used.
A.2.4 RSASSA-PKCS1-v1_5
The object identifier for RSASSA-PKCS1-v1_5 shall be one of the
following. The choice of OID depends on the choice of hash
algorithm: MD2, MD5, SHA-1, SHA-256, SHA-384, or SHA-512.
String sigAlgo = "SHA256WithRSAEncryption"; // "SHA256WithRSAEncryption" for SHA256, "SHA1WithRSAEncryption" for SHA1, etc.
ContentSigner signer = new CustomContentSigner(key, sigAlgo );
public class CustomContentSigner implements ContentSigner {
private AlgorithmIdentifier algorithmIdentifier;
private Signature signature;
private ByteArrayOutputStream outputStream;
public CustomContentSigner(PrivateKey privateKey, String sigAlgo) {
//Utils.throwIfNull(privateKey, sigAlgo);
this.algorithmIdentifier = new DefaultSignatureAlgorithmIdentifierFinder().find(sigAlgo);
try {
this.outputStream = new ByteArrayOutputStream();
this.signature = Signature.getInstance("NONEwithRSA");
this.signature.initSign(privateKey);
} catch (GeneralSecurityException gse) {
throw new IllegalArgumentException(gse.getMessage());
}
}
#Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return algorithmIdentifier;
}
#Override
public OutputStream getOutputStream() {
return outputStream;
}
#Override
public byte[] getSignature() {
try {
signature.update(outputStream.toByteArray());
return signature.sign();
} catch (GeneralSecurityException gse) {
gse.printStackTrace();
return null;
}
}
}
Disclaimer: I do not know if it will work but you can try it

Get public key from private in Java

I remember do this long time ago with OpenSSL, but I want to know if it's possible and how, I've never used Cryptography on java.
The assumption is that we are talking about RSA private and Public keys. Then, if you are working from a PEM format file, then first you need to read the private key from the file into a PrivateKey object:
public PrivateKey readPemRsaPrivateKey(String pemFilename) throws
java.io.IOException,
java.security.NoSuchAlgorithmException,
java.security.spec.InvalidKeySpecException
{
String pemString = File2String(pemFilename);
pemString = pemString.replace("-----BEGIN RSA PRIVATE KEY-----\n", "");
pemString = pemString.replace("-----END RSA PRIVATE KEY-----", "");
byte[] decoded = Base64.decodeBase64(pemString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
where File2String is something like:
private static String File2String(String fileName) throws
java.io.FileNotFoundException, java.io.IOException
{
File file = new File(fileName);
char[] buffer = null;
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
buffer = new char[(int)file.length()];
int i = 0;
int c = bufferedReader.read();
while (c != -1) {
buffer[i++] = (char)c;
c = bufferedReader.read();
}
return new String(buffer);
}
Now you can generate the corresponding PublicKey with code like this:
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPublicKeySpec;
...
PrivateKey myPrivateKey = readPemRsaPrivateKey(myPrivateKeyPemFileName);
RSAPrivateCrtKey privk = (RSAPrivateCrtKey)myPrivateKey;
RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(), privk.getPublicExponent());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey myPublicKey = keyFactory.generatePublic(publicKeySpec);
Credits: How to get a RSA PublicKey by giving a PrivateKey?
Please make sure that Eli Rosencruft answer is basically correct, but the order of the modulus and the public exponent are incorrect! This is the correct statement:
RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(), privk.getPublicExponent());
You cannot generate either key directly from the other. It is mathematically impossible. If you had a key blob that contained both the public and private keys, you could extract either one of them with relative ease.
EDIT, 2017: Many years and a much better understanding of crypto later, and it's now clear to me that this answer isn't really correct.
To quote Wikipedia:
The public key consists of the modulus n and the public (or encryption) exponent e. The private key consists of the modulus n and the private (or decryption) exponent d, which must be kept secret. p, q, and λ(n) must also be kept secret because they can be used to calculate d.
The public modulus n can be computed as p × q. The only thing missing from a raw private key is e, but this value is usually selected as 65537, and if not you can still compute e from d and λ(n).
However, many private key storage formats actually contain the public modulus n alongside the other components, so you can just do a direct extraction of the values.
EDIT, 2018: Still getting downvotes for this, and rightly so! I'm leaving this answer up so people can see why I was originally wrong, and to remind myself not to be wrong in future.

Categories

Resources