Android端ksoap2接收服务器RSA公钥字节数组的问题
我正尝试在服务器与Android应用间实现RSA加密,目标如下:
- 向应用共享公钥;
- 应用执行加密并将密文发送至服务器;
- 服务器用私钥解密。
此前在Java应用中已完成相同步骤,运行正常。服务器端实现了一个SOAP Web服务返回公钥字节数组,代码如下:
@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]; } }
Java客户端通过javax.jws创建接口,直接获取字节数组生成公钥,代码如下:
@WebService(targetNamespace = "http://my.targetname.value/") @SOAPBinding(style = SOAPBinding.Style.RPC) public interface KeyService { @WebMethod byte[] getPublicKey(); }
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; }
这里直接使用服务器端java.security.Key.getEncoded()返回的字节数组即可,无需额外处理。
Android Kotlin实现(ksoap2库)
尝试引入JAXB失败后,采用ksoap2库(依赖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 } }
问题点
Android端通过objectResult.getProperty("return").toString()获取到的是字符串,而Java客户端能直接获取字节数组。尝试用Base64解码后,服务器解密时出现javax.crypto.BadPaddingException: Decryption error,说明并非Base64编码。
疑问:
- 在Android的ksoap2中能否像Java客户端那样直接接收字节数组?
- 若必须将字符串转换为字节数组,正确的转换方式是什么?
1. 在ksoap2中直接接收字节数组
可以直接接收字节数组,无需转成字符串。ksoap2的SoapObject.getProperty("return")返回的Object实际是SoapPrimitive类型,内部保留了原始字节数据,通过encodedBytes属性即可直接获取:
修改Android端的结果处理代码:
// 替换原有的getKey.postValue(objectResult.getProperty("return").toString()) val resultObj = objectResult.getProperty("return") if (resultObj is SoapPrimitive) { val keyBytes = resultObj.encodedBytes // 用字节数组生成公钥 try { val spec = X509EncodedKeySpec(keyBytes) val kf = KeyFactory.getInstance("RSA") val publicKey = kf.generatePublic(spec) // 后续处理公钥,比如更新LiveData getKey.postValue("SUCCESS") } catch (e: Exception) { getKey.postValue("FAILURE") e.printStackTrace() } } else { getKey.postValue("FAILURE") }
原理:服务器返回的byte[]在SOAP协议中会被序列化为base64Binary类型,ksoap2的SoapPrimitive会自动解析并保留原始字节数组,不需要经过字符串转换。
2. 字符串转字节数组的正确方式
如果因特殊原因只能拿到字符串,需先确认字符串是原始Base64编码内容(而非字节数组的toString()结果,比如[B@xxxxxxx这种对象地址),再用Base64解码:
val base64Str = objectResult.getProperty("return").toString() val keyBytes = Base64.decode(base64Str, Base64.DEFAULT) // 生成公钥逻辑同上
但这种方式仅适用于服务器明确将字节数组转成Base64字符串返回的场景,你的服务器直接返回byte[],优先使用第一种直接获取字节数组的方案。
内容的提问来源于stack exchange,提问作者TwoDent

