[英]Receive byte array from server in ksoap2 Android client
再會。
我正在嘗試在服務器和 Android 應用程序之間實施 RSA 加密。 我的目標是:
在 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.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
}
}
問題
我這里的問題是我得到的響應是一個字符串,正如您在這一行中看到的:
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.