InvalidKeyException: Invalid key format java - java

I am trying to retrieve the public key from Vault. It is stored as secret.
I am trying to convert the string retrieved to a PUBLIC KEY to verify the signature.
Sample public key string looks like this
-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B
/Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO
QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax
c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC
5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5
fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1
AgMBAAE=
-----END PUBLIC KEY-----
I have added the same in my secret value and there is no formatting.
However with the below code I am facing error InvalidKeyException: INVALID KEY FORMAT in the line
PublicKey publicKey = fact.generatePublic(pubKeySpec);
Here is the code:
String publicKeyAsString = secretClient.getSecret("key-name").getValue();
byte[] keyContentAsBytes = publicKeyAsString.getBytes();
KeyFactory fact = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytes);
PublicKey publicKey = fact.generatePublic(pubKeySpec);
Edited with stacktrace:
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:386) ~[?:?]
at sun.security.x509.X509Key.decode(X509Key.java:401) ~[?:?]
at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:122) ~[?:?]
at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:330) ~[?:?]
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:235) ~[?:?]
EDIT: PUBLIC KEY for testing:
-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B
/Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO
QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax
c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC
5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5
fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1
AgMBAAE=
-----END PUBLIC KEY-----
The value of PublicKeyAsString looks like below:
-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B
/Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO
QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax
c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC
5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5
fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1
AgMBAAE= -----END PUBLIC KEY-----

Initially I thought that your problem had to do with the kind of information returned by the Azure KeyVault Secret API, usually encoded as base 64.
In that case, you nee to perform a proper base 64 decoding before attempting to perform the actual key material processing:
String publicKeyAsString = secretClient.getSecret("key-name").getValue();
byte[] keyContentAsBytes = Base64.getDecoder().decode(publicKeyAsString);
But it seems that the Azure client is providing you the information as plain text.
In this case, the secret is a pem encoded public key.
The standard KeyFactory will not allow you to process the returned information out of the box, but yes with slight modifications. For example, try the following:
// Actually
// String publicKeyAsString = secretClient.getSecret("key-name").getValue();
String publicKeyAsString =
"-----BEGIN PUBLIC KEY-----\n" +
"MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B\n" +
"/Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO\n" +
"QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax\n" +
"c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC\n" +
"5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5\n" +
"fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1\n" +
"AgMBAAE=\n" +
"-----END PUBLIC KEY-----";
String publicKeyPem = publicKeyAsString
.replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll("\\n", "")
.replace("-----END PUBLIC KEY-----", "");
byte[] keyContentAsBytes = Base64.getDecoder().decode(publicKeyPem);
try {
KeyFactory fact = KeyFactory.getInstance("RSA");
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytes);
PublicKey publicKey = fact.generatePublic(pubKeySpec);
System.out.println(publicKey);
}catch (Throwable t) {
t.printStackTrace();
}
Or better, use BouncyCastle PemReader for this task:
try (
Reader reader = new StringReader(publicKeyAsString);
PemReader pemReader = new PemReader(reader)
) {
KeyFactory fact = KeyFactory.getInstance("RSA");
PemObject pemObject = pemReader.readPemObject();
byte[] keyContentAsBytesFromBC = pemObject.getContent();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytesFromBC);
PublicKey publicKey = fact.generatePublic(pubKeySpec);
System.out.println(publicKey);
} catch (Throwable t) {
t.printStackTrace();
}
Please, be aware that I included several carriage returns in the definition of the variable publicKeyAsString, they were necessary in order to allow the program process the information.
Please, verify that Azure is returning the pem encoded key in a similar way: if it is not the case, it could be very likely the reason of the problem.
Also, pay attention in the fact that Azure KeyVault is returning the secret as you uploaded it: maybe the problem is there. Please, try the following instead:
PublicKey publicKey = ...
StringWriter writer = new StringWriter();
PemWriter pemWriter = new PemWriter(writer);
pemWriter.writeObject(
new PemObject("PUBLIC KEY", publicKey.getEncoded())
);
pemWriter.flush();
pemWriter.close();
String publicKeyAsString = writer.toString();
// Upload to Azure KeyVault

