I need to generating a RSA and DSA key pair (public and private key) in PEM format using java.
I want the public and private key files to be opened with this format:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAryQICCl6NZ5gDKrnSztO
3Hy8PEUcuyvg/ikC+VcIo2SFFSf18a3IMYldIugqqqZCs4/4uVW3sbdLs/6PfgdX
7O9D22ZiFWHPYA2k2N744MNiCD1UE+tJyllUhSblK48bn+v1oZHCM0nYQ2NqUkvS
j+hwUU3RiWl7x3D2s9wSdNt7XUtW05a/FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTd
OrUZ/wK69Dzu4IvrN4vs9Nes8vbwPa/ddZEzGR0cQMt0JBkhk9kU/qwqUseP1QRJ
5I1jR4g8aYPL/ke9K35PxZWuDp3U0UPAZ3PjFAh+5T+fc7gzCs9dPzSHloruU+gl
FQIDAQAB
-----END PUBLIC KEY-----
My public key is already generated before with this format that i do not want it:
0Ÿ0 *†H†÷ 0Ÿ0 *†H†÷
ok, this is my code of key generation:
private static void createKey()
throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Password to encrypt the private key: ");
String password = in.readLine();
System.out.println("Generating an RSA keypair...");
// Create an RSA key
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
System.out.println("Done generating the keypair.\n");
// Now we need to write the public key out to a file
System.out.print("Public key filename: ");
String publicKeyFilename = "C:/Users/Joe/Desktop/" + in.readLine();
// Get the encoded form of the public key so we can
// use it again in the future. This is X.509 by default.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// Write the encoded public key out to the filesystem
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(publicKeyBytes);
fos.close();
// Now we need to do the same thing with the private key,
// but we need to password encrypt it as well.
System.out.print("Private key filename: ");
String privateKeyFilename = "C:/Users/Joe/Desktop/" + in.readLine();
// Get the encoded form. This is PKCS#8 by default.
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
// Here we actually encrypt the private key
byte[] encryptedPrivateKeyBytes =
passwordEncrypt(password.toCharArray(),privateKeyBytes);
fos = new FileOutputStream(privateKeyFilename);
fos.write(encryptedPrivateKeyBytes);
fos.close();
}
thank you for your help..
Instead of manually generating the PEM String, you can use the bouncy castle to do it for you: since it's a tested library you can be sure about the output. The following code is in Kotlin but can easily be used with Java syntax:
val gen = KeyPairGenerator.getInstance("RSA")
gen.initialize(2048)
val pair = gen.generateKeyPair()
val privateKey: PrivateKey = pair.private
val pemObject = PemObject("RSA PRIVATE KEY", privateKey.encoded)
val byteStream = ByteArrayOutputStream()
val pemWriter = PemWriter(OutputStreamWriter(byteStream))
pemWriter.writeObject(pemObject)
pemWriter.close();
println(String(byteStream.toByteArray()))
Maybe a bit late but there is my solution. Hope it helps others.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
Here you're taking bytes of key and writing directly to file. So you get the appropriate result - DER-encoded file. However PEM is Base64 encoded format with line breaks each 64 symbols and header/footer.
There is code implementing this logic:
String publicKeyContent = Base64.getEncoder().encodeToString(publicKeyBytes);
String publicKeyFormatted = "-----BEGIN PUBLIC KEY-----" + System.lineSeparator();
for (final String row:
Splitter
.fixedLength(64)
.split(publicKeyContent)
)
{
publicKeyFormatted += row + System.lineSeparator();
}
publicKeyFormatted += "-----END PUBLIC KEY-----";
So publicKeyFormatted will contain PEM-encoded string of public key.
P.S. Splitter is a class provided in Guava lib, but you can split the string with a simple cycle or somehow.
I've written a library that includes methods which can do this. It's called Reasonably Easy Cryptography and you can use the PEMHandler methods for this. Here's how you could do it, assuming you've imported PEMHandler from my library, and that key's class is an implementation of java.security.Key such as a PrivateKey:
String pem = PEMHandler.keyToPem(key);
This works with any kind of Key, it'll figure out whether it's a public or private key and what algorithm it uses on its own (it isn't perfect and I'm still working on finding the best way to do that, but it does work fine with PrivateKey and PublicKey). There's also a method to do this for both keys in a KeyPair in one call, and methods to convert the PEM string back to a Key.
Related
I need to generate an openssh format key pair in java.
I generate a public private key pair using the KeyPairGenerator in java and save it to a file:
gen.initialize(2048);
KeyPair pair = gen.generateKeyPair();
Base64.Encoder encoder = Base64.getEncoder();
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
System.out.println(encoder.encodeToString(publicKey.getEncoded());
System.out.println(encoder.encodeToString(privateKey.getEncoded());
However, if I try to use this with ssh-keygen I get a "Failed to load key private.key: invalid format"
How can I either convert the PKCS#8 private key to openssh format or generate a key pair in the openssh format?
EDIT:
To clarify, I am looking for something like this:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAo+2pHTLFxKmzyMZC4VVnrFTTHONdoKRYPeT4+ohK/g2X7U8aBj1V
FyQntFUMIRHgQcZcGQX0tLpnv2J5Fyr1YfxCakLB6W5XIAHIwuSRO4H6YpX4iMW7C7RWsq
Q2JUW5Vab89WduAotltIjLOyRUO2E8LWtOcCnnk1j+tVaB6q+EvK0MNxU5JOtybRkQWJTa
ONkBuZMeJ4e2Et/WVNceY6ZmBlMEVVW9uQ4j0MQk3UoWkY4JdPinIGi1Do2xUfuTwbmE0w
y4yb5yMw8fZvVDxFqNJSNLHFUihzsRMgbDRQ+P30e9uFKBBJ0pe2ZArYWLA40ojPzzi2ob
oB+NwSVHDQAAA7iTErFrkxKxawAAAAdzc2gtcnNhAAABAQCj7akdMsXEqbPIxkLhVWesVN
Mc412gpFg95Pj6iEr+DZftTxoGPVUXJCe0VQwhEeBBxlwZBfS0ume/YnkXKvVh/EJqQsHp
blcgAcjC5JE7gfpilfiIxbsLtFaypDYlRblVpvz1Z24Ci2W0iMs7JFQ7YTwta05wKeeTWP
61VoHqr4S8rQw3FTkk63JtGRBYlNo42QG5kx4nh7YS39ZU1x5jpmYGUwRVVb25DiPQxCTd
ShaRjgl0+KcgaLUOjbFR+5PBuYTTDLjJvnIzDx9m9UPEWo0lI0scVSKHOxEyBsNFD4/fR7
24UoEEnSl7ZkCthYsDjSiM/POLahugH43BJUcNAAAAAwEAAQAAAQEAn+Qoxn0GX4sy+8s9
4rG93F4kSJIQeaazFzPmEd+sXd5+aI52EM3z2A2A2Kj3mq3n8d/7ZsDjbQBAP3FaMNnK3B
cD5MdWgkwImQSEgGwWqFdgFJa5AxbyGTl+MuJuma5HVp75LpgCumKjAhNHP1lw+zYdTyPS
Lx8AbD0qu080iuWtMwWV5Hap9ZHjYEVIrgDArxbzTT0wvpqT1cFCon9vJVJpPOWsRbpEgt
d+M+eOpBL7mDKJhub9soguhhRxqgYvgigJueeqMnYpxPye7oewratTMAWskk0n0zXKRON0
q9zVVfdc9Md0cqkkbNGyQ/VdbrM+zZt+46v0zgyk6zdrdQAAAIAcnNjD6Pm8YFXa2mkI47
m+2SRcKJ4jmvGJTN8iRan7nmBMNDfC4EbGW8eQbcl4PsUevqd0O2PUpDNL5m9stAF8vBna
QRlzlWNncdJTBS4Rlzf+LisCrcecdl+Vg1tSLpur/+prk9VFZDnj5uUPSHb3zdLfYt7onV
VA0FSm0PeFdwAAAIEA/YQw567hHMp2mqaxW3ogNsqSv+nRiZEPqEHvCW3znVMJZPsbubpg
MiJaQIfi1fNkgPu3pKdCu1M1+tf4hKK5Tjh8jPh9KSi/T6VZ4lQbFZaMFWXeY0FKKszkGR
b+8BHHdH9a8XnAJvwWOGN5evJl69j7VylMB0ZIIZHiEt/ZDIsAAACBAKWIyVHk+RLz/maM
11lajRrnM46YXRJR4pm0AWQLcso74Q5DMhi0I6joUyrTPliUKcWM38lCN3uKV5qBioCzJv
PfWgDquQCj7kMCC98CA3YSbffeLtMigon+kT3JZb9tYKbTyRx35yw+6ghbi7ccWZgLoz6w
g6ulyyuhmYLHM3XHAAAAAAEC
-----END OPENSSH PRIVATE KEY-----
versus this:
-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCj7akdMsXEqbPI
xkLhVWesVNMc412gpFg95Pj6iEr+DZftTxoGPVUXJCe0VQwhEeBBxlwZBfS0ume/
YnkXKvVh/EJqQsHpblcgAcjC5JE7gfpilfiIxbsLtFaypDYlRblVpvz1Z24Ci2W0
iMs7JFQ7YTwta05wKeeTWP61VoHqr4S8rQw3FTkk63JtGRBYlNo42QG5kx4nh7YS
39ZU1x5jpmYGUwRVVb25DiPQxCTdShaRjgl0+KcgaLUOjbFR+5PBuYTTDLjJvnIz
Dx9m9UPEWo0lI0scVSKHOxEyBsNFD4/fR724UoEEnSl7ZkCthYsDjSiM/POLahug
H43BJUcNAgMBAAECggEBAJ/kKMZ9Bl+LMvvLPeKxvdxeJEiSEHmmsxcz5hHfrF3e
fmiOdhDN89gNgNio95qt5/Hf+2bA420AQD9xWjDZytwXA+THVoJMCJkEhIBsFqhX
YBSWuQMW8hk5fjLibpmuR1ae+S6YArpiowITRz9ZcPs2HU8j0i8fAGw9KrtPNIrl
rTMFleR2qfWR42BFSK4AwK8W8009ML6ak9XBQqJ/byVSaTzlrEW6RILXfjPnjqQS
+5gyiYbm/bKILoYUcaoGL4IoCbnnqjJ2KcT8nu6HsK2rUzAFrJJNJ9M1ykTjdKvc
1VX3XPTHdHKpJGzRskP1XW6zPs2bfuOr9M4MpOs3a3UCgYEA/YQw567hHMp2mqax
W3ogNsqSv+nRiZEPqEHvCW3znVMJZPsbubpgMiJaQIfi1fNkgPu3pKdCu1M1+tf4
hKK5Tjh8jPh9KSi/T6VZ4lQbFZaMFWXeY0FKKszkGRb+8BHHdH9a8XnAJvwWOGN5
evJl69j7VylMB0ZIIZHiEt/ZDIsCgYEApYjJUeT5EvP+ZozXWVqNGuczjphdElHi
mbQBZAtyyjvhDkMyGLQjqOhTKtM+WJQpxYzfyUI3e4pXmoGKgLMm899aAOq5AKPu
QwIL3wIDdhJt994u0yKCif6RPcllv21gptPJHHfnLD7qCFuLtxxZmAujPrCDq6XL
K6GZgsczdccCgYEAu1VvfgRwPIIv2l/LNzmrjFFs13vEZs9WrpLqPCGPn3W4v3H/
LuNWKjXQU1cWe9r7LYTUU0t1uE7o2I+3COvl2rNK9jC47C08EXKyVNipmu5AXZ+F
Efsw/yegdhnUETpSFPf3D/FT2Hr3QHvhTMTKI9mXAPV3RPjeXFAcqq3XCbkCgYBI
2scgUBJ/kPuqztoI7Z2k9ZTvcYelBH0jAOKL0a4X6/rFeDWYQdBgCsBv3MFX4v3v
gG0N+yLIML8VtWXr9u1x8B+Av83kxkGbJE9tO0miscHMkfEx48JoUa5C71zkv5MG
Wbft26fXBWmjfAcl9EhKbvTOJooNBc9ByMHzmRDBCQKBgByc2MPo+bxgVdraaQjj
ub7ZJFwoniOa8YlM3yJFqfueYEw0N8LgRsZbx5BtyXg+xR6+p3Q7Y9SkM0vmb2y0
AXy8GdpBGXOVY2dx0lMFLhGXN/4uKwKtx5x2X5WDW1Ium6v/6muT1UVkOePm5Q9I
dvfN0t9i3uidVUDQVKbQ94V3
-----END RSA PRIVATE KEY-----
For the private key, PKCS8 PEM is compatible with OpenSSH. (Contrary to Maarten you can't use DER, but contrary to Topaco you aren't required to convert to PKCS1 'traditional' format.) You do need to create correct PEM, which requires header and footer lines AND linebreaks in the body (not a single humongous string); see wikipedia or rfc7468. Java 8+ Base64.getMimeEncoder() does the linebreaks.
PrivateKey pkey = pair.getPrivate();
System.out.println ("-----BEGIN PRIVATE KEY-----");
System.out.println (Base64.getMimeEncoder().encodeToString(pkey.getEncoded()));
// to be strict use getMimeEncoder(64, value_of_line_separator)
// for PEM, but in practice MIME default 76 actually works
System.out.println ("-----END PRIVATE KEY-----");
Note this format is unencrypted, thus anyone who gets/sees your file (or a copy or backup of it) gets your private key. Whether, when and how this is a problem depends on many factors and belongs on security.SX not SO. OpenSSH supports and generally encourages encrypted privatekey files, but (also) supports unencrypted ones.
Public key is harder. OpenSSH's public key format does not follow the standards supported by Java (without adding BouncyCastle). However, you can construct this by hand, and this has been answered several times; see my list at ssh-keygen and openssl gives two different public keys and example in How to Calculate Fingerprint From SSH RSA Public Key in Java? (which references the same). Note the X.509 SubjectPublicKeyInfo format preferred by OpenSSL is the same format used by Java's PublicKey.getEncoded() (which Java's .getFormat() ambiguously calls "X.509"), except that Java uses binary/DER only while OpenSSL uses both binary/DER and PEM.
Update: your complaint was 'fails to load in ssh-keygen' so I thought you could use any format accepted by OpenSSH, which the PKCS8 format above is. If you insist on the OpenSSH-defined 'new' format, even though it isn't necessary for OpenSSH including ssh-keygen, that's much harder in plain Java without BouncyCastle. So far I've got the case you now show (RSA unencrypted) only:
RSAPrivateCrtKey pkey = // generated or read (I used test data I already have)
byte[] alg = "ssh-rsa".getBytes(), none = "none".getBytes();
byte[] nbyt = pkey.getModulus().toByteArray(), ebyt = pkey.getPublicExponent().toByteArray();
int rand = new Random().nextInt();
ByteBuffer pub = ByteBuffer.allocate(nbyt.length+50); // always enough, but not too much over
for( byte[] x : new byte[][]{alg,ebyt,nbyt} )
{ pub.putInt(x.length); pub.put(x); }
ByteBuffer prv = ByteBuffer.allocate(nbyt.length*4+50); // ditto
prv.putInt(rand); prv.putInt(rand);
for( byte[] x : new byte[][]{alg,nbyt,ebyt,pkey.getPrivateExponent().toByteArray(),
pkey.getCrtCoefficient().toByteArray(),pkey.getPrimeP().toByteArray(),pkey.getPrimeQ().toByteArray()} )
{ prv.putInt(x.length); prv.put(x); }
prv.putInt(0); // no comment
for( int i = 0; prv.position()%8 != 0; ) prv.put((byte)++i); // 8 apparently default? IDK
ByteBuffer all = ByteBuffer.allocate(100+pub.position()+prv.position()); // ditto
all.put("openssh-key-v1".getBytes()); all.put((byte)0);
all.putInt(none.length); all.put(none); // cipher
all.putInt(none.length); all.put(none); // pbkdf
all.putInt(0); all.putInt(1); // parms, count
all.putInt(pub.position()); all.put(pub.array(),0,pub.position());
all.putInt(prv.position()); all.put(prv.array(),0,prv.position());
byte[] result = Arrays.copyOf(all.array(), all.position());
System.out.print ("-----BEGIN OPENSSH PRIVATE KEY-----\n"
+ Base64.getMimeEncoder(68,"\n".getBytes()).encodeToString(result)
+ "\n-----END OPENSSH PRIVATE KEY-----\n");
// MimeEncoder only does multiple of 4 char, consistent with the 3-to-4 logic
// OpenSSH writes 70 but accepts 68; it requires no-CR (contrary to MIME)
I have a publicKey/privateKey pair generated from this function:
public static void generateKey() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(2048);
final KeyPair key = keyGen.generateKeyPair();
File privateKeyFile = new File(PRIVATE_KEY_FILE);
File publicKeyFile = new File(PUBLIC_KEY_FILE);
// Create files to store public and private key
if (privateKeyFile.getParentFile() != null) {
privateKeyFile.getParentFile().mkdirs();
}
privateKeyFile.createNewFile();
if (publicKeyFile.getParentFile() != null) {
publicKeyFile.getParentFile().mkdirs();
}
publicKeyFile.createNewFile();
// Saving the Public key in a file
ObjectOutputStream publicKeyOS = new ObjectOutputStream(
new FileOutputStream(publicKeyFile));
publicKeyOS.writeObject(key.getPublic());
publicKeyOS.close();
// Saving the Private key in a file
ObjectOutputStream privateKeyOS = new ObjectOutputStream(
new FileOutputStream(privateKeyFile));
privateKeyOS.writeObject(key.getPrivate());
privateKeyOS.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Now I want to convert publicKey to base64 while writing and use that base64 decode to get publicKey back ,how can that be done?
Generally if you want to store a file in base 64 you can simply encode the byte array. You can even put a Base64 stream in between the ObjectOutputStream and FileOutputStream (helpfully provided by the Base64 class within Java 8).
However, public keys and private keys have default encodings which can be accessed using their getEncoded methods:
PublicKey publicKey = key.getPublic();
byte[] encodedPublicKey = publicKey.getEncoded();
String b64PublicKey = Base64.getEncoder().encodeToString(encodedPublicKey);
try (OutputStreamWriter publicKeyWriter =
new OutputStreamWriter(
new FileOutputStream(publicKeyFile),
StandardCharsets.US_ASCII.newEncoder())) {
publicKeyWriter.write(b64PublicKey);
}
This saves the public key in SubjectPublicKeyInfo format, something that can be read and written by multiple types of software and cryptographic libraries.
For instance, you can paste it in an online ASN.1 decoder (the online decoder will itself convert it to hex, but it will parse base 64 as well). The format of bytes are in so called ASN.1 / DER (which is a generic format, just like you can encode multiple types of files in XML).
If you want to have the key in OpenSSL compatible format (with a "PUBLIC KEY" header and footer) you can use a library such as Bouncy Castle (e.g. org.bouncycastle.openssl.jcajce.JcaPEMWriter).
I currently have a public/private key pair in as two separate strings that I am trying to convert back into public/private key format.
I am currently reading in the string from a .auth file, not a public key file and private key file so I can't use openssl pkcs8 -topk8... to get rid of the algid parse error. I am reading in the keys like so:
BufferedReader br = new BufferedReader(new FileReader(authFileName));
String publicK = br.readLine();
String privateK = br.readLine();
br.close();
From there, I am converting the files back into byte arrays like so using the Base64 Decoder like so:
byte[] privateBytes = Base64.getDecoder().decode(privateK);
byte[] publicBytes = Base64.getDecoder().decode(publicK);
Lastly I am converting those byte arrays into Public and Private keys using a key factory. Here is the code:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateBytes));
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicBytes));
When my code tries to generate the private and public keys using the key factory, that is when it throws the InvalidKeyException algid parse error
I have looked online and noticed many people saying adding the following line to my code should fix the problem:
Security.addProvider(new BouncyCastleProvider());
But it didn't. Is there any other solution, besides bouncy castle and openssl, that I should try that worked for anyone else?
For obvious security reasons i need to encrypt and decrypt User's PIN codes with RSA private and public key, I have found working solution, which looks like:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(512);
KeyPair rsaKeyPair = kpg.genKeyPair();
byte[] txt = "This is a secret message.".getBytes();
System.out.println("Original clear message: " + new String(txt));
// encrypt
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, rsaKeyPair.getPublic());
txt = cipher.doFinal(txt);
} catch (Throwable e) {
e.printStackTrace();
return;
}
System.out.println("Encrypted message: " + new String(txt));
// decrypt
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, rsaKeyPair.getPrivate());
txt = cipher.doFinal(txt);
} catch (Throwable e) {
e.printStackTrace();
return;
}
System.out.println("Decrypted message: " + new String(txt));
}
everything works fine, but in this example key-pair is not static and generate new values everytime, but I need to use same keys, which are represented as String variables:
public static final String PrivateKey = "MIICXAIBAAKBgQDx0PSJr6zEP9914k1eM+sS8/eW+FenhBQI/jf6ARe8kZHFig9Y"
+ bla bla bla
+ "wdK3jBzObK319yNFr/2LukNZ9Bgv7fS78roBvxbe2gI=";
public static final String PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDx0PSJr6zEP9914k1eM+sS8/eW"
+ bla bla bla
+ "jYo5w2Nhxe2cukCQMQIDAQAB";
Is there any way to cast these variables to PublicKey and PrivateKey Class?
If I understand what you want, to obtain PublicKey and PrivateKey instances from your static variables you can do, for example, this way:
private static final String privateKeyString = "...";
private static PrivateKey privateKey;
private static final String publicKeyString = "...";
private static PublicKey publicKey;
static {
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
byte[] encodedPv = Base64.decodeBase64(privateKeyString);
PKCS8EncodedKeySpec keySpecPv = new PKCS8EncodedKeySpec(encodedPv);
privateKey = kf.generatePrivate(keySpecPv);
byte[] encodedPb = Base64.decodeBase64(publicKeyString);
X509EncodedKeySpec keySpecPb = new X509EncodedKeySpec(encodedPb);
publicKey = kf.generatePublic(keySpecPb);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
}
}
After (mostly) concurring with #JB that passwords (usually) shouldn't be encrypted, they should be "hashed" -- using a method specifically designed to "stretch" and salt such as scrypt, not a fast hash like SHA-1 -- and also noting that RSA-512 as used in your original code is broken and even RSA-1024 as apparently used in your modification is considered weak:
Your PrivateKey value appears (from its beginning) to be base64 of a plain PKCS#1 DER encoding, which basically is used only by OpenSSL and things that use OpenSSL (format) like older versions of OpenSSH. The Java standard "Sun" providers do not handle this, although I think BouncyCastle might if you want to explore that. For Sun you need to convert it to binary DER from base64; wrap it into PKCS#8 format (which in binary is just adding a header and maybe EOC trailers because the algorithm-specific part of PKCS#8 for RSA is PKCS#1); and put it in a PKCS8EncodedKeySpec and run it through generatePrivate of a KeyFactory of type RSA. See
http://docs.oracle.com/javase/8/docs/api/java/util/Base64.html (Java8 only)
http://docs.oracle.com/javase/8/docs/api/java/security/KeyFactory.html
https://www.rfc-editor.org/rfc/rfc5208#section-5 for the structure of unencrypted PKCS#8 (Java doesn't do the encrypted format in section 6) and look at the publickey form for the OID for RSA.
Alternatively add the header/trailer to make it proper PEM, use OpenSSL to convert it to PKCS#8 (unencrypted), and optionally binary at the same time, and run that through generatePrivate.
Your PublicKey similarly appears to be base64 of an X.509 SubjectPublicKeyInfo encoding, which OpenSSL (but not OpenSSH) uses and standard Java does support under the name "X.509". So just convert from base64 to binary, put in an X509EncodedKeySpec, and run through generatePublic of the RSA KeyFactory. Note if your encryption will be done remote or distributed, which is the usual scenario for publickey-encryption, the encryptor must be certain to use the correct publickey; if an attacker can substitute a wrong publickey they can decrypt and steal at least some of your supposedly secure data. That's why real PK systems don't use a plain publickey, they use a certificate, either X.509 like SSL/TLS and S/MIME, or web-of-trust like PGP.
I got this running doing the following:
public Key loadPrivateKey(String stored) throws GeneralSecurityException {
PKCS8EncodedKeySpec keySpec =
new PKCS8EncodedKeySpec(
Base64.getDecoder().decode(stored.getBytes(StandardCharsets.UTF_8)));
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
public Key loadPublicKey(String stored) throws GeneralSecurityException {
byte[] data = Base64.getDecoder().decode(stored.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
KeyFactory fact = KeyFactory.getInstance("RSA");
return fact.generatePublic(spec);
}
You must also to remove -----BEGIN PRIVATE KEY-----, -----END PRIVATE KEY----- and all \n from the strings that contain you keys.
I am encrypting using Bouncy Castle. I am using RSA and public key is stored in PEM file. I am finding that when I run the code in simple console project (not Android project) everything works fine - meaning the encrypted string can be decrypted using the private key. However, when I run the same code in Android app, the encrypted byte array is different for the same public key and is not recognized as valid encryption for the given key pair.
Details:
Here is the code that encrypts string using Bouncy Castle and taken out of the console project. This one works fine and produces the encrypted string which is valid encryption for the key pair and can be decrypted.
private static void encrypt() {
try {
//This example uses the Bouncy Castle library
Security.addProvider(new BouncyCastleProvider());
String plainText = "This needs to be encrypted";
String public_key_file = "PublicKey.pem";
//Load public key
PEMParser parser = new PEMParser(new FileReader(public_key_file));
Object key = parser.readObject();
parser.close();
PublicKey pubKey = null;
if (key instanceof SubjectPublicKeyInfo) {
SubjectPublicKeyInfo spki = (SubjectPublicKeyInfo) key;
pubKey = KeyFactory.getInstance("RSA").generatePublic
(new X509EncodedKeySpec(spki.getEncoded()));
}
//Encrypt the plain text
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted_data = cipher.doFinal(plainText.getBytes());
String encoded_data = new String(Base64.encode(encrypted_data));
System.out.println("Encrypted Value:");
System.out.println(encoded_data);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
Now here is the code taken out of the Android app. The only important difference is that the key file is read from Assets folder. This code also produces encrypted string but it is not valid for the key pair and can't be decrypted.
public void encrypt(Context context) {
try {
//This example uses the Bouncy Castle library
Security.addProvider(new BouncyCastleProvider());
String plainText = "This needs to be encrypted";
String public_key_file = "PublicKey.pem";
//Load public key
InputStream inputStream = context.getAssets().open(public_key_file);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
// read from the input stream reader
PEMParser parser = new PEMParser(inputStreamReader);
Object key = parser.readObject();
parser.close();
PublicKey pubKey = null;
if (key instanceof SubjectPublicKeyInfo) {
SubjectPublicKeyInfo spki = (SubjectPublicKeyInfo) key;
pubKey = KeyFactory.getInstance("RSA").generatePublic
(new X509EncodedKeySpec(spki.getEncoded()));
}
//Encrypt the plain text
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted_data = cipher.doFinal(plainText.getBytes());
String encoded_data =
new String(Base64.encode(encrypted_data));
Log.d("MyApp", "Encrypted Value:");
Log.d("MyApp", encoded_data);
} catch (Exception ex) {
Log.d("MyApp", ex.getMessage());
}
}
In both the cases, following things are same:
Public Key
Actual code written (with difference of reading from Assets folder and logging to Logcat)
JDK version (1.7)
Bouncy Castle libraries (bcpkix-jdk15on-152.jar and bcprov-jdk15on-152.jar)
What's different: The environment. One is console program and the other is Android App.
On further investigation while debugging
I observed that when running the console program the KeyFactory returns instance of "sun.security.rsa.RSAPublicKeyImpl". However when running android app, the KeyFactory returns instance of "com.android.org.conscrypt.OpenSSLRSAPublicKey". Not sure if that's the problem. The encrypted byte array is different for same plain text and public key.
Any help is greatly appreciated.
Thanks in Advance,
Sandeep