Obviously I am no android or java expert. What I want to do in my Android app is, load data from a server. I already got working this part and sourcecode is attached. But I want to do it in a way which is secure. As a first step, instead of http://thisismyurl.com/a.php?action=get I want to do it with username/password like this: http://username:password#thisismyurl.com/a.php?action=get How would I do it? Should I just add the username and password part to the url?
Lets say I've accomplished that this will not be of any of use, because someone can just open the apk and decompile the sourcecode and get the url and the username/password. so is there a truly secure way of doing that?
I hope I am getting understood here.
String url = "http://thisismyurl.com/a.php?action=get";
String result = Web.executeWeb(url);
public class Web {
public static String executeWeb(final String url) {
final StringBuilder sb = new StringBuilder();
Thread thread = new Thread(new Runnable() {
public void run()
{
try
{
InputStream is = (InputStream) new URL(url).getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String result, line = reader.readLine();
result = line;
while((line=reader.readLine())!=null){
result+=line;
}
sb.append(result);
//System.out.println(result);
//Log.i("My Response :: ", result);
} catch (Exception e)
{
// TODO: handle exception
}
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sb.toString();
}
}
To start with, you should firstly consider what you want to achieve and how and afterwards decide what you need.
Firstly, you have to be clear that malicious users will try to crack your app, and if your app stores financial, personal or other kind of sensitive data, the persistence will increase exponencially.
That being said, a few considerations:
Hardcoding keys into your code is a bad idea. If you do that, it's just matter of time for a cracker to decipher what key have you used.
Hardcoding keys in the URL is even a worse idea. Keep in mind your URL will travel through a lot of places before reaching the end point (your server) and meanwhile anyone who was access to see that traffic will see your credentials, and even without effort as you're sending them unencrypted.
Depending on how will you generate your keys, I'd suggest using either symmetric or asymmetric encryption:
If you plan to store an unique password for all your clients (which is, by the way, also a bad idea, because if the malicious user breaks your key, they might have all your client's information), you could use a symmetric encryption method like AES. You simply encrypt your messages, send them via HTTP POST (for example) and decrypt it on the other side. Pretty easy concept.
If you plan to generate a key for each of your clients, you have the additional handicap that you somehow need to make your server know the key you have generated, or your client know which key has generated for the client (dependind on how you face it). In this context you could use the next points approach (which is basically the one I would recommend from amongst all these).
You could simply use an assymetric encryption method. That means that the server generates a pair of keys, one public and one private. Users (clients) will have the public one to encrypt messages and send them to the server. You might be wondering: And how do I protect my messages so noone can decrypt them but my server? That's where the private key joins, you can just decrypt messages if you have the private key. That's why you don't want to share it with anyone (that's where its name comes from). This way, your clients may have your public key at anytime without any obfuscation needs, then you use it to encrypt some text and send it. The server will decrypt the message using its private key and process it accordingly.
Advantages of this last approach are:
You don't have to worry on how to make your key reach the other party securely, as it will be encrypted and just the server is able to decrypt.
You don't need to generate a key for each of your clients.
If you choose a good asymmetric algorithm such as SSL/TLS, you don't need to worry about its cracking (or at least, not as much as if you had chosen some other approach).
Replacing an old pair of keys is such easy as generating a new pair, replacing the old private key and make your clients have the new public key.
You might want to have a look at these links:
Public-key cryptography
Symmetric-key algorithm
Advanced Encryption Standard (AES)
Transport Layer Security
what I did is that , I used AES encryption for this. whenever user register i send an encryption key and version in the header to the appliation so all communication will be encrypted.server always check for the version of key and then decrypt accordingly. if new key available server send new key to the application and then application update key and then decrypt with that.
i used these method to decrypt and encrypt in android.
public byte[] decrypt(byte[] cipherText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpecy = new SecretKeySpec(key, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec);
cipherText = cipher.doFinal(cipherText);
return cipherText;
}
public byte[] encrypt(byte[] plainText, byte[] key, byte [] initialVector) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
Cipher cipher = Cipher.getInstance(cipherTransformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, aesEncryptionAlgorithm);
IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
plainText = cipher.doFinal(plainText);
return plainText;
}
and in request add header like
request.addHeader("KeyVersion",String.valueOf(utils.getInt(Key.Key_Version)));
request.addHeader("EmpId",String.valueOf(utils.getInt(Key.Emp_Id)));
and when response come i check for new key like
Header[] headers = response.getHeaders("KeyVersion");
if(headers.length>0){
String keyVersion = headers[0].getValue();
if (keyVersion == null) {
System.out.println("Key 'Server' is not found!");
} else {
System.out.println("Key 'Server' found! -- with version "+keyVersion);
if(utils.getInt("KeyVersion")<Integer.parseInt(keyVersion)){
utils.saveInt("KeyVersion", Integer.parseInt(keyVersion));
utils.saveString("Key", response.getHeaders("KeyValue")[0].getValue());
String s = response.getHeaders("KeyValue")[0].getValue();
System.out.println("key is "+s);
}
}
Encryption isn't the answer.
If someone wants the URL, user and password, which are stored in the client, you cannot avoid it. Even if it's encrypted, you have to provide the decryption key to the client, which then could be decompiled itself.
You cannot prevent reverse-engineering of your service interface.
And therefore cannot prevent other clients to use your service interface. It's easy to sniff the network traffic with Fiddler. Even SSL is no problem, because we can manipulate the client itself, before the data becomes encrypted.
Let's see some other SO threads about reverse-engineering.
Following on from nKn's answer:
One way of doing what you want to do is use both asymmetric and symmetric encryption, what you do is this:
Client initiates a connection to the server
Server generates a pair of public and private keys for asymmetric encryption (RSA for example).
The Server sends the public key unecrypted in plaintext to the client
The client generates a new unrelated key for the symmetric encryption (AES for example).
Client uses the public key from before to encrypt the symmetric-algorithm key and send it to the server.
Server decrypts the message using the private key, and now both sides have a common symmetric key to use.
This approach forces the client to generate a new symmetric key every time, so you need your server to keep the correct key for every connection/session. You cannot use this approach if you want a static symmetric key, for this you can use another approach:
Client generates the asymmetric key pair and send the public key to the client.
The Server uses the public key to encrypt the symmetric key and send it to the client.
The Client decrypts the message using the private key, and destroys the private key as soon as possible. - Now both sides share the same symmetric key
With the second approach you can just store the symmetric key on your server and you don't have to store a different key for every connection/session. I would still advise you to periodically change the symmetric key, just to be sure.
Both approaches do not force you to hard code keys into your client code, or send symmetric keys in plaintext.
The only thing that is sent in plaintext is the public key, and that is not a problem at all, hence the name "public key".
String httpsURL = "https://www.abcd.com/auth/login/";
String query = "email="+URLEncoder.encode("abc#xyz.com","UTF-8");
query += "&";
query += "password="+URLEncoder.encode("abcd","UTF-8") ;
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection)myurl.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-length", String.valueOf(query.length()));
con.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
con.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0;Windows98;DigExt)");
con.setDoOutput(true);
con.setDoInput(true);
DataOutputStream output = new DataOutputStream(con.getOutputStream());
output.writeBytes(query);
output.close();
DataInputStream input = new DataInputStream( con.getInputStream() );
for( int c = input.read(); c != -1; c = input.read() )
System.out.print( (char)c );
input.close();
System.out.println("Resp Code:"+con .getResponseCode());
System.out.println("Resp Message:"+ con .getResponseMessage());
First, never work with password itself, work only with hash representation(sha1 function) of password. Second, you can use SSL (https)to establish secure connection.
To sum it up ,after user clicks on login button ,get password from edittext and create hash of it. On server side also use hash representation. Next, send data as body of https request to www.yoursite.com/login and in request body will be your data. Send it as json f.e...
Related
I am making an server API which will return some confidential keys to my app.
Then the app will use these key to perform a particular action. I would be sending the Keys over SSL so that any Man In the Middle attack could not read them.
To start first I will be first everything the Package name and then I also want to verify the something which assures me that my app has not been decompiled and recompiled and the package is not fake.
Basically I want to avoid these issues:
1) Someone is not creating a fake package name and then sending the request
2) Someone has not recompiled my app and then sending the request
3) Someone if not tracking the response of the server via MIM
Till now I have thought the best way would be to use a HASH key and then compare it within my server to see if the POST key is the same as stored in my server.
But I have not been able to find a key which is attached to the signing key of the app and which cannot be accessed by anyone having the APK of my app.
Any help would be grateful.
You can add extra layer of protection if you create keys in your app using C++ code available on android's NDK libraries. Here's an amazing tutorial for that. Basically, this protects your app from de-compiling tools which commonly de-compiles java files. Also, I recommend adding AES encryption on your keys before sending it through the post request of your SSL server.
On your onCreate() method, get the key from native C++ implementation:
String nativeKey = invokeNativeFunction()
then encrypt it:
byte[] keyStart = nativeKey.getBytes();
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(keyStart);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] key = skey.getEncoded();
// encrypt
byte[] encryptedData = encrypt(key,b);
Encrypt method:
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
Edit for CSRF:
There's an interesting answer from here: Authenticity_token in Rails + Android, also on Wikipedia, there are quite suggestions as to how to counter cross site request forgery. which includes:
Synchronizer token pattern
Cookie-to-header token
to name a few.
Here's a layer of extra security to identify the authenticity of the app request as well.
I have a jhipster spring boot application which accepts a token generated by a third party which has been encrypted with our public key. I have a JWTFilter which decrypts the token using our private key and creates an authentication object which is stored in the security context. Once execution reaches the controller, I intend to pull the username and password from the security context so I can make API calls back to the third party application.
This is working to some degree in our integration environment where the third-party has a link to a running instance of our application. To test locally, I am hitting the link in the integration environment, and copying the token. Then I make a request from Postman to an instance of our application I'm running locally with the token added in the headers, the same as our js client would.
I am using "com.nimbusds:nimbus-jose-jwt:4.23" for decryption, and I am getting a 'MAC check failed' error. I can change the value of macCheckPassed to true in the debugger, and the decryption will complete, allowing me to see the claims and load them into the security context. However, some other filter is catching my hack, and the request gets rejected with an authorization error.
public static byte[] decryptAuthenticated(final SecretKey secretKey,
final byte[] iv,
final byte[] cipherText,
final byte[] aad,
final byte[] authTag,
final Provider ceProvider,
final Provider macProvider)
throws JOSEException {
// Extract MAC + AES/CBC keys from input secret key
CompositeKey compositeKey = new CompositeKey(secretKey);
// AAD length to 8 byte array
byte[] al = AAD.computeLength(aad);
// Check MAC
int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).
put(aad).
put(iv).
put(cipherText).
put(al).
array();
byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
byte[] expectedAuthTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
boolean macCheckPassed = true;
if (! ConstantTimeUtils.areEqual(expectedAuthTag, authTag)) {
// Thwart timing attacks by delaying exception until after decryption
macCheckPassed = false;
}
byte[] plainText = decrypt(compositeKey.getAESKey(), iv, cipherText, ceProvider);
if (! macCheckPassed) {
throw new JOSEException("MAC check failed");
}
return plainText;
}
What is this MAC check? I thought it had to do with the origin of the token. Something along the lines of the token being encrypted with the MAC id of the source system, which throws an error when it doesn't synch up with my current host.
What other filter would be rejecting the request if the decryption passed? Is there some other flag I'm supposed to be setting so the framework will honor the request?
JWE spec mandates authenticated encryption, to ensure the plain text is not just encrypted, but also protected against tampering. To ensure that an HMAC is applied after the content encryption.
The "Mac check failed" error can mean two things - the library that produced the original JWE / JWT has applied the HMAC incorrectly, or, the JWE / JWT was modified in transit.
I'm trying to do a login method that keeps the client password secure and encrypted in my server.
The library I'm using from Angular, is https://github.com/middleout/angular-cryptography
The idea is, to follow these steps:
I set a salt in my module.config:
app.config(['$cryptoProvider', function($cryptoProvider){
$cryptoProvider.setCryptographyKey('thisismysalt');
}]);
I encrypt the password with itself:
user.pw = $crypto.encrypt(user.pw, user.pw);
If I'm registering an user, I re-encrypt the password with itself (repeat step 2) and save it in the DB. If I'm logging, I just send the result from the last step, to the server.
When you decypher the double-encrypted string with the single-encrypted one, you get the single encrypted string again. So if your password was correct, you just compare the result with the single-encrypted string, and you validate the user.
Ok, this method should work (I already did it in Node some time ago), works great with SSL to protect user's passwords even in your server!
But I can't find any library or snippet in Java that can do it. I tried many of them, but they are hard to understand and when I adapt them to my procedure, they just won't work. I tried the following method:
static String IV = "AAAAAAAAAAAAAAAA";
static String plaintext = "test text 123\0\0\0"; /*Note null padding*/
static String encryptionKey = "0123456789abcdef";
public static String decrypt(byte[] cipherText, String encryptionKey) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(encryptionKey.getBytes("UTF-8"), "AES");
cipher.init(Cipher.DECRYPT_MODE, key,new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(cipher.doFinal(cipherText),"UTF-8");
}
I passed it as first argument, the double-encrypted password from the DB, and as second argument, the single-encrypted password from the frontend:
java.security.InvalidKeyException: Invalid AES key length: 44 bytes
Am I doing something wrong? Should I use a different algorithm?
middleout/angular-cryptography uses CryptoJS 3.1.2 under the hood with the least effort possible.
So
return $crypto.encrypt(plaintext, password);
is the same as
$cryptoProvider.setCryptographyKey(password);
return $crypto.encrypt(plaintext, password);
and the same as
return CryptoJS.AES.encrypt(plaintext, password).toString();
I describe in my answer here how to do the same thing in Java.
If you're using SSL/TLS, there is not much benefit to doing this encryption additionally. The password is already sent in an encrypted way over the internet. Even worse, since the password must be available at the server side, you must store the password in cleartext. That's not how it is done.
You need to use hashing instead with some strong ones being PBKDF2, bcrypt, scrypt and Argon2. Since hash functions are one-way function, you won't be able to "decrypt" the hashes. In order to authenticate your user, you can run the password through the hash function again in order to compare with the hash that is stored in the database. Since you're already using SSL/TLS, the password is already secured during transmission. See more: How to securely hash passwords?
I need to use AEAD to share information between two users, part of which must be encrypted and part of which should be kept in cleartext.
Is there an API to check the ciphertext tag and access the associated data once a message has been encrypted with AES/GCM ?
In more detail:
I'm using Java 7 with bouncycastle as a provider and I have managed to encrypt and decrypt my data successfully, using the corresponding API:
private byte[] encrypt(SecretKey key, byte[] nonce, byte[] message, byte[] associatedData) throws ... {
Cipher aeadCipher = Cipher.getInstance(AES_GCM_NOPADDING);
aeadCipher.init(Cipher.ENCRYPT_MODE, kint, new GCMParameterSpec(GCM_MAC_SIZE, nonce);
aeadCipher.updateAAD(associatedData);
return aeadCipher.doFinal(message);
}
private byte[] decrypt(SecretKey key, byte[] nonce, byte[] cipherText, byte[] associatedData) throws ... {
Cipher aeadCipher = Cipher.getInstance(AES_GCM_NOPADDING);
aeadCipher.init(Cipher.DECRYPT_MODE, kint, new GCMParameterSpec(GCM_MAC_SIZE, nonce);
aeadCipher.updateAAD(associatedData);
return aeadCipher.doFinal(cipherText);
}
However, it is my understanding that AES/GCM ciphertexts should already contain the parameters that could affect decryption (nonce and associatedData).
Therefore, I would like to be able to retrieve them from the ciphertext, rather than having to store them alongside the ciphertext and pass them along to the decryption function. Furthermore, I'd like to be able to run integrity checks (computing the tag) and run some checks on the associated data without having to completely decrypt the message.
Is there an API that would allow this and that I might have missed ?
So far, I've checked:
The Cipher API: http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
This very informative blog post on AES/GCM implementations in Java: http://blog.philippheckel.com/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/
Since the Java API automatically places the tag at the end, you only have to extract this tag from the your encryption result as follow:
private byte[] getTag(SecretKey key, byte[] nonce, byte[] message, byte[] associatedData) throws ... {
Cipher aeadCipher = Cipher.getInstance(AES_GCM_NOPADDING);
aeadCipher.init(Cipher.ENCRYPT_MODE, kint, new GCMParameterSpec(GCM_MAC_SIZE, nonce);
aeadCipher.updateAAD(associatedData);
byte[] encrypted = aeadCipher.doFinal(message);
// Assuming you have an AAD_SIZE = 128 bits (16 bytes)
return Arrays.copyOfRange (encrypted, encrypted.length-16, encrypted.length)
}
There is no implicit format that stores all the input data of GCM in a specific location. The Java API is already a bit strange in
the sense that it automatically places the tag at the end. This makes the
algorithm more compatible with the Cipher class, but in principle the tag just
needs to be kept with the ciphertext - where does not matter. Now you have the
issue that you don't know where the AAD ends and the ciphertext starts for instance.
So what you can do is either to create your own format (maybe your AAD has a
static size, so you can just concatenate) or you can use a predefined container format.
There is an internet draft
that specifies how to use both modes in the Cryptographic Message Syntax (CMS).
The AAD can then be stored in authenticated atributes, which should also include the required
parameters (including the IV comprising of the nonce).
If you feel masochistic you could also try and use XML-encryption with GCM mode, but beware of the many pitfalls with regard to verifying XML authenticity (e.g. beware that you are actually verifying the data you are going to use).
Bouncy Castle seems to offer support for CMS using GCM.
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.CertificateException
{
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.