Related

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format

I am trying to get RSA PublicKey for jwt token validation in Java but my attempts fail with this exception:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format.
My code:
String publicKeyString = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBMk5pKytLeUpVaDhLYklLVU51OG0KUk5lRCtrU2tXZEh6MjBvSFZYK0g0cVd4WHlJSTk0MVdJUFU2WFpPc3lMeE9qZU1hb0ZRanJ6bDFwYnZQekUyRQpwMmhlK1BnQ1JteDNqOFlMVVd3dGpuQTVwTTFLWDhpNG5vTUw4RmlWY1U2NkE5RjRkZmRQR2MzY0tQQ2ZPbnorCmtBbW5qRllzajYzRCsrTThYSDRWaS9Vc0V3T1lzU05FR2RncUd2OTlTNHpVRzFzd2FqZ1NnODhvbTVaOC9Ja1AKY01LT3cvWkpvVHNDN3l1VlJnTC9xa3EwaDVkM2lXVXNNdXl1K0xoblRhTko4bW9WQmpJT2lQQkR0cEQyN1lzNgpCSGs1dEdBa3ZHZDg0N3c4SjVEeTFzYWlQS0pxelltcUx5akg3b3VlcERFczdEZ2UxZUlJeno5a1RnSkhKZHVzCnd3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
PublicKey publicKey = getPublicKey(Base64.getDecoder().decode(publicKeyString), "RSA");
And getPublicKey function:
private static PublicKey getPublicKey(byte[] keyBytes, String algorithm) {
PublicKey publicKey = null;
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
System.out.println(keySpec.getFormat());
publicKey = kf.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
System.out.println("Could not reconstruct the public key, the given algorithm could not be found.");
} catch (InvalidKeySpecException e) {
e.printStackTrace();
System.out.println("Could not reconstruct the public key");
}
return publicKey;
}
I know that there are many similar questions dealing with the same exception being thrown and I've read many of them and tried the solutions but nothing worked. I would appreciate any help.
Your data is not the base64 encoding of an actual X.509 publickey block (SubjectPublicKeyInfo) as required by Java crypto. It actually is the base64 encoding of a PEM file which is itself structured and encoded. Moreover this PEM file is labelled as containing 'RSA PUBLIC KEY' which would be the 'raw' PKCS1 form and wrong for Java, but fortunately whatever created this PEM file was defective and the body of the PEM file actually is an X.509 SubjectPublicKeyInfo which should have PEM label 'PUBLIC KEY' per RFC7468 #13. (RFC5280 is a profile of X.509.)
Thus you need to decode your string to PEM, then parse the PEM to get X.509:
String orig = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBMk5pKytLeUpVaDhLYklLVU51OG0KUk5lRCtrU2tXZEh6MjBvSFZYK0g0cVd4WHlJSTk0MVdJUFU2WFpPc3lMeE9qZU1hb0ZRanJ6bDFwYnZQekUyRQpwMmhlK1BnQ1JteDNqOFlMVVd3dGpuQTVwTTFLWDhpNG5vTUw4RmlWY1U2NkE5RjRkZmRQR2MzY0tQQ2ZPbnorCmtBbW5qRllzajYzRCsrTThYSDRWaS9Vc0V3T1lzU05FR2RncUd2OTlTNHpVRzFzd2FqZ1NnODhvbTVaOC9Ja1AKY01LT3cvWkpvVHNDN3l1VlJnTC9xa3EwaDVkM2lXVXNNdXl1K0xoblRhTko4bW9WQmpJT2lQQkR0cEQyN1lzNgpCSGs1dEdBa3ZHZDg0N3c4SjVEeTFzYWlQS0pxelltcUx5akg3b3VlcERFczdEZ2UxZUlJeno5a1RnSkhKZHVzCnd3SURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
String pem = new String(Base64.getDecoder().decode(orig)); // default cs okay for PEM
String[] lines = pem.split("\n"); lines[0] = lines[lines.length-1] = ""; String body = String.join("", lines);
// in general split on "\r?\n" (or delete "\r" and split on "\n")
//or instead:
//String body = pem.replaceAll("-----(BEGIN|END) RSA PUBLIC KEY-----\n","").replaceAll("\n", ""); // or "\r?\n"
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey key = kf.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(body)));
// for test only:
System.out.println ( ((java.security.interfaces.RSAPublicKey)key).getModulus() );
Note your specific data has PEM using single-character (NL aka \n) linebreaks, but PEM in general can use either NL or CRLF (\r\n); see my comments.

