在ksoap2 Android客户端中从服务器接收字节数组

w9apscun  于 2023-01-03  发布在  Android
关注(0)|答案(2)|浏览(141)

再见。
我正在尝试在服务器和Android应用程序之间实现RSA加密。我的目标是:
1.与应用共享公钥。
1.应用程序执行加密并将加密文本发送到服务器。
1.服务器使用私钥解密文本。
在Android应用程序之前,我也做了同样的步骤,但使用的是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()

    • 安卓Kotlin版本**

首先,我试着把JAXB导入到android中,在尝试的过程中我死了,后来我从这个问题中找到了android库ksoap2,实现几乎是一样的,依赖也是一样的(ksoap2-android-2.5.2kxml2 - 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
    }
}
    • 问题所在**

我的问题是我得到的响应是一个字符串,就像你在这行看到的:
对象结果. getProperty("返回"). toString()
如果你检查Java应用程序客户端,我直接得到数组值,而不是字符串。这里的问题是,我不知道什么样的编码是做方法java. security. Key. getEncoded()。我的意思是,如果我知道,例如,这个方法是返回一个Base64编码的字节数组,在Android应用程序中,我只需要像这样解码和完成:

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
    }
}
    • 但显然不是这样**,如果我加密并将编码后的字符串发送到服务器,而服务器试图解密此字符串,我将获得javax. crypto. BadPaddingException:解密错误,因为字节数组未使用Base64编码...

是否有方法在ksoap2 Android中接收字节数组,就像在Java应用程序中创建的接口一样?
如果别无选择,我必须解码字符串(在Android应用程序中生成RSA公钥)...什么是正确的方法?

db2dz4w8

db2dz4w81#

我为您编写了一些代码,您可以在https://github.com/XSComSoft/stack-overflow中看到完整的代码
在测试代码包org.exmaple.Stack74979278的主副本和两个副本中,分别包含客户端代码和服务器代码
如果使用idea作为maven项目打开,则可以在类org.example.stack74979278.Main上简单地运行测试
通信只需要依靠java jws
下面是一些代码片段

密钥服务.java

@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface KeyService {
@WebMethod
@WebResult(name = "get")
byte[] getPublicKey();

@WebMethod
@WebResult(name = "get")
String getDecodeByPrivateKey(String base64) throws Exception;

}

密钥服务实现.java

@WebService(endpointInterface = "org.example.stack74979278.KeyService")
public class KeyServiceImpl implements KeyService{

    private RSAPublicKey puk;
    private RSAPrivateKey prk;

    public byte[] getPublicKey() {
        return puk.getEncoded();
    }

    @Override
    public String getDecodeByPrivateKey(String base64) throws Exception {
        return RSAUtils.decryptByPrivateKey(base64, prk);
    }

    public KeyServiceImpl() throws Exception {
        //        List<Key> keyList = RSAUtils.getRSAKeyObject(1024);
        //        puk = (RSAPublicKey) keyList.get(0);
        //        prk = (RSAPrivateKey) keyList.get(1);
        Properties properties = new Properties();
        properties.load(KeyServiceImpl.class.getResourceAsStream("/stack74979278/keys.properties"));

        puk = RSAUtils.getPublicKey(properties.getProperty("public"));
        prk = RSAUtils.getPrivateKey(properties.getProperty("private"));

        String publicKey = Base64.getEncoder().encodeToString(puk.getEncoded());
        String privateKey = Base64.getEncoder().encodeToString(prk.getEncoded());
        System.out.println("publicKey");
        System.out.println(publicKey);

        System.out.println("privateKey");
        System.out.println(privateKey);
    }

    public static void main(String[] args) throws Exception {
        Endpoint.publish("http://localhost/test", new KeyServiceImpl());
    }
}

RSAUtil.java语言

public class  RSAUtils {
 
    // 加密算法
    private final static String ALGORITHM_RSA = "RSA";
 
    /**
     * 直接生成公钥、私钥对象
     *
     * @param modulus
     *
     * @throws NoSuchAlgorithmException
     *
     */
    public static List<Key> getRSAKeyObject(int modulus) throws NoSuchAlgorithmException{
 
        List<Key> keyList = new ArrayList<>(2);
        // 创建RSA密钥生成器
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
        // 设置密钥的大小,此处是RSA算法的模长 = 最大加密数据的大小
        keyPairGen.initialize(modulus);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // keyPair.getPublic() 生成的是RSAPublic的是咧
        keyList.add(keyPair.getPublic());
        // keyPair.getPrivate() 生成的是RSAPrivateKey的实例
        keyList.add(keyPair.getPrivate());
        return keyList;
    }
 
