You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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编码。

疑问:

  1. 在Android的ksoap2中能否像Java客户端那样直接接收字节数组?
  2. 若必须将字符串转换为字节数组,正确的转换方式是什么?

解决方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 08:25:16