How do I RSA encrypt a string with a plaintext key using Java BouncyCastle API on Android
I am trying to encrypt a string using the BouncyCastle API in Android to send off to a server.
I have the public key in plaintext (in memory, not in the filesystem, of course! no need to yell at me, cryptographers ;) ) and I need to use this plaintext public key to encrypt a string to an RSA encrypted string.
This is my class:
public class RSAEncryptor {
//Get certificate from base64 string
public static X509Certificate getCertificateFromBase64String(String string)
throws CertificateException, javax.security.cert.CertificateExce开发者_如何学Cption
{
if(string.equals("") || string == null) {
return null;
}
byte[] certBytes = Base64.decode(string.getBytes(), Base64.DEFAULT);
X509Certificate cert = X509Certificate.getInstance(certBytes);
return cert;
}
//Get public key from base64 encoded string
public static PublicKey getPublicKeyFromEncodedCertData(String encodedCertData)
throws CertificateException, javax.security.cert.CertificateException
{
if(encodedCertData == null || encodedCertData.equals("")) return null;
X509Certificate cert = getCertificateFromBase64String(encodedCertData);
if(cert == null) return null;
return cert.getPublicKey();
}
public static String rsaEncrypt(String plainText, String keyFromResources)
throws NoSuchAlgorithmException, InvalidKeySpecException,
IOException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException //
{
if(plainText == null || plainText.equals("")) return null;
if(keyFromResources == null || keyFromResources.equals("")) return null;
byte[] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = null;
try {
publicKey = getPublicKeyFromEncodedCertData(keyFromResources);
}
catch(Exception ex) {
Logger.LogError("getPublicKeyFromEncodedCertData()", ex);
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainText.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
}
I'm currently not getting the properly encrypted string back out, just a garbled mess like this:
��RB��%����I��Q��F*�bd[@�y�_H]T{KƾuTN�Q�
��U�f��]�S
�q|.t�t�9�Rˇ�����)��{�},ޱ�ª�ǥ#���@k=�WO���f�7t"yP�z�
(The <?>
's are null chars)
I should be getting back an alphanumeric string similar to this:
2+tSXez8JrAIX+VJ2Dy4IsA56XhWpTwF8X2yGGaI6novucXknwykDyqJZICpmYcqx75qBRgxwrW2kY9LmQR2xU17PLqTukAu2Bna8WXYTmJJQ7CWsN3SdABlETRfsYA+g3A2rO2Qp6aR9OCBcFVJpnZJjb9kaOUj5Pcj0tNPFdM=
(changed obviously from the actual response :D)
I'd appreciate any help!
Thanks!
Has anyone done this before? I'd love any suggestions you have as to how to fix this.
Problems in the code:
String encrypted = new String(encryptedBytes);
Bad idea! Cipher#doFinal
returns a byte[]
for a good reason. It looks like random data - turning this into a String will make a mess for sure, because the platform default encoding (UTF-8 in most cases) will interpret the random bytes wrong almost with certainty. So if you want to have a String from encrypted data, then you should Base64- or Hex-encode the byte array.
From what you said you were expecting I would say you want Base64-encoded data, so you should Base64-encode your Cipher's output.
Then, encrypting a string (is this real, human-readable text?) is also less than optimal. Highly vulnerable, the reduced entropy plus the characteristics of the ECB mode (this is used by the RSA cipher) lower the security of your solution drastically.
A plain RSA cipher should never be used to encrypt data that is larger than one block (i.e. larger than the key size of your RSA key) and also only if the data is cryptographically secure random. In 99% of all cases this is only given for symmetric keys used for symmetric Ciphers such as AES etc.
Use RSA for nothing else than symmetric key wrapping and digital signatures, in all remaining cases where you want to actually encrypt sensitive data, you use a symmetric cipher, AES is a good choice - 128 or 256 bits doesn't really matter.
The workflow would look like this:
Generate a symmetric key for AES (16/32 bytes if you use AES-128/256). Now you would RSA-encrypt this symmetric key and nothing else using the server's public key and send the key to the server, then encrypt your private data using AES and the symmetric key, the server would decrypt the symmetric key using its private RSA key and decrypt the packets you send to it.
Use TLS:
Note my use of would. The above is only part of the story. What you just invented there is a Key Transport Protocol. And unless you are designing those for a living chances are high that you won't get this secure on your first try (as in Man-In-The-Middle-Attacks, Reflection Attacks, Replay Attacks, ...).
That's why in my opinion the only widely available secure option to set up a secure channel between client device and server is to use TLS (the former SSL). The protocol was designed specifically for the purpose of exchanging private data with one-way (server only) or two-way authentication (client and server) (authentication is the part where you would use RSA for in your case - configuring the "server certificate").
It has been hardened for years and revised a couple of times to withstand all known attacks on such protocols. And I know, there are messages every other day about how "SSL" has been broken by this or that person, but still, if you set it up carefully it's as secure as it gets for mere mortals without extensive experience in protocol design.
And the nice thing is you only have to configure it on the server (which is quite easy compared to inventing a protocol from scratch) to be able to use fully encrypted, secure communication between both client and server. If you set up a certificate/key pair on the server that was bought from a "public CA" then using TSL is completely transparent to your clients - they merely change their access URL from 'http' to 'https' - the server's certificate will be automatically trusted by being able to identify it in a certificate path that leads to one of the root certificates kept in Java's default trust store cacerts
.
精彩评论