簡體   English   中英

在 ksoap2 Android 客戶端中從服務器接收字節數組

[英]Receive byte array from server in ksoap2 Android client

再會。

我正在嘗試在服務器和 Android 應用程序之間實施 RSA 加密。 我的目標是:

  1. 與應用共享公鑰。
  2. 應用程序執行加密並將加密的文本發送到服務器。
  3. 服務器使用私鑰解密文本。

在 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;
    } 

這一行:

byte[] key = keyserv.getPublicKey();

我沒有對字節做任何事情,我只是獲取它並完成,不管是從服務器拋出方法java.security.Key.getEncoded()

ANDROID 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
    }
}

問題

我這里的問題是我得到的響應是一個字符串,正如您在這一行中看到的:

objectResult.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: Decryption error ,因為字節數組沒有用 Base64 編碼...

有沒有辦法在ksoap2 Android中接收字節數組,就像在 Java 應用程序中創建的接口一樣?

如果別無選擇,我必須解碼字符串(在 android 應用程序中生成 RSA 公鑰)......正確的方法是什么?

我給你寫了一些代碼,你可以在https查看完整代碼://github.com/XSComSoft/stack-overflow

在測試代碼package org.exmaple.Stack74979278的主體和兩份分別包含客戶端代碼和服務端代碼

你可以簡單地在 class org.example.stack74979278.Main運行測試,如果你用idea作為 maven 項目打開它

通信只需要依賴java jws

下面是一些代碼片段

KeyService.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;

}

KeyServiceImpl.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

客戶端測試 class

@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);
    }

}

現在您可以獲得 android 客戶端的public key byte並將encrypted base64 string發送給服務器

好吧,我不知道如何開始。 顯然,像我現在所做的那樣接收字節數組的方法是正確的

使用ksoap2 ,您可以獲得響應字符串,然后解碼該字符串以獲取您的字節數組。

  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")

根據這個問題的評論如果您以這種方式初始化它,將會發生的情況是 Android 應用程序和服務器都將使用它們的獨立實現。 這就是為什么它在解密時總是失敗的原因。

為了讓所有事情都清楚:

如果你想和我一樣,嘗試在兩側(服務器和應用程序)用RSA/ECB/PKCS1Padding初始化所有密碼

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

KeyFactory 和 KeyPairGenerator 等其他東西只能使用“RSA”進行初始化。

KeyPairGenerator.getInstance("RSA")

KeyFactory.getInstance("RSA")

PD:我從來沒有在我的 SOAP 服務中將密鑰的字節數組編碼為 Base64 ...這讓我認為 base64 編碼在某些時候默認完成

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM