Good day.
I'm trying to implement RSA encryption between server and Android app. My goal is:
- Share the public key with the app.
- The application performs encryption and sends the encrypted text to the server.
- The server decrypts the text with the private key.
Before the Android app, I did the same steps but with a Java Application, and it worked like a charm. But the thing is how the client was made in that application... On the server side, I did a Soap web service that returns the array of bytes of the public key:
@WebMethod(operationName = "getKey")
public byte[] getPublicKey() {
try {
// Message Context
MessageContext mctx = wsctx.getMessageContext();
// Getting parameter services
ParameterServices params = ParameterServices.NewParameterServices(getHttpServletRequestFromMsgContext(mctx), "Dumb Payload");
// Initializing KEYGEN Object
initKeyGen(params);
// Checking if there are no keys created
KEYGEN.updateKeys();
// Creating and sending public key
return KEYGEN.generatePublicKey();
} catch (Throwable e) {
LOGGER.error("Exception in KeyService", e);
return new byte[0];
}
}
In the Java Application, I created the client Interface with javax.jws like this:
@WebService(targetNamespace = "http://my.targetname.value/")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface KeyService {
@WebMethod
byte[] getPublicKey();
}
And this is the part where I'm pretty sure that is the problem. How I retrieve the array of bytes to make the public key it´s something like this:
try {
URL url = new URL(WSDLURL);
QName qname =
new QName(TARGETNAMESPACE, SERVICENAME);
Service service = Service.create(url, qname);
KeyService keyserv = service.<KeyService>getPort(KeyService.class);
byte[] key = keyserv.getPublicKey();
X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey = kf.generatePublic(spec);
return true;
} catch (MalformedURLException|java.security.NoSuchAlgorithmException|java.security.spec.InvalidKeySpecException e) {
e.printStackTrace();
return false;
}
This line:
byte[] key = keyserv.getPublicKey();
I'm not doing anything with the bytes, I'm just fetching it and done, whatever is throwing the method java.security.Key.getEncoded() from the server.
ANDROID KOTLIN VERSION
First of all, I tried to import JAXB to android, and I died during the attempt. Later I found the android library ksoap2 from this question. The implementation's almost the same, also the dependencies (ksoap2-android-2.5.2, kxml2-2.2.1...)
class KeyServiceKSoap {
val NAMESPACE = "http://my.targetname.value/"
val URL = "http://192.168.0.11:8080/RSATest/keyService?wsdl"
val METHOD_NAME = "obtainKey"
val SOAP_ACTION = "http://my.targetname.value/RSATest/obtainKeyRequest"
private var thread: Thread? = null
fun fetchByteArray(getKey : MutableLiveData<String>) {
thread = object : Thread() {
override fun run() {
try {
Log.d("KeyService", "Starting...")
val request = SoapObject(NAMESPACE, METHOD_NAME)
val envelope = SoapSerializationEnvelope(
SoapEnvelope.VER12
)
envelope.env = "http://schemas.xmlsoap.org/soap/envelope/"
envelope.setOutputSoapObject(request)
envelope.headerOut = Array(1) { buildAuthHeaders() }
val androidHttpTransport = HttpTransportSE(
URL,
30000
)
androidHttpTransport.call(SOAP_ACTION, envelope)
val objectResult: SoapObject = envelope.bodyIn as SoapObject
getKey.postValue(objectResult.getProperty("return").toString())
} catch (sp: SoapFault) {
sp.printStackTrace()
getKey.postValue("FAILURE")
} catch (e: Exception) {
e.printStackTrace()
getKey.postValue("FAILURE")
}
}
}
thread!!.start()
}
private fun buildAuthHeaders() : Element {
val authorization = Element().createElement(NAMESPACE, "Authorization")
authorization.addChild(Node.TEXT, "BEARER")
return authorization
}
}
THE PROBLEM
My problem here is that the response I'm getting it as a String, as you can see in this line:
objectResult.getProperty("return").toString()
If you check the Java Application client, I'm getting the array value directly, and not as a String. The issue here is that I don't know what kind of encoding is doing the method java.security.Key.getEncoded(). I mean, If I know for example that this method is returning a Base64 encoded byte array, in the android application I just need to decode like this and done:
private fun makeKey(encodedString : String) : Boolean {
return try {
val byteArr = Base64.decode(encodedString, Base64.DEFAULT);
val specifications = X509EncodedKeySpec(byteArr)
val factory: KeyFactory = KeyFactory.getInstance("RSA")
publicKey = factory.generatePublic(specifications)
true
} catch (e : Exception) {
e.printStackTrace()
false
}
}
BUT OBVIOUSLY IS NOT LIKE THIS, if I encrypt and send the encoded string to the server, and the server tries to decrypt this string, I will get javax.crypto.BadPaddingException: Decryption error, since the array of bytes was not encoded with Base64...
Is there a way to receive the byte array in ksoap2 Android like the interface created in the Java Application?
If there is no choice and I have to decode the String (to make the RSA public key in the android app)... what would be the right way to do it?