Converting a string private key to PrivateKey type

I am having below RSA private key in string format.
String privatekey = -----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqAKCCMKqboM8ywSuNvmf2zuT0e2elxntdEe5msv22jzT89Xa
1Q/V4CeQdZruIw8eTAMa67Dej+3cPdSrjdDnfxcb9L14U9oFPgOyvxVwb+/S8jqm
F9o7Gvm85X972C8izh+K4ndpPtztxkZ0g7cu7RqrCCBzw5SUfi3pgIpprdiKlVDP
4lF7CTwzRH+oi+BxwOABEiuKOJtjOXX1WJhV6ukEy8K6Fq/QOyt/7vgxkm8I8HMo
SMq2UZNswn5/9SqMWuaTBaQbjZ2f77zaq5X/jOiCThTxFNPjPnzhKhG8ekaqIUpB
y9VuICFgdtVQimnlDykrdJWyeOFWPjYl5vehkwIDAQABAoIBAQCN2Ig2ozvHUA/s
i8gWbAyVyKgVrhueEOrUqJXAZoHEYWTFUgGOru7wcBbb4/E4MlRu8pQw90QAml6+
6SXp9rzsJPOaXrkPelvArtij0ZL7PqyHjBKcwsfyD3V3AXnq3GjzQ9g7OXvm8hnh
s6w9ZFQ/JKvPka6LKo9wNI0W1EVC2tggN0Jt6YJFU7trb5TtiQm/B4NKpflZ7PsC
1WOttz0q+VSzF/p04+33OLXugF1crgMr9KCg0uSPi5zCcM+3RVSWDrcZoh+yV4pK
+g5XKKw/BQD9vrUzsLMDjupp4Is0sSEXwMQeRbUZRnUOWVO7E7jrawzhGXV6v4ZT
3PoxB7CxAoGBANkK9ITnfIr1AlF8S1Ok12AlHLYvCPG4TVBw4WNVUX1y3wBXpOmx
t5S2wGpTmyf3LBpw/0m+5EzoghjSb2QnIRFveAxuhxPCPNqxvyzm2D7ycaGJX41y
RfsxvWVQpzvCVH4yV+tkH107ivR2uFDtbDjqlydPyIxmA1Frlm87CODpAoGBAMYq
gLO5EEKxs1MlCzZkuqsEAq+kZVX7y3Kw9rzCJtRQKfYu0IlNFudPg3KpqcbQsrfR
1Psl6iTLXtLe+92NXDOcc9rfj9crmAKmZA1llXOwxe4FOrLWbxk+i6V3qJ2qrf+I
Dwc9+Xrc6ydcDtTHAi3JeIReEbDMvFc6JRiyjwEbAoGAeH68gYyCeCLNxq9aonVB
nP79kadLL+dCBQamGp+jPiIn6+i8hYFEiitrZ5xC500yDvvsvuRbmtb6Yw1xCgkv
Mp7P5xb1puKPJlrH6AXAyDGRJD0/7ych8vMKUtUUAvlAL0+DwAs13mzQGChQ65zk
GDUk9Y41qLx52xn/yoDbzQECgYEAksM9qF1iPpLPBcAEqtc1LJz+xEiTyHeAOMP5
KNj92vY37ZzEUzulv9AywQQIujcsdVlRTGPLIk8APlpo3K/p3kt7vlkedbRSk3vZ
09YtNo5wOJTk4ThQ9bhNwlF5rrOOxBZnUuzCTQ06l17lmQ5+fZydxiLJJVCsGEn2
2XC82osCgYB9maZnLrSZ3Xq4X256/H+Et8TcgqOZGbLuGkNzheheP/E5LcUhQqbP
oJRB24XoX+yw+Do5q2pHHgbrrHwcdg3Xfw9sb5P2JNbtG57mO7QEilIHfUQzQl/U
XSKgJJZ/9bTPlbZmp9ChM9izUv8DI5vjhDputhlEHP4BpHBN03a85Q==
-----END RSA PRIVATE KEY-----
I am trying to convert this to PrivateKey type using below code snippet-
public PrivateKey generatePrivateKey(String privateKey) throws Exception{
privateKey = privateKey.replace("-----BEGIN RSA PRIVATE KEY-----", "");
privateKey = privateKey.replace("-----END RSA PRIVATE KEY-----", "");
privateKey = privateKey.replaceAll("\\s+", "");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey.getBytes());
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
I am getting below error -
It is giving me an error on generatePrivate method.
Execution exception[[InvalidKeySpecException: java.security.InvalidKeyException: invalid key format]]
Tried using the answer here Java: Convert DKIM private key from RSA to DER for JavaMail
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Base64;
public class pkcs1ToPkcs8 {
public static void main(String[] args) throws IOException {
String pkcs1 = //privatekey provided above
// b64 now contains the base64 "body" of the PEM-PKCS#1 file
byte[] oldder = Base64.getDecoder().decode(pkcs1.getBytes());
// concatenate the mostly-fixed prefix plus the PKCS#1 data
final byte[] prefix = {0x30,(byte)0x82,0,0, 2,1,0, // SEQUENCE(lenTBD) and version INTEGER
0x30,0x0d, 6,9,0x2a,(byte)0x86,0x48,(byte)0x86,(byte)0xf7,0x0d,1,1,1, 5,0, // AlgID for rsaEncryption,NULL
4,(byte)0x82,0,0 }; // OCTETSTRING(lenTBD)
byte[] newder = new byte [prefix.length + oldder.length];
System.arraycopy (prefix,0, newder,0, prefix.length);
System.arraycopy (oldder,0, newder,prefix.length, oldder.length);
// and patch the (variable) lengths to be correct
int len = oldder.length, loc = prefix.length-2;
newder[loc] = (byte)(len>>8); newder[loc+1] = (byte)len;
len = newder.length-4; loc = 2;
newder[loc] = (byte)(len>>8); newder[loc+1] = (byte)len;
FileOutputStream fo = new FileOutputStream ("pkcs8_file");
fo.write (newder); fo.close();
System.out.println ("converted length " + newder.length);
}
}
BEGIN RSA PRIVATE KEY means your key is pkcs#1 format and not pkcs#8. Reading pkcs#1 keys is not natively supported by Java. You need to use bouncycastle library ( see Read RSA private key of format PKCS1 in JAVA )or use any of these solutions Getting RSA private key from PEM BASE64 Encoded private key file
if your key were pkcs#8, it would have the header BEGIN PRIVATE KEY. In that case, for your code to work correctly you would need additionally decode the key content from base64
To convert a pkcs#1 key to pkcs#8 you can use openssl
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in pkcs1.key -out pkcs8.key

