How can i check if a base64 text is a valid RSA public key format (in java).
==> check that is in base64
==> check that is valid key of RSA 4096 bits.
Thank you
Something like this should work for you, pay attention to the comments in the code that I added
I referred to this answer and made few modifications to the code: How can I construct a java.security.PublicKey object from a base64 encoded string?
public static PublicKey getKey(String key){
try{
//if base64 is invalid, you will see an error here
byte[] byteKey = Base64.getDecoder().decode(key);
//if it is not in RSA public key format, you will see error here as java.security.spec.InvalidKeySpecException
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509publicKey);
}
catch(Exception e){
e.printStackTrace();
}
return null;
}
Related
So I am creating a basic socket program where I want to send an encrypted string in C to a Java program. My C program encrypts the string with a public PEM key. I have converted the matching private PEM key to a DER key and now want to decrypt the string that was sent to my Java program. How do I do this?
At the moment I am getting an IllegalBlockSizeException stating that "Data must not be longer than 256 bytes" when trying to run the code as it stands.
This is what I have at the moment:
C client program...
//Get our public key from the publickey file created by server
FILE *publicKeyFile = fopen("publicKey.pem", "rb");
RSA *rsa = RSA_new();
rsa = PEM_read_RSA_PUBKEY(publicKeyFile, &rsa, NULL, NULL);
if(rsa == NULL) {
printf("Error with public key...\n");
}
else {
//if the public key is correct we will encrypt the message
RSA_public_encrypt(2048, sigMessage, sigMessage, rsa, RSA_PKCS1_PADDING);
}
Java decryption...
public static String decrypt(byte[] encryptedMessage) {
try {
Cipher rsa;
rsa = Cipher.getInstance("RSA");
PrivateKey ourKey = getKey("resources/privateKey.der");
rsa.init(Cipher.DECRYPT_MODE, ourKey);
byte[] utf8 = rsa.doFinal(encryptedMessage);
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static PrivateKey getKey(String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
File f = new File(filePath);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
You can't encrypt 2,048 bytes with RSA unless you have an RSA key of over 16K. You probably have a 2048 bit RSA key, which limits you to under 256 bytes. Check out the openssl man page for RSA_public_encrypt:
flen must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based
padding modes, less than RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING
and exactly RSA_size(rsa) for RSA_NO_PADDING. The random number
generator must be seeded prior to calling RSA_public_encrypt().
And RSA_size:
RSA_size() returns the RSA modulus size in bytes. It can be used to
determine how much memory must be allocated for an RSA encrypted
value.
You should not encrypt a full 256 bytes with a 2048 bit key, though. You want to always use random padding with RSA encryption, and choose OAEP over v1.5.
I have this public key:
MIGJAoGBAKv4OKlpY2oq9QZPMzAjbQfiqDqTnisSvdLP+mTswZJdbtk1J+4+qAySJuZjSQljzcUu0ANg+QG0VsvoU72zu5pErZKWubfe9HB/tq69bhP60qgP6/W2VebWlqUNGtsMedxuVaFBL3SoqU7e5RELIsuArCJJIgz86BQDX0x63VpXAgMBAAE=
I am trying to use it to decode this:
Zm/qR/FrkzawabBZYk7WfQJNMVZoZrwWTvfQwIhPMzAuqEO+y+sb/x9+TZwTbqmu45/GV4yhKv0bbDL8F6rif7RJap7iQUFQBDEIAraY42IGZ8pB6A0Q0RSnJWW+tLTLJg5cTrgZQ8sLoO+U03T6DE1wy73FU5h6XhXxZERo0tQ=
In which I know the unencrypted value is this:
2ABB43E83F7EC33D0D33F64BA5782E42
I have been trying several different things including Bouncy Castle (Java implementation) but I am unable to get the public key to work, mostly ending in invalid encoding errors.
This is my current implementation:
byte[] keyBytes = Base64.decodeBase64(PUB_KEY);
try {
AlgorithmIdentifier rsaIdent = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption);
SubjectPublicKeyInfo kInfo = new SubjectPublicKeyInfo(rsaIdent, keyBytes);
ASN1Primitive primKey = kInfo.parsePublicKey();
byte[] encoded = primKey.getEncoded();
byte[] sessionBytes = Base64.decodeBase64("Zm/qR/FrkzawabBZYk7WfQJNMVZoZrwWTvfQwIhPMzAuqEO+y+sb/x9+TZwTbqmu45/GV4yhKv0bbDL8F6rif7RJap7iQUFQBDEIAraY42IGZ8pB6A0Q0RSnJWW+tLTLJg5cTrgZQ8sLoO+U03T6DE1wy73FU5h6XhXxZERo0tQ=");
Security.addProvider(new BouncyCastleProvider());
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
KeyFactory factory = KeyFactory.getInstance(spec.getFormat());
Cipher cipher = Cipher.getInstance("RSA", "BC");
cipher.init(Cipher.DECRYPT_MODE, factory.generatePublic(spec));
// ----- THIS IS WHERE IT BREAKS -----
byte[] decrypted = cipher.doFinal(sessionBytes);
String tada = new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) { ... }
When I get to generate the public key from the factory I get
java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.ASN1Integer
I have tried several other things but all result in the same error above.
Is there something wrong with my public key? What is the correct way to do this?
First of all, your key is PKCS#1 encoded. It's not a SubjectPublicKeyInfo structure required by Java. You can see how to decode it here.
Second, you cannot decrypt with a public key, you need a private key for that.
I've seen a number of similar questions, but nothing has quite worked for me. I am simply trying to convert an RSA public key that's in PEM format that I've retrieved from a server into a PublicKeyin Android. Can anyone point me in the right direction?
EDIT:
I've successfully used the following code to convert the PEM into a PublicKey, but upon encoding a message, I get unexpected output...
public PublicKey getFromString(String keystr) throws Exception
{
// Remove the first and last lines
String pubKeyPEM = keystr.replace("-----BEGIN PUBLIC KEY-----\n", "");
pubKeyPEM = pubKeyPEM.replace("-----END PUBLIC KEY-----", "");
// Base64 decode the data
byte [] encoded = Base64.decode(pubKeyPEM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubkey = kf.generatePublic(keySpec);
return pubkey;
}
public String RSAEncrypt(final String plain) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
if (pubKey!=null) {
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
Log.d("BYTES", new String(encryptedBytes));
return Hex.encodeHexString(encryptedBytes);
}
else
return null;
}
The output looks like this:
b6813f8791d67c0fa82890d005c8ff554b57143b752b34784ad271ec01bfaa9a6a31e7ae08444baef1585a6f78f3f848eecb1706bf7b2868fccefc9d728c30480f3aabc9ac5c3a9b4b3c74c2f7d6f0da235234953ea24b644112e04a2ec619f6bf95306ef30563c4608ec4b53ed7c15736d5f79c7fa1e35f2444beb366ae4c71
when I expect something closer to:
JfoSJGo1qELUbpzH8d4QXtafup+J2F9wLxHCop00BQ4YS0cRdRCKDfHpFPZQYjNeyQj00HwHbz+vj8haTPbpdqT94AHAl+VZ+TPAiUw1U5EXLLyy4tzbmfVI7CwvMm26lwB4REzYUZdedha1caxMEfxQ5duB+x4ol9eRZM/savg=
Is there some formatting or file type that I'm missing?
To answer my own question...The first output is in hex and the second output is in base 64. Just change the return statement to return new String(Base64.encode(encryptedBytes));
and you'll be good!
This doesn't answer the question, but I find the content relevant. Posting as an answer because it doesn't fit as a comment.
PEM vs DER
PEM basically encapsulates a DER-encoded certificate or key.
DER is binary, PEM is text; so PEM can easily be copy-pasted to an email, for example.
What PEM does is:
Encode the DER certificate or key using Base64, and
Delimit the result with -----BEGIN <something>----- and -----END <something>-----.
The key or certificate is the same, just represented in a different format.
Mostly paraphrasing from ASN.1(wiki).
DER to Android/Java public key
The following is an example of how to use a key factory in order to
instantiate a DSA public key from its encoding. Assume Alice has
received a digital signature from Bob. Bob also sent her his public
key (in encoded format) to verify his signature. Alice then performs
the following actions:
X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);
...
Note that bobEncodedPubKey is DER-encoded in this sample.
https://developer.android.com/reference/java/security/KeyFactory
PEM to Android/Java public key
Similar to what is done for DER, but do the following beforehand:
Remove the BEGIN/END delimitation, and
Decode the content in Base64 to obtain the original DER.
(The question already shows code on how to do this.)
Background:
I have created an applet to extract public key of a certificate extracted from a smart card.
This public key is then stored in database.
The private key of certificate is used to sign data and the public key is then used to verify the signature.
Code for extracting public key from certificate:
private byte[] getPublicKey(KeyStore paramKeyStore)
throws GeneralSecurityException {
Enumeration localEnumeration = paramKeyStore.aliases();
if (localEnumeration.hasMoreElements()) {
String element = (String) localEnumeration.nextElement();
Certificate[] arrayOfCertificate =
paramKeyStore.getCertificateChain(element);
byte[] publicKeyByteArray =
arrayOfCertificate[0].getPublicKey().getEncoded();
return publicKeyByteArray;
}
throw new KeyStoreException("The keystore is empty!");
}
This publicKeyByteArray is then storeed in database as BLOB after converting to string using bytes2String method:
private static String bytes2String(byte[] bytes) {
StringBuilder string = new StringBuilder();
for (byte b : bytes) {
String hexString = Integer.toHexString(0x00FF & b);
string.append(hexString.length() == 1 ? "0" + hexString : hexString);
}
return string.toString();
}
The content of the BLOB(key) saved in database is:
30820122300d06092a864886f70d01010105000382010f003082010a02820101009bd307e4fc38adae43b93ba1152a4d6dbf82689336bb4e3af5160d16bf1599fe070f7acbfefd93e866e52043de1620bd57d9a3f244fb4e6ef758d70d19e0be86e1b12595af748fbc00aad9009bd61120d3348079b00af8462de46e254f6d2b092cbc85c7f6194c6c37f8955ef7b9b8937a7e9999541dbbea8c1b2349c712565482dbd573cd9b7ec56a59e7683b4c246620cf0d8148ed38da937f1e4e930eb05d5b4c6054712928fa59870763468c07e71265525e1e40839b51c833579f5742d3c8e0588766e3ed6deef1593b10baad0a2abea34734de1505d37710e1cfaa4225b562b96a6a4e87fecb1d627d4c61916e543eba87054ee9212e8183125cdb49750203010001
After reading the stored public key byte[] from database, I try to convert it back to Public Key using following code:
Cipher rsa;
rsa = Cipher.getInstance("RSA");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pkey.getBytes());
PublicKey pk = keyFactory.generatePublic(publicKeySpec);
rsa.init(Cipher.DECRYPT_MODE, pk);
byte[] cipherDecrypt = rsa.doFinal(encryptedText.getBytes());
but it gives following error:
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.x509.X509Key.decode(X509Key.java:387)
at sun.security.x509.X509Key.decode(X509Key.java:403)
at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:83)
at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:298)
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:201)
Please suggest the reason and resolution for this issue.
You must have an error in the way you read the key back from the database. The following code works just fine for me:
String key = "3082012230..."; // full key omitted for brevity
byte[] derPublicKey = DatatypeConverter.parseHexBinary(key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(derPublicKey);
keyFactory.generatePublic(publicKeySpec);
I would guess, based on the use of pkey.getBytes(), that you've simply tried to get the bytes from the string rather than hex-decoding it.
I have this strange error which I can't find a solution to fix.
So I generate a public-private key pair, convert it to byte-array and after that recover the original key from that by array. No errors
{
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey pub = keyPair.getPublic();
byte[] pubBytes = pub.getEncoded();
try
{
// to recover the key
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pub_recovered = kf.generatePublic(new X509EncodedKeySpec(pubBytes));
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
System.out.println("Finish");
After that I repeat the same steps, but before recovering the key, I convert the byte array to string and after that back. In this case I get error.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.generateKeyPair();
PublicKey pub = keyPair.getPublic();
byte[] pubBytes = pub.getEncoded();
try
{
String pub1 = new String(pubBytes, "UTF-8");
byte[] pub2 = pub1.getBytes("UTF-8");
// to recover the key
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pub_recovered = kf.generatePublic(new X509EncodedKeySpec(pub2));
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
The error occurs a the line:
PublicKey pub_recovered = kf.generatePublic(new X509EncodedKeySpec(pub2));
and it says:
java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=111, too big.
Does anyone know how to fix and why in general this happens? The message says invalid key exception, however since it worked i the first case, I am more inclined that something wrong happens during byte conversion. It does not make sense at all.
A key contains arbitrary bytes. And those bytes don't necessarily represent valid UTF8 characters. So you shouldn't transform them to a String, because that is a lossy conversion. If you really need a String, then use Hex or Base64 encoding.
To make you realize what the error is, let's say that you use ASCII instead of UTF8. ASCII characters go from 0 to 127. All the other 128 byte values don't represent valid characters. So if the key contain any of those values, the transformation to a String loses them. It's basically the same thing with any encoding (except, IIRC, ISO8859-1).
I encountered a similar problem beforeļ¼ i forgot to import the configuration file which contains the key in project, my configuration file is conf/XX.properties