From 6f679e361111995ed19742465b8d8a84136ec168 Mon Sep 17 00:00:00 2001 From: FongMi Date: Thu, 5 Oct 2023 00:31:26 +0800 Subject: [PATCH] Support rsa and multipart --- .../com/fongmi/quickjs/method/Global.java | 39 ++++----- .../com/fongmi/quickjs/utils/Connect.java | 17 +++- .../java/com/fongmi/quickjs/utils/Crypto.java | 82 +++++++++++++++++++ 3 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java diff --git a/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java b/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java index a3fb824aa..149eea4ac 100644 --- a/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java +++ b/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java @@ -1,16 +1,16 @@ package com.fongmi.quickjs.method; -import android.util.Base64; - import androidx.annotation.Keep; import androidx.annotation.NonNull; import com.fongmi.quickjs.bean.Req; import com.fongmi.quickjs.utils.Connect; +import com.fongmi.quickjs.utils.Crypto; import com.fongmi.quickjs.utils.JSUtil; import com.fongmi.quickjs.utils.Parser; import com.fongmi.quickjs.utils.Proxy; import com.github.catvod.utils.Trans; +import com.orhanobut.logger.Logger; import com.whl.quickjs.wrapper.JSArray; import com.whl.quickjs.wrapper.JSFunction; import com.whl.quickjs.wrapper.JSMethod; @@ -20,15 +20,10 @@ import com.whl.quickjs.wrapper.QuickJSContext; import java.io.IOException; import java.lang.reflect.Method; import java.net.URLEncoder; -import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; @@ -105,7 +100,7 @@ public class Global { public JSObject _http(String url, JSObject options) { JSFunction complete = options.getJSFunction("complete"); if (complete == null) return req(url, options); - Req req = Req.objectFrom(ctx.stringify(options)); + Req req = Req.objectFrom(options.stringify()); Connect.to(url, req).enqueue(getCallback(complete, req)); return null; } @@ -114,7 +109,7 @@ public class Global { @JSMethod public JSObject req(String url, JSObject options) { try { - Req req = Req.objectFrom(ctx.stringify(options)); + Req req = Req.objectFrom(options.stringify()); Response res = Connect.to(url, req).execute(); return Connect.success(ctx, req, res); } catch (Exception e) { @@ -155,21 +150,17 @@ public class Global { @Keep @JSMethod public String aesX(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { - try { - byte[] keyBuf = key.getBytes(); - if (keyBuf.length < 16) keyBuf = Arrays.copyOf(keyBuf, 16); - byte[] ivBuf = iv == null ? new byte[0] : iv.getBytes(); - if (ivBuf.length < 16) ivBuf = Arrays.copyOf(ivBuf, 16); - Cipher cipher = Cipher.getInstance(mode + "Padding"); - SecretKeySpec keySpec = new SecretKeySpec(keyBuf, "AES"); - if (iv == null) cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec); - else cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBuf)); - byte[] inBuf = inBase64 ? Base64.decode(input, Base64.DEFAULT) : input.getBytes("UTF-8"); - return outBase64 ? Base64.encodeToString(cipher.doFinal(inBuf), Base64.DEFAULT) : new String(cipher.doFinal(inBuf), "UTF-8"); - } catch (Exception e) { - e.printStackTrace(); - return ""; - } + String result = Crypto.aes(mode, encrypt, input, inBase64, key, iv, outBase64); + Logger.t("aesX").d("mode:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:%s\niv:%s\ninput:\n%s\nresult:\n%s", mode, encrypt, inBase64, outBase64, key, iv, input, result); + return result; + } + + @Keep + @JSMethod + public String rsaX(String mode, boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + String result = Crypto.rsa(pub, encrypt, input, inBase64, key, outBase64); + Logger.t("rsaX").d("mode:%s\npub:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:\n%s\ninput:\n%s\nresult:\n%s", mode, pub, encrypt, inBase64, outBase64, key, input, result); + return result; } private Callback getCallback(JSFunction complete, Req req) { diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java index 2f0775fce..09cf21c35 100644 --- a/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java @@ -10,11 +10,13 @@ import com.whl.quickjs.wrapper.QuickJSContext; import java.util.List; import java.util.Map; +import java.util.Random; import okhttp3.Call; import okhttp3.FormBody; import okhttp3.Headers; import okhttp3.MediaType; +import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; @@ -63,6 +65,7 @@ public class Connect { private static RequestBody getPostBody(Req req, String contentType) { if (req.getData() != null && req.getPostType().equals("json")) return getJsonBody(req); if (req.getData() != null && req.getPostType().equals("form")) return getFormBody(req); + if (req.getData() != null && req.getPostType().equals("form-data")) return getFormDataBody(req); if (req.getBody() != null && contentType != null) return RequestBody.create(req.getBody(), MediaType.get(contentType)); return RequestBody.create("", null); } @@ -72,10 +75,18 @@ public class Connect { } private static RequestBody getFormBody(Req req) { - FormBody.Builder formBody = new FormBody.Builder(); + FormBody.Builder builder = new FormBody.Builder(); Map params = Json.toMap(req.getData()); - for (String key : params.keySet()) formBody.add(key, params.get(key)); - return formBody.build(); + for (String key : params.keySet()) builder.add(key, params.get(key)); + return builder.build(); + } + + private static RequestBody getFormDataBody(Req req) { + String boundary = "--dio-boundary-" + new Random().nextInt(42949) + "" + new Random().nextInt(67296); + MultipartBody.Builder builder = new MultipartBody.Builder(boundary).setType(MultipartBody.FORM); + Map params = Json.toMap(req.getData()); + for (String key : params.keySet()) builder.addFormDataPart(key, params.get(key)); + return builder.build(); } private static void setHeader(QuickJSContext ctx, Response res, JSObject object) { diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java new file mode 100644 index 000000000..45f28f776 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java @@ -0,0 +1,82 @@ +package com.fongmi.quickjs.utils; + +import android.util.Base64; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class Crypto { + + public static String aes(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { + try { + byte[] keyBuf = key.getBytes(); + if (keyBuf.length < 16) keyBuf = Arrays.copyOf(keyBuf, 16); + byte[] ivBuf = iv == null ? new byte[0] : iv.getBytes(); + if (ivBuf.length < 16) ivBuf = Arrays.copyOf(ivBuf, 16); + Cipher cipher = Cipher.getInstance(mode + "Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyBuf, "AES"); + if (iv == null) cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec); + else cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBuf)); + byte[] inBuf = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + return outBase64 ? Base64.encodeToString(cipher.doFinal(inBuf), Base64.DEFAULT) : new String(cipher.doFinal(inBuf), "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String rsa(boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + try { + Key rsaKey = generateKey(pub, key); + int len = getModulusLength(rsaKey); + byte[] outBytes = new byte[0]; + byte[] inBytes = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey); + int blockLen = encrypt ? len / 8 - 11 : len / 8; + int bufIdx = 0; + while (bufIdx < inBytes.length) { + int bufEndIdx = Math.min(bufIdx + blockLen, inBytes.length); + byte[] tmpInBytes = new byte[bufEndIdx - bufIdx]; + System.arraycopy(inBytes, bufIdx, tmpInBytes, 0, tmpInBytes.length); + byte[] tmpBytes = cipher.doFinal(tmpInBytes); + bufIdx = bufEndIdx; + outBytes = concatArrays(outBytes, tmpBytes); + } + return outBase64 ? Base64.encodeToString(outBytes, Base64.DEFAULT) : new String(outBytes, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + private static Key generateKey(boolean pub, String key) throws Exception { + if (pub) key = key.replaceAll(System.lineSeparator(), "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", ""); + else key = key.replaceAll(System.lineSeparator(), "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", ""); + return pub ? KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))) : KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))); + } + + private static int getModulusLength(Key key) { + if (key instanceof PublicKey) return ((RSAPublicKey) key).getModulus().bitLength(); + else return ((RSAPrivateKey) key).getModulus().bitLength(); + } + + private static byte[] concatArrays(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] result = new byte[aLen + bLen]; + System.arraycopy(a, 0, result, 0, aLen); + System.arraycopy(b, 0, result, aLen, bLen); + return result; + } +}