How to load RSA public key from String for signature verification in Java?

I have the following public key, that is stored in the DB (PostgresSQL) as text. It's a String, in java:
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI
1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW
s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ
KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9
QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg
d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy
esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi
KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD
9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn
Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla
2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==
-----END RSA PUBLIC KEY-----
I don't know how this key has been generated, I'm sorry. I have been told to take this key and verify the signature of another string that I'll call "object". I have been told that the algorithm that I have to use to verify "object" is SHA256withRSA.
So, I have written the following java method to read the key
private PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = publicKey.replaceAll("\\n", "");
publicKey = publicKey.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKey = publicKey.replace("-----END RSA PUBLIC KEY-----", "");
publicKey = publicKey.trim();
byte[] keyDecoded = Base64.getDecoder().decode(publicKey.getBytes());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyDecoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicSpec);
return pubKey;
}
The point is that I get the following exception:
java.security.InvalidKeyException: IOException: algid parse error, not
a sequence
I have read plenty of qustions as mine in stackoverflow. The code written by other users is pretty similar (sometimes identical) to mine. So I definitely don't get why it doesn't work for me. Other developers (workmates) are doing the same in php and it works great, so I would discard the hypothesis of wrong public key. Maybe didn't I understood the process clearly? Do you have any clue, please?
I have also tried to cope with the problem using BouncyCastle library, as suggested here, but I get the same exception. The following is the code I have written:
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new BouncyCastleProvider());
PemReader pp = new PemReader(new StringReader(publicKey));
PemObject pem = pp.readPemObject();
byte[] content = pem.getContent();
pp.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
You can't load that key using an X509EncodedKeySpec. According to it's JavaDoc documentation it expects the following format:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Instead your key looks different. I used the data from your post, converted it to hex data and posted it into the online ASN.1 decoder.
The output is this:
SEQUENCE (2 elem)
INTEGER (4096 bit) 873481340827968071893572683200799871431146795599597693981565010037737…
INTEGER 65537
As you may recognize your key does not contain an AlgorithmIdentifier therefore it can not be loaded using X509EncodedKeySpec.
My suggestion would be to use the BouncyCastle library and it's PEMParser class for loading this key:
File pemFile = new File("test.pem");
try (PEMParser pp = new PEMParser(new InputStreamReader(new FileInputStream(pemFile)))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = kf.generatePublic(rsaSpec);
System.out.println(publicKey);
}
Or you manually convert the key to PKCS#8 format via openssl.

