I have a RSA public key (2048 bit) generated by a HSM, that key has been saved in a file (with a size of 256 byte) and is encoded as DER.
Is it possibile to programmatically create a self-signed certificate using JDK API (without BouncyCastle) starting from that file?
I'm stuck with the first step, because I'm trying to load the key file to create a PublicKey object:
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import org.apache.commons.io.IOUtils;
public class Crypto {
public static void main(String[] args) throws Exception {
byte[] byteArray = IOUtils.toByteArray(new FileInputStream("/tmp/pub.key"));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(byteArray);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pub = kf.generatePublic(spec);
....
}
}
but I get this exception:
Exception in thread "main" java.security.spec.InvalidKeySpecException: Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys
at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:289)
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:184)
at java.security.KeyFactory.generatePublic(KeyFactory.java:304)
at org.alex.Crypto.main(Crypto.java:17)
Is there a way to do that?
Use X509EncodedKeySpec (which internally actually used PKCS#1 encoding for RSA keys) instead. Keep the rest of the code identical. PKCS#8 is for private keys, not public keys (because it uses the PKCS#8 internal structure required to wrap a key with another key, and wrapping public keys makes no sense).
The exception is telling you the issue! => Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys
You are trying to use PKCS8EncodedKeySpec, which is not supported, create RSAPublicKeySpec or X509EncodedKeySpec class
Example
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] input = new byte[] { (byte) 0xbe, (byte) 0xef };
Cipher cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
"12345678", 16), new BigInteger("11", 16));
RSAPrivateKeySpec privKeySpec = new RSAPrivateKeySpec(new BigInteger(
"12345678", 16), new BigInteger("12345678",
16));
RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(pubKeySpec);
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
Related
If I get the actual key with getEncoded from a public or a private key in Bouncy Castle in Java (actual class seems to be BCECPublicKey and BCECPrivateKey). Is it possible to reconstruct the key objects to use them in code?
I found out here in Stack Overflow how to serialize the whole object to binary (and then to disk) and then back to binary and to an object of the appropriate class, but I believe that serialization contains implementation details and if I try to use those keys with anything else than Bouncy Castle, it'll fail. I'm not trying to do that now, but I want to future-proof my program.
This is how I'm creating the keys:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp521r1"), new SecureRandom());
java.security.KeyPair keyPair = keyPairGenerator.generateKeyPair();
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
The KeyFactory is used to convert between encoded keys and the Java classes that represent them. However, the KeyFactory instance doesn't convert directly between a byte array and a Key class. Instead, you must already know what format the encoding uses, and then create a KeySpec object using the byte array in the constructor. The format can be determined by called the getFormat() method on the key. Here is an example illustrating some of these points.
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class Main {
public static void main(String[] args) throws Exception{
Security.addProvider(new BouncyCastleProvider());
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp521r1"), new SecureRandom());
java.security.KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
System.out.println(privateKey.getFormat());
PublicKey publicKey = keyPair.getPublic();
System.out.println(publicKey.getFormat());
// A KeyFactory is used to convert encoded keys to their actual Java classes
KeyFactory ecKeyFac = KeyFactory.getInstance("EC", "BC");
// Now do a round-trip for a private key,
byte [] encodedPriv = privateKey.getEncoded();
// now take the encoded value and recreate the private key
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(encodedPriv);
PrivateKey privateKey2 = ecKeyFac.generatePrivate(pkcs8EncodedKeySpec);
// And a round trip for the public key as well.
byte [] encodedPub = publicKey.getEncoded();
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(encodedPub);
PublicKey publicKey2 = ecKeyFac.generatePublic(x509EncodedKeySpec);
System.out.println(publicKey2);
}
}
i got problem Encrypting a string using RSA.
my RSA is in XML format, it looks like that:
<RSAKeyValue><Modulus>lT8ykfyV0R8o3mJZZezLKTKJpYB90Pzvp0moLzh9CTGfgsxLKYiAl+YGaoRfQ7hVQos5UlLIONHWKPNco9kKcmL6EBJvFc8wqBnhX0p4ML2WSv1yDIRsm9XXra82WHIa3+fxK8bNUJHrucxmpr9pDRPdZGZkz+Q9s94FcOyFKbs=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
and i'm trying to encrypt a string using this class:
import java.io.BufferedReader;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Security;
import javax.crypto.Cipher;
import org.bouncycastle.openssl.PEMReader;
import android.util.Base64;
import android.util.Log;
public class RsaEncryption {
private String publicKey;
public RsaEncryption(String publicKey)
{
this.publicKey = publicKey;
}
/*
* Function to encrypt the data.
*
*/
public String encrypt( String data ) throws Exception
{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
byte[] keyBytes = Base64.decode( this.publicKey, 0 );
PublicKey publickey = strToPublicKey(new String(keyBytes));
cipher.init( Cipher.ENCRYPT_MODE , publickey );
// Base 64 encode the encrypted data
byte[] encryptedBytes = Base64.encode( cipher.doFinal(data.getBytes()), 0 );
return new String(encryptedBytes);
}
public static PublicKey strToPublicKey(String s)
{
PublicKey pbKey = null;
try {
BufferedReader br = new BufferedReader( new StringReader(s) );
PEMReader pr = new PEMReader(br);
Object obj = pr.readObject();
if( obj instanceof PublicKey )
{
pbKey = (PublicKey) pr.readObject();
}
else if( obj instanceof KeyPair )
{
KeyPair kp = (KeyPair) pr.readObject();
pbKey = kp.getPublic();
}
pr.close();
}
catch( Exception e )
{
Log.d("CIPHER", e.getMessage() );
}
return pbKey;
}
}
as you can see i'm using bouncycastle's jar
the error that i get is:
java.security.InvalidKeyException: unknown key type passed to RSA
I'm not sure about this part
Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
maybe this is the problem?
if it is, what need to be there instead?
i did hours of research and still didn't find a solution...
Thanks in advance :)
Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
maybe this is the problem?
No it's not.
OAEPWith<digest>And<mgf>Padding
Means Optimal Asymmetric Encryption Padding scheme defined in PKCS1, where <digest> should be replaced by the message digest algorithm and <mgf> by the mask generation function. Examples: OAEPWithMD5AndMGF1Padding and OAEPWithSHA-512AndMGF1Padding.
Reference Standard Names and RFC 4055.
The problem is in your Public Key generation. As your key is in XML, and Base64 encoded:
First you need to separate modulus and exponent.
Then Base64 decode both modulus and exponent.
After decoding you will get the byte array of modulus and exponent, so you can easily prepare public key object like the following procedure:
BigInteger modBigInteger = new BigInteger(1, modulus);//modulus must be byte array
BigInteger exBigInteger = new BigInteger(1, exponent);//exp must be byte array
RSAPublicKeySpec spec = new RSAPublicKeySpec(modBigInteger, exBigInteger);
KeyFactory factory = KeyFactory.getInstance("RSA");
PublicKey publicKey = factory.generatePublic(spec);
XML is not PEM.
You need to extract the modulus and the public exponent from the XML and then generate a key using an "RSA" KeyFactory instance and a RSAPublicKeySpec.
I'm newer to RSA and used BC get SubjectPublicKeyInfo from a public key.
String key = "-----BEGIN RSA PUBLIC KEY-----\n" +
"........\n" +// Multiple lines here
"-----END RSA PUBLIC KEY-----\n";
Security.addProvider(new BouncyCastleProvider());
PEMParser reader = new PEMParser(new StringReader(key));
SubjectPublicKeyInfo subjectPublicKeyInfo = (SubjectPublicKeyInfo) reader.readObject();
And then I want encrypt datas. I found some one use RSAEngine to do it:
AsymmetricKeyParameter aKey = (RSAKeyParameters) PublicKeyFactory.createKey(subjectPublicKeyInfo);
AsymmetricBlockCipher engine = new RSAEngine();
engine.init(false, aKey);
byte[] dataEncrypted = engine.processBlock(data, 0, data.length);
After I run those code, I found the result isn't equal to the expection. So I want to know is there any mistake in my code?
Finally I found my way out.
If anyone is familiar with BouncyCastle, he can point me the low-level mistakes.
Firstly, to encrypt data should init Engine with true at first arg in init function.
Secondly, my public key is start with '-----BEGIN RSA PUBLIC KEY-----'. It's PKCS#1 format RSA public key and should use BouncyCastle to read in, but encrypt data should have padding. So, I shouldn't use RSAEngine directly, use PKCS1Encoding instead.
At last , post my encrypt code and decrypt code:
Encryption:
Security.addProvider(new BouncyCastleProvider());
PEMParser reader = new PEMParser(new StringReader(key));
SubjectPublicKeyInfo subjectPublicKeyInfo = (SubjectPublicKeyInfo) reader.readObject();
RSAKeyParameters rsaKeyParameters = (RSAKeyParameters)
PublicKeyFactory.createKey(subjectPublicKeyInfo);
PKCS1Encoding engine = new PKCS1Encoding(new RSAEngine());
engine.init(true, rsaKeyParameters);
return engine.processBlock(data, 0, data.length);
Decryption:
public static byte[] decryptByPublicKey(String data, String key) throws Exception {
byte[] rawData = Base64.decode(data);
Security.addProvider(new BouncyCastleProvider());
PEMParser reader = new PEMParser(new StringReader(key));
PEMKeyPair pemKeyPair = (PEMKeyPair) reader.readObject();
SubjectPublicKeyInfo publicKeyInfo = pemKeyPair.getPublicKeyInfo();
PrivateKeyInfo privateKeyInfo = pemKeyPair.getPrivateKeyInfo();
RSAKeyParameters rsaKeyParameters = (RSAKeyParameters)
PrivateKeyFactory.createKey(privateKeyInfo);
PKCS1Encoding engine = new PKCS1Encoding(new RSAEngine());
engine.init(false, rsaKeyParameters);
return engine.processBlock(rawData, 0, rawData.length);
}
For encryption , you can use modulus and public exponent to create a key that support by JDK:
Security.addProvider(new BouncyCastleProvider());
PEMParser reader = new PEMParser(new StringReader(key));
PemObject obj = reader.readPemObject();
org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(obj.getContent());
BigInteger modulus = rsaPublicKey.getModulus();
BigInteger publicExponent = rsaPublicKey.getPublicExponent();
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(modulus, publicExponent);
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");//This line should use right padding.For PKCS#1 format RSA key , it should be this.
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
See Also:Basic RSA example.
In my App I generate a public/private key pair and store them for later usage on disk. Loading and re-initialising the private key works fine but for the private key I get a Unknown KeySpec type: java.security.spec.PKCS8EncodedKeySpec - and I have no idea why.
That's how I create and save the keys (code a bit simplified to be easier to read):
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(4096);
KeyPair keyPair = kpg.generateKeyPair();
privKey =keyPair.getPrivate();
pubKey =keyPair.getPublic();
DataOutputStream out=new DataOutputStream(ctx.openFileOutput(PRIVKEY_FILE,Context.MODE_PRIVATE));
byte[] data=privKey.getEncoded();
out.write(data);
out.close();
DataOutputStream out=new DataOutputStream(ctx.openFileOutput(PUBKEY_FILE,Context.MODE_PRIVATE));
byte[] data=pubKey.getEncoded();
out.write(data);
out.close();
Next loading of the private key works fine:
DataInputStream in=new DataInputStream(ctx.openFileInput(PRIVKEY_FILE));
byte[] data=new byte[in.available()];
in.readFully(data);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
KeyFactory kf = KeyFactory.getInstance("RSA");
privKey = kf.generatePrivate(keySpec);
decryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, privKey);
Similar code for the public key fails miserably:
DataInputStream in=new DataInputStream(ctx.openFileInput(PUBKEY_FILE));
byte[] data=new byte[in.available()];
in.readFully(data);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
KeyFactory kf = KeyFactory.getInstance("RSA");
pubKey = kf.generatePublic(keySpec); --> here the exception is thrown
encryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, pubKey);
So what am I doing wrong? What is the proper way of loading a public keys data from disk?
Thanks!
Public and Private keys are encoded differently. Whilst private keys are encoded in PKCS #8, public keys are not. They are instead encoded in X.509 according to the ASN.1 specifications.
Description from the Key.getFormat() method:
Returns the name of the primary encoding format of this key, or null if this key does not support encoding. The primary encoding format is named in terms of the appropriate ASN.1 data format, if an ASN.1 specification for this key exists. For example, the name of the ASN.1 data format for public keys is SubjectPublicKeyInfo, as defined by the X.509 standard; in this case, the returned format is "X.509". Similarly, the name of the ASN.1 data format for private keys is PrivateKeyInfo, as defined by the PKCS #8 standard; in this case, the returned format is "PKCS#8".
According to this, instead of reading public keys as PKCS #8, you should read it as X.509.
Consider changing your public key reading code from:
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(data);
to:
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(data);
Where can I find a RSA encrypt example that does not use "NoPadding"?
--update
Better: how to make this SSCCE run correctly without throw the "too much data for RSA block" exception?
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
/**
* Basic RSA example.
*/
public class TestRSA {
public static void main(String[] args) throws Exception {
byte[] input = new byte[100];
Cipher cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
// create the keys
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger("d46f473a2d746537de2056ae3092c451",
16), new BigInteger("11", 16));
RSAPrivateKeySpec privKeySpec = new RSAPrivateKeySpec(new BigInteger(
"d46f473a2d746537de2056ae3092c451", 16), new BigInteger("57791d5430d593164082036ad8b29fb1",
16));
RSAPublicKey pubKey = (RSAPublicKey) keyFactory.generatePublic(pubKeySpec);
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privKeySpec);
// encryption step
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherText = cipher.doFinal(input);
// decryption step
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] plainText = cipher.doFinal(cipherText);
}
}
--update: about loop
Using:
byte[] cipherText = new byte[input.length];
for (int i = 0; i < input.length; i++) {
byte[] singleByteArray = new byte[] { input[i] };
cipherText[i] = cipher.doFinal(singleByteArray)[0];
}
does not work fine. For a unknown reason the cipherText became full of zeros - even if the input is an array of 0x03.
The Sun Providers Documentation for the SunJCE provider tells you what padding specifications are allowed in the Cipher.getInstance() argument. Try Cipher.getInstance("RSA/ECB/PKCS1PADDING");
EDIT:
It is not a padding issue, it is more that you have a misunderstanding of how RSA is used in cryptography. You can either 1) make the modulus bigger than the data, 2) use a Hybrid cryptosystem, or 3) least desirable is to manually break up the input into chunks that are each smaller than the modulus. If you are going to use PKCS1 padding (which is generally recommended), then the input must be not larger than n-11 bytes in length, where n is the number of bytes needed to store the RSA modulus.
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(content);
Update: Are you sure you need bouncycastle for this? And why not just pass RSA as argument to Cipher.getInstance(..) ?
Update 2: Why don't you try any of these RSA encryption examples?