    /**
     * 生成公钥、私钥的字符串
     * 方便传输
     *
     * @param modulus 模长
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static List<String> getRSAKeyString(int modulus) throws NoSuchAlgorithmException{
 
        List<String> keyList = new ArrayList<>(2);
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
        keyPairGen.initialize(modulus);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        keyList.add(publicKey);
        keyList.add(privateKey);
        return keyList;
    }

    public static RSAPublicKey getPublicKey(String publicKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        return (RSAPublicKey) keyFactory.generatePublic(spec);
    }

    public static RSAPublicKey getPublicKey(byte[] keyBytes) throws Exception {
        RSAPublicKey publicKey =
                (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
        return publicKey;
    }

    public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        return getPrivateKey(keyBytes);
    }

    public static RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        return (RSAPrivateKey) keyFactory.generatePrivate(spec);
    }
 
    /**
     * 公钥加密
     *
     * @param data
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey(String data, RSAPublicKey publicKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 模长n转换成字节数
        int modulusSize = publicKey.getModulus().bitLength() / 8;
        // PKCS Padding长度为11字节,所以实际要加密的数据不能要 - 11byte
        int maxSingleSize = modulusSize - 11;
        // 切分字节数组,每段不大于maxSingleSize
        byte[][] dataArray = splitArray(data.getBytes(), maxSingleSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 分组加密,并将加密后的内容写入输出字节流
        for (byte[] s : dataArray) {
            out.write(cipher.doFinal(s));
        }
        // 使用Base64将字节数组转换String类型
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }
 
    /**
     * 私钥解密
     *
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // RSA加密算法的模长 n
        int modulusSize = privateKey.getModulus().bitLength() / 8;
        byte[] dataBytes = data.getBytes();
        // 之前加密的时候做了转码,此处需要使用Base64进行解码
        byte[] decodeData = Base64.getDecoder().decode(dataBytes);
        // 切分字节数组,每段不大于modulusSize
        byte[][] splitArrays = splitArray(decodeData, modulusSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for(byte[] arr : splitArrays){
            out.write(cipher.doFinal(arr));
        }
        return new String(out.toByteArray());
    }
 
    /**
     * 按指定长度切分数组
     *
     * @param data
     * @param len 单个字节数组长度
     * @return
     */
    private static byte[][] splitArray(byte[] data,int len){
 
        int dataLen = data.length;
        if (dataLen <= len) {
            return new byte[][]{data};
        }
        byte[][] result = new byte[(dataLen-1)/len + 1][];
        int resultLen = result.length;
        for (int i = 0; i < resultLen; i++) {
            if (i == resultLen - 1) {
                int slen = dataLen - len * i;
                byte[] single = new byte[slen];
                System.arraycopy(data, len * i, single, 0, slen);
                result[i] = single;
                break;
            }
            byte[] single = new byte[len];
            System.arraycopy(data, len * i, single, 0, len);
            result[i] = single;
        }
        return result;
    }
 
 
}

客户端生成器为cmd wsimport -keep -p org.example.stack74979278 http://localhost/test?wsdl,详见a

客户端测试类

@Log
public class Main {

    @Test
    public void getPublicKey(){
        byte[] bytes = new KeyServiceImplService().getKeyServiceImplPort().getPublicKey();
        System.out.println(bytes);
    }

    @Test
    public void getDecode() throws Exception {
        KeyService keyService = new KeyServiceImplService().getKeyServiceImplPort();
        byte[] bytes = keyService.getPublicKey();
        RSAPublicKey publicKey = RSAUtils.getPublicKey(bytes);
        String encrypt = "The message encrypt";
        String str = keyService.getDecodeByPrivateKey(RSAUtils.encryptByPublicKey(encrypt, publicKey));
        log.info(str);
    }

}

现在,您可以获取public key byte for android客户端并将encrypted base64 string发送到服务器

uemypmqf

uemypmqf2#

我不知道该怎么开始,显然,我现在所做的,接收一个字节数组的方法,是正确的。
使用ksoap 2可以获得响应字符串,然后解码String以获得字节数组。

private fun makeKey(encodedString : String) : Boolean {
    return try {
        val byteArr = Base64.decode(encodedString, Base64.DEFAULT) <--- OK
        val specifications = X509EncodedKeySpec(byteArr)
        val factory: KeyFactory = KeyFactory.getInstance("RSA")
        publicKey = factory.generatePublic(specifications)
        true
    } catch (e : Exception) {
        e.printStackTrace()
        false
    }
  }

我为什么这么肯定呢?嗯,是因为让我头疼的事情和编码无关。而是我如何初始化Cipher。正如我之前所说,对于Java应用程序,一切都正常工作,但是当在Android应用程序中使用相同的实现时(Java应用程序〈=〉Android应用程序),解密失败。
我初始化密码的过程是这样的:

Cipher.getInstance("RSA")

根据这个question的评论如果你这样初始化它,会发生什么是,Android应用程序和服务器将使用它们的独立实现.这就是为什么它总是在解密失败的时刻.

为了让一切变得清晰

如果您尝试执行与我相同的操作,请尝试在两端(服务器和应用程序)使用RSA/ECB/PKCS 1 Padding初始化所有密码

Cipher.getInstance("RSA/ECB/PKCS1Padding")

其他的东西,如KeyFactory和KeyPairGenerator,只能用“RSA”初始化。

KeyPairGenerator.getInstance("RSA")

KeyFactory.getInstance("RSA")

P.D:我从来没有在我的SOAP服务中将键的字节数组编码为Base64......这让我认为在某些时候默认使用Base64编码

相关问题