RSA public and private key as String variables in Java

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.

Unable to load RSA public key

I'm trying to read RSA public key shown below, but I get an exception at line 6: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
My code:
String rsaPublicKey = rsaPublicKeyString.replace(
"-----BEGIN RSA PUBLIC KEY-----\n", "");
rsaPublicKey = rsaPublicKey.replace("\n-----END RSA PUBLIC KEY-----", "");
byte[] bytes = EncryptionUtils.decodeBase64(rsaPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
pubKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);
RSA public key:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-----END RSA PUBLIC KEY-----
What am I doing wrong?
UPD:
public static byte[] decodeBase64(String data) throws EncryptionException {
try {
BASE64Decoder decoder = new BASE64Decoder();
return decoder.decodeBuffer(data);
} catch (Exception e) {
throw new EncryptionException(e);
}
}
For me, I was missing the OID in the public key. I had to correct that on the iOS side using help from here: http://blog.wingsofhermes.org/?p=42
Also, my public key didn't have to be casted to an RSAPublicKey, the standard worked just fine:
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
Your problem is caused because your public key is an RSAPublicKey object rather than a SubjectPublicKeyInfo object (see this answer for a good description of the difference). You will need to convert from one to the other before your code will work.
BouncyCastle can do the conversion for you. The following code snippet will work, although I dislike it for two reasons:
It uses a deprecated class PEMReader.
It requires the BouncyCastle provider to be loaded.
Code:
Security.addProvider(new BouncyCastleProvider());
PEMReader reader = new PEMReader(new StringReader(rsaPublicKeyString));
BCRSAPublicKey key = (BCRSAPublicKey) reader.readObject();
bytes[] = key.getEncoded(); // now in SubjectPublicKeyInfo format.
// as before...
With BouncyCastle, there is always many ways to skin a cat. Perhaps someone can find a more elegant solution than the one above?

Categories

Resources