From 92294fe180966ec47cecdfaea570f56ee6d25c03 Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Mon, 12 Jul 2021 10:14:28 +0800 Subject: [PATCH] =?UTF-8?q?version:beta=20update:=20fix:=20add:=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=94=B5=E5=AD=90=E4=B9=A6=E5=8C=85=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E5=A2=9E=E5=8A=A0=E8=8E=B7=E5=8F=96=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E7=A0=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 12 +- app/src/main/AndroidManifest.xml | 37 +- .../appstore/IGetLicenseInterface.aidl | 14 + .../myappstore/activity/HomeActivity.java | 6 +- .../myappstore/activity/MainActivity.java | 9 +- .../myappstore/activity/MainContact.java | 5 + .../myappstore/activity/MainPresenter.java | 23 +- .../manager/NetInterfaceManager.java | 12 +- .../myappstore/network/HTTPInterface.java | 4 +- .../myappstore/receiver/BootReceiver.java | 12 +- .../myappstore/receiver/MyJPushReceiver.java | 3 +- .../{server => service}/GuardService.java | 2 +- .../{server => service}/JWebSocketClient.java | 2 +- .../{server => service}/LogcatService.java | 4 +- .../{server => service}/MainService.java | 10 +- .../MyDownloadService.java | 2 +- .../myappstore/service/RemoteService.java | 41 + .../{server => service}/StepService.java | 2 +- .../myappstore/utils/AES/AESCrypt.java | 199 ++++ .../myappstore/utils/AES/AESEncrypt.java | 141 +++ .../mjsheng/myappstore/utils/AES/AESUtil.java | 170 ++++ .../myappstore/utils/AES/AESUtils3.java | 126 +++ .../utils/AES/AesCbcWithIntegrity.java | 941 ++++++++++++++++++ .../myappstore/utils/AES/CXAESUtil.java | 327 ++++++ .../mjsheng/myappstore/utils/JGYUtils.java | 96 +- .../mjsheng/myappstore/utils/ProcessUtil.java | 96 ++ .../mjsheng/myappstore/utils/TimeUtils.java | 2 +- .../main/res/layout-land/activity_main.xml | 570 ++++++----- .../main/res/layout-port/activity_main.xml | 64 +- 29 files changed, 2561 insertions(+), 371 deletions(-) create mode 100644 app/src/main/aidl/com/jiaoguanyi/appstore/IGetLicenseInterface.aidl rename app/src/main/java/com/mjsheng/myappstore/{server => service}/GuardService.java (99%) rename app/src/main/java/com/mjsheng/myappstore/{server => service}/JWebSocketClient.java (95%) rename app/src/main/java/com/mjsheng/myappstore/{server => service}/LogcatService.java (98%) rename app/src/main/java/com/mjsheng/myappstore/{server => service}/MainService.java (99%) rename app/src/main/java/com/mjsheng/myappstore/{server => service}/MyDownloadService.java (99%) create mode 100644 app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java rename app/src/main/java/com/mjsheng/myappstore/{server => service}/StepService.java (99%) create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java create mode 100644 app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java diff --git a/app/build.gradle b/app/build.gradle index 4bc614a..fface70 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,9 +41,9 @@ android { //新平台正式 newly { flavorDimensions "default" - versionCode 518 + versionCode 519 //versionCode 1037 - versionName "2.1.8" + versionName "2.1.9" /*********************************极光推送************************************/ manifestPlaceholders = [ JPUSH_PKGNAME: "com.jiaoguanyi.appstore", @@ -61,8 +61,8 @@ android { //新平台测试 beta { flavorDimensions "default" - versionCode 525 - versionName "2.1.5" + versionCode 528 + versionName "2.1.8" /*********************************极光推送************************************/ manifestPlaceholders = [ JPUSH_PKGNAME: "com.jiaoguanyi.appstore", @@ -264,8 +264,8 @@ dependencies { //implementation fileTree(include: ['*.jar'], dir: 'libs') compileOnly files('src/main/libs/classes.jar') - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2f29617..032a0d1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,13 +56,10 @@ android:maxSdkVersion="22" /> - + android:maxSdkVersion="23" /> - - - - + + @@ -75,16 +72,13 @@ - - - - + + - + android:process=":remote" + android:exported="true" /> + @@ -118,14 +117,13 @@ - - - + + @@ -366,8 +364,7 @@ android:value="developer-default" /> - + android:value="${JPUSH_APPKEY}" /> { + //设置二维码 + void getQRImage(String mac); //获取学生信息 void getStudesInfo(); //获取设备锁定状态 diff --git a/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java b/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java index 19bf18c..d5b4f6c 100644 --- a/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java +++ b/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java @@ -20,7 +20,6 @@ import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import com.mjsheng.myappstore.BuildConfig; import com.mjsheng.myappstore.base.BaseApplication; -import com.mjsheng.myappstore.bean.Appground; import com.mjsheng.myappstore.bean.BaseResponse; import com.mjsheng.myappstore.bean.BrowserData; import com.mjsheng.myappstore.bean.ForceDownloadBean; @@ -31,8 +30,9 @@ import com.mjsheng.myappstore.bean.StudentsInfo; import com.mjsheng.myappstore.jpush.TagAliasOperatorHelper; import com.mjsheng.myappstore.manager.NetInterfaceManager; import com.mjsheng.myappstore.network.HTTPInterface; -import com.mjsheng.myappstore.server.MainService; +import com.mjsheng.myappstore.service.MainService; import com.mjsheng.myappstore.utils.ApkUtils; +import com.mjsheng.myappstore.utils.AES.CXAESUtil; import com.mjsheng.myappstore.utils.ForegroundAppUtil; import com.mjsheng.myappstore.utils.JGYUtils; import com.mjsheng.myappstore.utils.SPUtils; @@ -88,6 +88,25 @@ public class MainPresenter implements MainContact.Presenter { this.mView = null; } + String key = "0123456789ABCDEF"; + + @Override + public void getQRImage(String mac) { + Log.e(TAG, "getQRImage: " + mac); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("sn", Utils.getSerial()); + jsonObject.addProperty("mac", mac); + String json = jsonObject.toString(); + String content = mac; + try { + content = CXAESUtil.encrypt(key, json); + Log.e(TAG, "getQRImage: " + content); + } catch (Exception e) { + Log.e(TAG, "getQRImage: " + e.getMessage()); + e.printStackTrace(); + } + mView.setQRImage(JGYUtils.getInstance().createQRImage(content, 300, 300)); + } /** * 通过sn获取用户信息 diff --git a/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java b/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java index 1fa5e88..8be0f6b 100644 --- a/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java +++ b/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java @@ -1,5 +1,6 @@ package com.mjsheng.myappstore.manager; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Environment; @@ -65,7 +66,8 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; public class NetInterfaceManager { - private static NetInterfaceManager sInstance; + @SuppressLint("StaticFieldLeak") + private static NetInterfaceManager INSTANCE; private Context mContext; private static Retrofit mRetrofit; @@ -86,17 +88,17 @@ public class NetInterfaceManager { } public static void init(Context context) { - if (sInstance == null) { - sInstance = new NetInterfaceManager(context); + if (INSTANCE == null) { + INSTANCE = new NetInterfaceManager(context); } } public static NetInterfaceManager getInstance() { - if (sInstance == null) { + if (INSTANCE == null) { throw new IllegalStateException("You must be init NetworkManager first"); } - return sInstance; + return INSTANCE; } private static final long cacheSize = 1024 * 1024 * 32;// 缓存文件最大限制大小20M diff --git a/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java b/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java index bcb9ba5..ae03f46 100644 --- a/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java +++ b/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java @@ -24,17 +24,15 @@ import com.mjsheng.myappstore.utils.JGYUtils; import com.mjsheng.myappstore.utils.URLUtils; import com.lzy.okgo.OkGo; import com.lzy.okgo.callback.StringCallback; -import com.mjsheng.myappstore.base.BaseApplication; import com.mjsheng.myappstore.bean.Appground; import com.mjsheng.myappstore.bean.BaseResponse; import com.mjsheng.myappstore.bean.NetAndLaunchBean; -import com.mjsheng.myappstore.bean.NetAndLaunchData; import com.mjsheng.myappstore.jpush.TagAliasOperatorHelper; import com.mjsheng.myappstore.manager.NetInterfaceManager; import com.mjsheng.myappstore.network.api.newapi.SnTimeControl; import com.mjsheng.myappstore.network.api.newapi.TopAppControlApi; import com.mjsheng.myappstore.network.api.newapi.UpdateDeviceInfoApi; -import com.mjsheng.myappstore.server.MainService; +import com.mjsheng.myappstore.service.MainService; import com.mjsheng.myappstore.utils.ApkUtils; import com.mjsheng.myappstore.utils.ForegroundAppUtil; import com.mjsheng.myappstore.utils.Logger; diff --git a/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java b/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java index 600cc6e..1ad9ec9 100644 --- a/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java +++ b/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java @@ -3,17 +3,13 @@ package com.mjsheng.myappstore.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Log; -import com.mjsheng.myappstore.server.GuardService; -import com.mjsheng.myappstore.server.LogcatService; -import com.mjsheng.myappstore.server.MainService; -import com.mjsheng.myappstore.server.StepService; +import com.mjsheng.myappstore.service.GuardService; +import com.mjsheng.myappstore.service.LogcatService; +import com.mjsheng.myappstore.service.MainService; +import com.mjsheng.myappstore.service.StepService; import com.mjsheng.myappstore.utils.BootManager; -import com.mjsheng.myappstore.utils.JGYUtils; -import com.mjsheng.myappstore.utils.SPUtils; public class BootReceiver extends BroadcastReceiver { private String TAG = BootReceiver.class.getSimpleName() + ":myappstore"; diff --git a/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java b/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java index 56a5e36..e359dea 100644 --- a/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java +++ b/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java @@ -1,7 +1,6 @@ package com.mjsheng.myappstore.receiver; import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.ContextWrapper; @@ -35,7 +34,7 @@ import com.mjsheng.myappstore.manager.AmapManager; import com.mjsheng.myappstore.manager.NetInterfaceManager; import com.mjsheng.myappstore.network.HTTPInterface; import com.mjsheng.myappstore.network.URLAddress; -import com.mjsheng.myappstore.server.MainService; +import com.mjsheng.myappstore.service.MainService; import com.mjsheng.myappstore.utils.ApkUtils; import com.mjsheng.myappstore.utils.CmdUtil; import com.mjsheng.myappstore.utils.ForegroundAppUtil; diff --git a/app/src/main/java/com/mjsheng/myappstore/server/GuardService.java b/app/src/main/java/com/mjsheng/myappstore/service/GuardService.java similarity index 99% rename from app/src/main/java/com/mjsheng/myappstore/server/GuardService.java rename to app/src/main/java/com/mjsheng/myappstore/service/GuardService.java index 0504b61..cf6338d 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/GuardService.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/GuardService.java @@ -1,4 +1,4 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; /** * 作者 mjsheng diff --git a/app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java b/app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java similarity index 95% rename from app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java rename to app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java index 0957f74..22d986f 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java @@ -1,4 +1,4 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; import android.util.Log; diff --git a/app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java b/app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java similarity index 98% rename from app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java rename to app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java index 5f4de5a..482726f 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java @@ -1,16 +1,14 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.os.Environment; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; -import com.android.server.am.ServiceRecordProto; import com.mjsheng.myappstore.utils.Utils; import java.io.BufferedReader; diff --git a/app/src/main/java/com/mjsheng/myappstore/server/MainService.java b/app/src/main/java/com/mjsheng/myappstore/service/MainService.java similarity index 99% rename from app/src/main/java/com/mjsheng/myappstore/server/MainService.java rename to app/src/main/java/com/mjsheng/myappstore/service/MainService.java index aafcda6..864b245 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/MainService.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/MainService.java @@ -1,4 +1,4 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; import android.annotation.SuppressLint; import android.app.Service; @@ -6,6 +6,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Build; @@ -27,9 +28,7 @@ import com.mjsheng.myappstore.activity.MainActivity; import com.mjsheng.myappstore.activity.MainContact; import com.mjsheng.myappstore.activity.MainPresenter; import com.mjsheng.myappstore.utils.ApkUtils; -import com.mjsheng.myappstore.utils.CacheUtils; import com.mjsheng.myappstore.utils.ForegroundAppUtil; -import com.mjsheng.myappstore.utils.JGYUtils; import com.mjsheng.myappstore.utils.SPUtils; import com.mjsheng.myappstore.utils.SaveListUtils; import com.mjsheng.myappstore.utils.SysSettingUtils; @@ -421,6 +420,11 @@ public class MainService extends Service implements MainContact.MainView { } } + @Override + public void setQRImage(Bitmap qrImage) { + + } + @Override public void setBatchText(String text, int visibility) { diff --git a/app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java b/app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java similarity index 99% rename from app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java rename to app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java index 189c90e..d53a8aa 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java @@ -1,5 +1,5 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; import android.app.Service; import android.content.Intent; diff --git a/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java b/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java new file mode 100644 index 0000000..895b85b --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java @@ -0,0 +1,41 @@ +package com.mjsheng.myappstore.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.jiaoguanyi.appstore.IGetLicenseInterface; +import com.mjsheng.myappstore.utils.Utils; + +public class RemoteService extends Service { + private String TAG = RemoteService.class.getSimpleName(); + + public RemoteService() { + } + + @Override + public IBinder onBind(Intent intent) { + Log.e(TAG, "onBind: "); + return mBinde; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return super.onStartCommand(intent, flags, startId); + } + + IGetLicenseInterface.Stub mBinde = new IGetLicenseInterface.Stub() { + @Override + public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { + + } + + @Override + public String getLicense() throws RemoteException { + Log.e(TAG, "getLicense: "); + return "8054u98729"; + } + }; +} diff --git a/app/src/main/java/com/mjsheng/myappstore/server/StepService.java b/app/src/main/java/com/mjsheng/myappstore/service/StepService.java similarity index 99% rename from app/src/main/java/com/mjsheng/myappstore/server/StepService.java rename to app/src/main/java/com/mjsheng/myappstore/service/StepService.java index 324c5a4..1f7df92 100644 --- a/app/src/main/java/com/mjsheng/myappstore/server/StepService.java +++ b/app/src/main/java/com/mjsheng/myappstore/service/StepService.java @@ -1,4 +1,4 @@ -package com.mjsheng.myappstore.server; +package com.mjsheng.myappstore.service; /** * 作者 mjsheng diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java new file mode 100644 index 0000000..f41534f --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java @@ -0,0 +1,199 @@ +package com.mjsheng.myappstore.utils.AES; + +import android.util.Base64; +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Encrypt and decrypt messages using AES 256 bit encryption that are compatible with AESCrypt-ObjC and AESCrypt Ruby. + *

+ * Created by scottab on 04/10/2014. + */ +public final class AESCrypt { + + private static final String TAG = "AESCrypt"; + + //AESCrypt-ObjC uses CBC and PKCS7Padding + private static final String AES_MODE = "AES/CBC/PKCS7Padding"; + private static final String CHARSET = "UTF-8"; + + //AESCrypt-ObjC uses SHA-256 (and so a 256-bit key) + private static final String HASH_ALGORITHM = "SHA-256"; + + //AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility) + private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + //togglable log option (please turn off in live!) + public static boolean DEBUG_LOG_ENABLED = false; + + + /** + * Generates SHA256 hash of the password which is used as key + * + * @param password used to generated key + * @return SHA256 of the password + */ + private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { + final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + byte[] bytes = password.getBytes("UTF-8"); + digest.update(bytes, 0, bytes.length); + byte[] key = digest.digest(); + + log("SHA-256 key ", key); + + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + return secretKeySpec; + } + + + /** + * Encrypt and encode message using 256-bit AES with key generated from password. + * + * + * @param password used to generated key + * @param message the thing you want to encrypt assumed String UTF-8 + * @return Base64 encoded CipherText + * @throws GeneralSecurityException if problems occur during encryption + */ + public static String encrypt(final String password, String message) + throws GeneralSecurityException { + + try { + final SecretKeySpec key = generateKey(password); + + log("message", message); + + byte[] cipherText = encrypt(key, ivBytes, message.getBytes(CHARSET)); + + //NO_WRAP is important as was getting \n at the end + String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP); + log("Base64.NO_WRAP", encoded); + return encoded; + } catch (UnsupportedEncodingException e) { + if (DEBUG_LOG_ENABLED) + Log.e(TAG, "UnsupportedEncodingException ", e); + throw new GeneralSecurityException(e); + } + } + + + /** + * More flexible AES encrypt that doesn't encode + * @param key AES key typically 128, 192 or 256 bit + * @param iv Initiation Vector + * @param message in bytes (assumed it's already been decoded) + * @return Encrypted cipher text (not encoded) + * @throws GeneralSecurityException if something goes wrong during encryption + */ + public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message) + throws GeneralSecurityException { + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); + byte[] cipherText = cipher.doFinal(message); + + log("cipherText", cipherText); + + return cipherText; + } + + + /** + * Decrypt and decode ciphertext using 256-bit AES with key generated from password + * + * @param password used to generated key + * @param base64EncodedCipherText the encrpyted message encoded with base64 + * @return message in Plain text (String UTF-8) + * @throws GeneralSecurityException if there's an issue decrypting + */ + public static String decrypt(final String password, String base64EncodedCipherText) + throws GeneralSecurityException { + + try { + final SecretKeySpec key = generateKey(password); + + log("base64EncodedCipherText", base64EncodedCipherText); + byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP); + log("decodedCipherText", decodedCipherText); + + byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText); + + log("decryptedBytes", decryptedBytes); + String message = new String(decryptedBytes, CHARSET); + log("message", message); + + + return message; + } catch (UnsupportedEncodingException e) { + if (DEBUG_LOG_ENABLED) + Log.e(TAG, "UnsupportedEncodingException ", e); + + throw new GeneralSecurityException(e); + } + } + + + /** + * More flexible AES decrypt that doesn't encode + * + * @param key AES key typically 128, 192 or 256 bit + * @param iv Initiation Vector + * @param decodedCipherText in bytes (assumed it's already been decoded) + * @return Decrypted message cipher text (not encoded) + * @throws GeneralSecurityException if something goes wrong during encryption + */ + public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText) + throws GeneralSecurityException { + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); + byte[] decryptedBytes = cipher.doFinal(decodedCipherText); + + log("decryptedBytes", decryptedBytes); + + return decryptedBytes; + } + + + + + private static void log(String what, byte[] bytes) { + if (DEBUG_LOG_ENABLED) + Log.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]"); + } + + private static void log(String what, String value) { + if (DEBUG_LOG_ENABLED) + Log.d(TAG, what + "[" + value.length() + "] [" + value + "]"); + } + + + /** + * Converts byte array to hexidecimal useful for logging and fault finding + * @param bytes + * @return + */ + private static String bytesToHex(byte[] bytes) { + final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + char[] hexChars = new char[bytes.length * 2]; + int v; + for (int j = 0; j < bytes.length; j++) { + v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + private AESCrypt() { + } +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java new file mode 100644 index 0000000..e741959 --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java @@ -0,0 +1,141 @@ +package com.mjsheng.myappstore.utils.AES; + +import android.util.Base64; +import android.util.Log; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +/** + * AES 对称加密算法,加解密工具类 + */ +public class AESEncrypt { + + private static final String TAG = AESEncrypt.class.getSimpleName() + " --> "; + + /** + * 加密算法 + */ + private static final String KEY_ALGORITHM = "AES"; + + /** + * AES 的 密钥长度,32 字节,范围:16 - 32 字节 + */ + public static final int SECRET_KEY_LENGTH = 32; + + /** + * 字符编码 + */ + private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; + + /** + * 秘钥长度不足 16 个字节时,默认填充位数 + */ + private static final String DEFAULT_VALUE = "0"; + /** + * 加解密算法/工作模式/填充方式 + */ + private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; + + /** + * AES 加密 + * + * @param data 待加密内容 + * @param secretKey 加密密码,长度:16 或 32 个字符 + * @return 返回Base64转码后的加密数据 + */ + public static String encrypt(String data, String secretKey) { + try { + //创建密码器 + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + //初始化为加密密码器 + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey)); + byte[] encryptByte = cipher.doFinal(data.getBytes(CHARSET_UTF8)); + // 将加密以后的数据进行 Base64 编码 + return base64Encode(encryptByte); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * AES 解密 + * + * @param base64Data 加密的密文 Base64 字符串 + * @param secretKey 解密的密钥,长度:16 或 32 个字符 + */ + public static String decrypt(String base64Data, String secretKey) { + try { + byte[] data = base64Decode(base64Data); + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + //设置为解密模式 + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey)); + //执行解密操作 + byte[] result = cipher.doFinal(data); + return new String(result, CHARSET_UTF8); + } catch (Exception e) { + handleException(e); + } + return null; + } + + /** + * 使用密码获取 AES 秘钥 + */ + public static SecretKeySpec getSecretKey(String secretKey) { + secretKey = toMakeKey(secretKey, SECRET_KEY_LENGTH, DEFAULT_VALUE); + return new SecretKeySpec(secretKey.getBytes(CHARSET_UTF8), KEY_ALGORITHM); + } + + /** + * 如果 AES 的密钥小于 {@code length} 的长度,就对秘钥进行补位,保证秘钥安全。 + * + * @param secretKey 密钥 key + * @param length 密钥应有的长度 + * @param text 默认补的文本 + * @return 密钥 + */ + private static String toMakeKey(String secretKey, int length, String text) { + // 获取密钥长度 + int strLen = secretKey.length(); + // 判断长度是否小于应有的长度 + if (strLen < length) { + // 补全位数 + StringBuilder builder = new StringBuilder(); + // 将key添加至builder中 + builder.append(secretKey); + // 遍历添加默认文本 + for (int i = 0; i < length - strLen; i++) { + builder.append(text); + } + // 赋值 + secretKey = builder.toString(); + } + return secretKey; + } + + /** + * 将 Base64 字符串 解码成 字节数组 + */ + public static byte[] base64Decode(String data) { + return Base64.decode(data, Base64.NO_WRAP); + } + + /** + * 将 字节数组 转换成 Base64 编码 + */ + public static String base64Encode(byte[] data) { + return Base64.encodeToString(data, Base64.NO_WRAP); + } + + /** + * 处理异常 + */ + private static void handleException(Exception e) { + e.printStackTrace(); + Log.e(TAG, TAG + e); + } +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java new file mode 100644 index 0000000..35c4225 --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java @@ -0,0 +1,170 @@ +package com.mjsheng.myappstore.utils.AES; + +import android.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; + +/** + * AES加密解密 + */ +public class AESUtil { + private static final String default_key = "12345678"; + static final String charset = "utf-8"; + static final String AES = "AES"; + + /** + * 先AES加密,再Base64加密 + * + * @param content + * @return + */ + public static String encodeBase64(String content) { + String encode = encode(content);//AES加密 + if (encode == null) return null;//加密失败返回空 + try { + String s = Base64.encodeToString(encode.getBytes(charset), Base64.NO_WRAP);//Base64加密 + return s; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 先Base64解密,再AES解密 + * + * @param content + * @return + */ + public static String decodeBase64(String content) { + try { + String s = new String(Base64.decode(content, Base64.DEFAULT), charset);//Base64解密 + String decode = decode(s);//AES解密 + return decode; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null;//解密失败返回空 + } + + /** + * AES加密 + * + * @param content + * @return + */ + public static String encode(String content) { + return encode(default_key, content); + } + + /** + * AES解密 + * + * @param content + * @return + */ + public static String decode(String content) { + return decode(default_key, content); + } + + /* + * AES加密 + * 1.构造密钥生成器 + * 2.根据ecnodeRules规则初始化密钥生成器 + * 3.产生密钥 + * 4.创建和初始化密码器 + * 5.内容加密 + * 6.返回字符串 + */ + public static String encode(String encodeRules, String content) { + try { + //1.构造密钥生成器,指定为AES算法,不区分大小写 + KeyGenerator keygen = KeyGenerator.getInstance(AES); + //2.根据ecnodeRules规则初始化密钥生成器 + //生成一个128位的随机源,根据传入的字节数组 +// keygen.init(128, new SecureRandom(encodeRules.getBytes())); + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(encodeRules.getBytes()); + keygen.init(128, random); + //3.产生原始对称密钥 + SecretKey original_key = keygen.generateKey(); + //4.获得原始对称密钥的字节数组 + byte[] raw = original_key.getEncoded(); + //5.根据字节数组生成AES密钥 + SecretKey key = new SecretKeySpec(raw, AES); + //6.根据指定算法AES自成密码器 + Cipher cipher = Cipher.getInstance(AES); + //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY + cipher.init(Cipher.ENCRYPT_MODE, key); + //8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码 + byte[] byte_encode = content.getBytes(charset); + //9.根据密码器的初始化方式--加密:将数据加密 + byte[] byte_AES = cipher.doFinal(byte_encode); + //10.将加密后的数据转换为字符串 + //这里用Base64Encoder中会找不到包 + //解决办法: + //在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。 + String AES_encode = Base64.encodeToString(byte_AES, Base64.NO_WRAP); + //11.将字符串返回 + return AES_encode; + } catch (Exception e) { + e.printStackTrace(); + } + return null;//加密失败返回空 + } + + /* + * AES解密 + * 解密过程: + * 1.同加密1-4步 + * 2.将加密后的字符串反纺成byte[]数组 + * 3.将加密内容解密 + */ + public static String decode(String encodeRules, String content) { + try { + //1.构造密钥生成器,指定为AES算法,不区分大小写 + KeyGenerator keygen = KeyGenerator.getInstance(AES); + //2.根据ecnodeRules规则初始化密钥生成器 + //生成一个128位的随机源,根据传入的字节数组 +// keygen.init(128, new SecureRandom(encodeRules.getBytes())); + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + random.setSeed(encodeRules.getBytes()); + keygen.init(128, random); + //3.产生原始对称密钥 + SecretKey original_key = keygen.generateKey(); + //4.获得原始对称密钥的字节数组 + byte[] raw = original_key.getEncoded(); + //5.根据字节数组生成AES密钥 + SecretKey key = new SecretKeySpec(raw, AES); + //6.根据指定算法AES自成密码器 + Cipher cipher = Cipher.getInstance(AES); + //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY + cipher.init(Cipher.DECRYPT_MODE, key); + //8.将加密并编码后的内容解码成字节数组 + byte[] byte_content = Base64.decode(content, Base64.NO_WRAP); + /* + * 解密 + */ + byte[] byte_decode = cipher.doFinal(byte_content); + String AES_decode = new String(byte_decode, charset); + return AES_decode; + } catch (Exception e) { + e.printStackTrace(); + } + return null;//解密失败返回空 + } + + public static void main(String[] args) { + String a = "1236"; + String s = encodeBase64(a); + System.out.println(s); + String s1 = decodeBase64(s); + System.out.println(s1); + } +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java new file mode 100644 index 0000000..98d5e4d --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java @@ -0,0 +1,126 @@ +package com.mjsheng.myappstore.utils.AES; + +import java.io.UnsupportedEncodingException; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +public class AESUtils3 { + /* 算法/模式/填充 */ + private static final String CipherMode = "AES/ECB/PKCS5Padding"; + + /* 创建密钥 */ + private static SecretKeySpec createKey(String password) { + byte[] data = null; + if (password == null) { + password = ""; + } + StringBuffer sb = new StringBuffer(32); + sb.append(password); + while (sb.length() < 32) { + sb.append("0"); + } + if (sb.length() > 32) { + sb.setLength(32); + } + + try { + data = sb.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return new SecretKeySpec(data, "AES"); + } + + /* 加密字节数据 */ + public static byte[] encrypt(byte[] content, String password) { + try { + SecretKeySpec key = createKey(password); + System.out.println(key); + Cipher cipher = Cipher.getInstance(CipherMode); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] result = cipher.doFinal(content); + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /*加密(结果为16进制字符串) */ + public static String encrypt(String content, String password) { + byte[] data = null; + try { + data = content.getBytes("UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } + data = encrypt(data, password); + String result = byte2hex(data); + return result; + } + + /*解密字节数组*/ + public static byte[] decrypt(byte[] content, String password) { + try { + SecretKeySpec key = createKey(password); + Cipher cipher = Cipher.getInstance(CipherMode); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] result = cipher.doFinal(content); + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /*解密16进制的字符串为字符串 */ + public static String decrypt(String content, String password) { + byte[] data = null; + try { + data = hex2byte(content); + } catch (Exception e) { + e.printStackTrace(); + } + data = decrypt(data, password); + if (data == null) return null; + String result = null; + try { + result = new String(data, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return result; + } + + /*字节数组转成16进制字符串 */ + public static String byte2hex(byte[] b) { // 一个字节的数, + StringBuffer sb = new StringBuffer(b.length * 2); + String tmp = ""; + for (int n = 0; n < b.length; n++) { + // 整数转成十六进制表示 + tmp = (java.lang.Integer.toHexString(b[n] & 0XFF)); + if (tmp.length() == 1) { + sb.append("0"); + } + sb.append(tmp); + } + return sb.toString().toUpperCase(); // 转成大写 + } + + /*将hex字符串转换成字节数组 */ + private static byte[] hex2byte(String inputString) { + if (inputString == null || inputString.length() < 2) { + return new byte[0]; + } + inputString = inputString.toLowerCase(); + int l = inputString.length() / 2; + byte[] result = new byte[l]; + for (int i = 0; i < l; ++i) { + String tmp = inputString.substring(2 * i, 2 * i + 2); + result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF); + } + return result; + } +} + diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java new file mode 100644 index 0000000..493f22d --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java @@ -0,0 +1,941 @@ +/* + * Copyright (c) 2014-2015 Tozny LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Created by Isaac Potoczny-Jones on 11/12/14. + */ + +package com.mjsheng.myappstore.utils.AES; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.SecureRandomSpi; +import java.security.Security; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import android.os.Build; +import android.os.Process; +import android.util.Base64; +import android.util.Log; + +/** + * Simple library for the "right" defaults for AES key generation, encryption, + * and decryption using 128-bit AES, CBC, PKCS5 padding, and a random 16-byte IV + * with SHA1PRNG. Integrity with HmacSHA256. + */ +public class AesCbcWithIntegrity { + // If the PRNG fix would not succeed for some reason, we normally will throw an exception. + // If ALLOW_BROKEN_PRNG is true, however, we will simply log instead. + private static final boolean ALLOW_BROKEN_PRNG = false; + + private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding"; + private static final String CIPHER = "AES"; + private static final int AES_KEY_LENGTH_BITS = 128; + private static final int IV_LENGTH_BYTES = 16; + private static final int PBE_ITERATION_COUNT = 10000; + private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output + private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1"; + + //Made BASE_64_FLAGS public as it's useful to know for compatibility. + public static final int BASE64_FLAGS = Base64.NO_WRAP; + //default for testing + static final AtomicBoolean prngFixed = new AtomicBoolean(false); + + private static final String HMAC_ALGORITHM = "HmacSHA256"; + private static final int HMAC_KEY_LENGTH_BITS = 256; + + /** + * Converts the given AES/HMAC keys into a base64 encoded string suitable for + * storage. Sister function of keys. + * + * @param keys The combined aes and hmac keys + * @return a base 64 encoded AES string and hmac key as base64(aesKey) : base64(hmacKey) + */ + public static String keyString(SecretKeys keys) { + return keys.toString(); + } + + /** + * An aes key derived from a base64 encoded key. This does not generate the + * key. It's not random or a PBE key. + * + * @param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey). + * @return an AES and HMAC key set suitable for other functions. + */ + public static SecretKeys keys(String keysStr) throws InvalidKeyException { + String[] keysArr = keysStr.split(":"); + + if (keysArr.length != 2) { + throw new IllegalArgumentException("Cannot parse aesKey:hmacKey"); + + } else { + byte[] confidentialityKey = Base64.decode(keysArr[0], BASE64_FLAGS); + if (confidentialityKey.length != AES_KEY_LENGTH_BITS /8) { + throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes"); + } + byte[] integrityKey = Base64.decode(keysArr[1], BASE64_FLAGS); + if (integrityKey.length != HMAC_KEY_LENGTH_BITS /8) { + throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes"); + } + + return new SecretKeys( + new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER), + new SecretKeySpec(integrityKey, HMAC_ALGORITHM)); + } + } + + /** + * A function that generates random AES and HMAC keys and prints out exceptions but + * doesn't throw them since none should be encountered. If they are + * encountered, the return value is null. + * + * @return The AES and HMAC keys. + * @throws GeneralSecurityException if AES is not implemented on this system, + * or a suitable RNG is not available + */ + public static SecretKeys generateKey() throws GeneralSecurityException { + fixPrng(); + KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER); + // No need to provide a SecureRandom or set a seed since that will + // happen automatically. + keyGen.init(AES_KEY_LENGTH_BITS); + SecretKey confidentialityKey = keyGen.generateKey(); + + //Now make the HMAC key + byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);//to get bytes + SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM); + + return new SecretKeys(confidentialityKey, integrityKey); + } + + /** + * A function that generates password-based AES and HMAC keys. It prints out exceptions but + * doesn't throw them since none should be encountered. If they are + * encountered, the return value is null. + * + * @param password The password to derive the keys from. + * @return The AES and HMAC keys. + * @throws GeneralSecurityException if AES is not implemented on this system, + * or a suitable RNG is not available + */ + public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException { + fixPrng(); + //Get enough random bytes for both the AES key and the HMAC key: + KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, + PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS); + SecretKeyFactory keyFactory = SecretKeyFactory + .getInstance(PBE_ALGORITHM); + byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); + + // Split the random bytes into two parts: + byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS /8); + byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS /8, AES_KEY_LENGTH_BITS /8 + HMAC_KEY_LENGTH_BITS /8); + + //Generate the AES key + SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER); + + //Generate the HMAC key + SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM); + + return new SecretKeys(confidentialityKey, integrityKey); + } + + /** + * A function that generates password-based AES and HMAC keys. See generateKeyFromPassword. + * @param password The password to derive the AES/HMAC keys from + * @param salt A string version of the salt; base64 encoded. + * @return The AES and HMAC keys. + * @throws GeneralSecurityException + */ + public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException { + return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS)); + } + + /** + * Generates a random salt. + * @return The random salt suitable for generateKeyFromPassword. + */ + public static byte[] generateSalt() throws GeneralSecurityException { + return randomBytes(PBE_SALT_LENGTH_BITS); + } + + /** + * Converts the given salt into a base64 encoded string suitable for + * storage. + * + * @param salt + * @return a base 64 encoded salt string suitable to pass into generateKeyFromPassword. + */ + public static String saltString(byte[] salt) { + return Base64.encodeToString(salt, BASE64_FLAGS); + } + + + /** + * Creates a random Initialization Vector (IV) of IV_LENGTH_BYTES. + * + * @return The byte array of this IV + * @throws GeneralSecurityException if a suitable RNG is not available + */ + public static byte[] generateIv() throws GeneralSecurityException { + return randomBytes(IV_LENGTH_BYTES); + } + + private static byte[] randomBytes(int length) throws GeneralSecurityException { + fixPrng(); + SecureRandom random = new SecureRandom(); + byte[] b = new byte[length]; + random.nextBytes(b); + return b; + } + + /* + * ----------------------------------------------------------------- + * Encryption + * ----------------------------------------------------------------- + */ + + /** + * Generates a random IV and encrypts this plain text with the given key. Then attaches + * a hashed MAC, which is contained in the CipherTextIvMac class. + * + * @param plaintext The text that will be encrypted, which + * will be serialized with UTF-8 + * @param secretKeys The AES and HMAC keys with which to encrypt + * @return a tuple of the IV, ciphertext, mac + * @throws GeneralSecurityException if AES is not implemented on this system + * @throws UnsupportedEncodingException if UTF-8 is not supported in this system + */ + public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys) + throws UnsupportedEncodingException, GeneralSecurityException { + return encrypt(plaintext, secretKeys, "UTF-8"); + } + + /** + * Generates a random IV and encrypts this plain text with the given key. Then attaches + * a hashed MAC, which is contained in the CipherTextIvMac class. + * + * @param plaintext The bytes that will be encrypted + * @param secretKeys The AES and HMAC keys with which to encrypt + * @return a tuple of the IV, ciphertext, mac + * @throws GeneralSecurityException if AES is not implemented on this system + * @throws UnsupportedEncodingException if the specified encoding is invalid + */ + public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys, String encoding) + throws UnsupportedEncodingException, GeneralSecurityException { + return encrypt(plaintext.getBytes(encoding), secretKeys); + } + + /** + * Generates a random IV and encrypts this plain text with the given key. Then attaches + * a hashed MAC, which is contained in the CipherTextIvMac class. + * + * @param plaintext The text that will be encrypted + * @param secretKeys The combined AES and HMAC keys with which to encrypt + * @return a tuple of the IV, ciphertext, mac + * @throws GeneralSecurityException if AES is not implemented on this system + */ + public static CipherTextIvMac encrypt(byte[] plaintext, SecretKeys secretKeys) + throws GeneralSecurityException { + byte[] iv = generateIv(); + Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION); + aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv)); + + /* + * Now we get back the IV that will actually be used. Some Android + * versions do funny stuff w/ the IV, so this is to work around bugs: + */ + iv = aesCipherForEncryption.getIV(); + byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext); + byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText); + + byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey()); + return new CipherTextIvMac(byteCipherText, iv, integrityMac); + } + + /** + * Ensures that the PRNG is fixed. Should be used before generating any keys. + * Will only run once, and every subsequent call should return immediately. + */ + private static void fixPrng() { + if (!prngFixed.get()) { + synchronized (PrngFixes.class) { + if (!prngFixed.get()) { + PrngFixes.apply(); + prngFixed.set(true); + } + } + } + } + + /* + * ----------------------------------------------------------------- + * Decryption + * ----------------------------------------------------------------- + */ + + /** + * AES CBC decrypt. + * + * @param civ The cipher text, IV, and mac + * @param secretKeys The AES and HMAC keys + * @param encoding The string encoding to use to decode the bytes after decryption + * @return A string derived from the decrypted bytes (not base64 encoded) + * @throws GeneralSecurityException if AES is not implemented on this system + * @throws UnsupportedEncodingException if the encoding is unsupported + */ + public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys, String encoding) + throws UnsupportedEncodingException, GeneralSecurityException { + return new String(decrypt(civ, secretKeys), encoding); + } + + /** + * AES CBC decrypt. + * + * @param civ The cipher text, IV, and mac + * @param secretKeys The AES and HMAC keys + * @return A string derived from the decrypted bytes, which are interpreted + * as a UTF-8 String + * @throws GeneralSecurityException if AES is not implemented on this system + * @throws UnsupportedEncodingException if UTF-8 is not supported + */ + public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys) + throws UnsupportedEncodingException, GeneralSecurityException { + return decryptString(civ, secretKeys, "UTF-8"); + } + + /** + * AES CBC decrypt. + * + * @param civ the cipher text, iv, and mac + * @param secretKeys the AES and HMAC keys + * @return The raw decrypted bytes + * @throws GeneralSecurityException if MACs don't match or AES is not implemented + */ + public static byte[] decrypt(CipherTextIvMac civ, SecretKeys secretKeys) + throws GeneralSecurityException { + + byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(civ.getIv(), civ.getCipherText()); + byte[] computedMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey()); + if (constantTimeEq(computedMac, civ.getMac())) { + Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION); + aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(), + new IvParameterSpec(civ.getIv())); + return aesCipherForDecryption.doFinal(civ.getCipherText()); + } else { + throw new GeneralSecurityException("MAC stored in civ does not match computed MAC."); + } + } + + /* + * ----------------------------------------------------------------- + * Helper Code + * ----------------------------------------------------------------- + */ + + /** + * Generate the mac based on HMAC_ALGORITHM + * @param integrityKey The key used for hmac + * @param byteCipherText the cipher text + * @return A byte array of the HMAC for the given key and ciphertext + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + */ + public static byte[] generateMac(byte[] byteCipherText, SecretKey integrityKey) throws NoSuchAlgorithmException, InvalidKeyException { + //Now compute the mac for later integrity checking + Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM); + sha256_HMAC.init(integrityKey); + return sha256_HMAC.doFinal(byteCipherText); + } + /** + * Holder class that has both the secret AES key for encryption (confidentiality) + * and the secret HMAC key for integrity. + */ + + public static class SecretKeys { + private SecretKey confidentialityKey; + private SecretKey integrityKey; + + /** + * Construct the secret keys container. + * @param confidentialityKeyIn The AES key + * @param integrityKeyIn the HMAC key + */ + public SecretKeys(SecretKey confidentialityKeyIn, SecretKey integrityKeyIn) { + setConfidentialityKey(confidentialityKeyIn); + setIntegrityKey(integrityKeyIn); + } + + public SecretKey getConfidentialityKey() { + return confidentialityKey; + } + + public void setConfidentialityKey(SecretKey confidentialityKey) { + this.confidentialityKey = confidentialityKey; + } + + public SecretKey getIntegrityKey() { + return integrityKey; + } + + public void setIntegrityKey(SecretKey integrityKey) { + this.integrityKey = integrityKey; + } + + /** + * Encodes the two keys as a string + * @return base64(confidentialityKey):base64(integrityKey) + */ + @Override + public String toString () { + return Base64.encodeToString(getConfidentialityKey().getEncoded(), BASE64_FLAGS) + + ":" + Base64.encodeToString(getIntegrityKey().getEncoded(), BASE64_FLAGS); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + confidentialityKey.hashCode(); + result = prime * result + integrityKey.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SecretKeys other = (SecretKeys) obj; + if (!integrityKey.equals(other.integrityKey)) + return false; + if (!confidentialityKey.equals(other.confidentialityKey)) + return false; + return true; + } + } + + + /** + * Simple constant-time equality of two byte arrays. Used for security to avoid timing attacks. + * @param a + * @param b + * @return true iff the arrays are exactly equal. + */ + public static boolean constantTimeEq(byte[] a, byte[] b) { + if (a.length != b.length) { + return false; + } + int result = 0; + for (int i = 0; i < a.length; i++) { + result |= a[i] ^ b[i]; + } + return result == 0; + } + + /** + * Holder class that allows us to bundle ciphertext and IV together. + */ + public static class CipherTextIvMac { + private final byte[] cipherText; + private final byte[] iv; + private final byte[] mac; + + public byte[] getCipherText() { + return cipherText; + } + + public byte[] getIv() { + return iv; + } + + public byte[] getMac() { + return mac; + } + + /** + * Construct a new bundle of ciphertext and IV. + * @param c The ciphertext + * @param i The IV + * @param h The mac + */ + public CipherTextIvMac(byte[] c, byte[] i, byte[] h) { + cipherText = new byte[c.length]; + System.arraycopy(c, 0, cipherText, 0, c.length); + iv = new byte[i.length]; + System.arraycopy(i, 0, iv, 0, i.length); + mac = new byte[h.length]; + System.arraycopy(h, 0, mac, 0, h.length); + } + + /** + * Constructs a new bundle of ciphertext and IV from a string of the + * format base64(iv):base64(ciphertext). + * + * @param base64IvAndCiphertext A string of the format + * iv:ciphertext The IV and ciphertext must each + * be base64-encoded. + */ + public CipherTextIvMac(String base64IvAndCiphertext) { + String[] civArray = base64IvAndCiphertext.split(":"); + if (civArray.length != 3) { + throw new IllegalArgumentException("Cannot parse iv:mac:ciphertext"); + } else { + iv = Base64.decode(civArray[0], BASE64_FLAGS); + mac = Base64.decode(civArray[1], BASE64_FLAGS); + cipherText = Base64.decode(civArray[2], BASE64_FLAGS); + } + } + + /** + * Concatinate the IV to the cipherText using array copy. + * This is used e.g. before computing mac. + * @param iv The IV to prepend + * @param cipherText the cipherText to append + * @return iv:cipherText, a new byte array. + */ + public static byte[] ivCipherConcat(byte[] iv, byte[] cipherText) { + byte[] combined = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, combined, 0, iv.length); + System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length); + return combined; + } + + /** + * Encodes this ciphertext, IV, mac as a string. + * + * @return base64(iv) : base64(mac) : base64(ciphertext). + * The iv and mac go first because they're fixed length. + */ + @Override + public String toString() { + String ivString = Base64.encodeToString(iv, BASE64_FLAGS); + String cipherTextString = Base64.encodeToString(cipherText, BASE64_FLAGS); + String macString = Base64.encodeToString(mac, BASE64_FLAGS); + return String.format(ivString + ":" + macString + ":" + cipherTextString); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(cipherText); + result = prime * result + Arrays.hashCode(iv); + result = prime * result + Arrays.hashCode(mac); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CipherTextIvMac other = (CipherTextIvMac) obj; + if (!Arrays.equals(cipherText, other.cipherText)) + return false; + if (!Arrays.equals(iv, other.iv)) + return false; + if (!Arrays.equals(mac, other.mac)) + return false; + return true; + } + } + + /** + * Copy the elements from the start to the end + * + * @param from the source + * @param start the start index to copy + * @param end the end index to finish + * @return the new buffer + */ + private static byte[] copyOfRange(byte[] from, int start, int end) { + int length = end - start; + byte[] result = new byte[length]; + System.arraycopy(from, start, result, 0, length); + return result; + } + + /** + * Fixes for the RNG as per + * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will Google be held liable for any damages arising + * from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, as long as the origin is not misrepresented. + * + * Fixes for the output of the default PRNG having low entropy. + * + * The fixes need to be applied via {@link #apply()} before any use of Java + * Cryptography Architecture primitives. A good place to invoke them is in + * the application's {@code onCreate}. + */ + public static final class PrngFixes { + + private static final int VERSION_CODE_JELLY_BEAN = 16; + private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; + private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); + + /** Hidden constructor to prevent instantiation. */ + private PrngFixes() { + } + + /** + * Applies all fixes. + * + * @throws SecurityException if a fix is needed but could not be + * applied. + */ + public static void apply() { + applyOpenSSLFix(); + installLinuxPRNGSecureRandom(); + } + + /** + * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if + * the fix is not needed. + * + * @throws SecurityException if the fix is needed but could not be + * applied. + */ + private static void applyOpenSSLFix() throws SecurityException { + if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) + || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { + // No need to apply the fix + return; + } + + try { + // Mix in the device- and invocation-specific seed. + Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_seed", byte[].class).invoke(null, generateSeed()); + + // Mix output of Linux PRNG into OpenSSL's PRNG + int bytesRead = (Integer) Class + .forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") + .getMethod("RAND_load_file", String.class, long.class) + .invoke(null, "/dev/urandom", 1024); + if (bytesRead != 1024) { + throw new IOException("Unexpected number of bytes read from Linux PRNG: " + + bytesRead); + } + } catch (Exception e) { + if (ALLOW_BROKEN_PRNG) { + Log.w(PrngFixes.class.getSimpleName(), "Failed to seed OpenSSL PRNG", e); + } else { + throw new SecurityException("Failed to seed OpenSSL PRNG", e); + } + } + } + + /** + * Installs a Linux PRNG-backed {@code SecureRandom} implementation as + * the default. Does nothing if the implementation is already the + * default or if there is not need to install the implementation. + * + * @throws SecurityException if the fix is needed but could not be + * applied. + */ + private static void installLinuxPRNGSecureRandom() throws SecurityException { + if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { + // No need to apply the fix + return; + } + + // Install a Linux PRNG-based SecureRandom implementation as the + // default, if not yet installed. + Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); + + // Insert and check the provider atomically. + // The official Android Java libraries use synchronized methods for + // insertProviderAt, etc., so synchronizing on the class should + // make things more stable, and prevent race conditions with other + // versions of this code. + synchronized (Security.class) { + if ((secureRandomProviders == null) + || (secureRandomProviders.length < 1) + || (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) { + Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); + } + + // Assert that new SecureRandom() and + // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed + // by the Linux PRNG-based SecureRandom implementation. + SecureRandom rng1 = new SecureRandom(); + if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) { + if (ALLOW_BROKEN_PRNG) { + Log.w(PrngFixes.class.getSimpleName(), + "new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass()); + return; + } else { + throw new SecurityException("new SecureRandom() backed by wrong Provider: " + + rng1.getProvider().getClass()); + } + } + + SecureRandom rng2 = null; + try { + rng2 = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + if (ALLOW_BROKEN_PRNG) { + Log.w(PrngFixes.class.getSimpleName(), "SHA1PRNG not available", e); + return; + } else { + new SecurityException("SHA1PRNG not available", e); + } + } + if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) { + if (ALLOW_BROKEN_PRNG) { + Log.w(PrngFixes.class.getSimpleName(), + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + + rng2.getProvider().getClass()); + return; + } else { + throw new SecurityException( + "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + + rng2.getProvider().getClass()); + } + } + } + } + + /** + * {@code Provider} of {@code SecureRandom} engines which pass through + * all requests to the Linux PRNG. + */ + private static class LinuxPRNGSecureRandomProvider extends Provider { + + public LinuxPRNGSecureRandomProvider() { + super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + + " /dev/urandom"); + // Although /dev/urandom is not a SHA-1 PRNG, some apps + // explicitly request a SHA1PRNG SecureRandom and we thus need + // to prevent them from getting the default implementation whose + // output may have low entropy. + put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); + put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + } + } + + /** + * {@link SecureRandomSpi} which passes all requests to the Linux PRNG ( + * {@code /dev/urandom}). + */ + public static class LinuxPRNGSecureRandom extends SecureRandomSpi { + + /* + * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a + * seed are passed through to the Linux PRNG (/dev/urandom). + * Instances of this class seed themselves by mixing in the current + * time, PID, UID, build fingerprint, and hardware serial number + * (where available) into Linux PRNG. + * + * Concurrency: Read requests to the underlying Linux PRNG are + * serialized (on sLock) to ensure that multiple threads do not get + * duplicated PRNG output. + */ + + private static final File URANDOM_FILE = new File("/dev/urandom"); + + private static final Object sLock = new Object(); + + /** + * Input stream for reading from Linux PRNG or {@code null} if not + * yet opened. + * + * @GuardedBy("sLock") + */ + private static DataInputStream sUrandomIn; + + /** + * Output stream for writing to Linux PRNG or {@code null} if not + * yet opened. + * + * @GuardedBy("sLock") + */ + private static OutputStream sUrandomOut; + + /** + * Whether this engine instance has been seeded. This is needed + * because each instance needs to seed itself if the client does not + * explicitly seed it. + */ + private boolean mSeeded; + + @Override + protected void engineSetSeed(byte[] bytes) { + try { + OutputStream out; + synchronized (sLock) { + out = getUrandomOutputStream(); + } + out.write(bytes); + out.flush(); + } catch (IOException e) { + // On a small fraction of devices /dev/urandom is not + // writable Log and ignore. + Log.w(PrngFixes.class.getSimpleName(), "Failed to mix seed into " + + URANDOM_FILE); + } finally { + mSeeded = true; + } + } + + @Override + protected void engineNextBytes(byte[] bytes) { + if (!mSeeded) { + // Mix in the device- and invocation-specific seed. + engineSetSeed(generateSeed()); + } + + try { + DataInputStream in; + synchronized (sLock) { + in = getUrandomInputStream(); + } + synchronized (in) { + in.readFully(bytes); + } + } catch (IOException e) { + throw new SecurityException("Failed to read from " + URANDOM_FILE, e); + } + } + + @Override + protected byte[] engineGenerateSeed(int size) { + byte[] seed = new byte[size]; + engineNextBytes(seed); + return seed; + } + + private DataInputStream getUrandomInputStream() { + synchronized (sLock) { + if (sUrandomIn == null) { + // NOTE: Consider inserting a BufferedInputStream + // between DataInputStream and FileInputStream if you need + // higher PRNG output performance and can live with future PRNG + // output being pulled into this process prematurely. + try { + sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE)); + } catch (IOException e) { + throw new SecurityException("Failed to open " + URANDOM_FILE + + " for reading", e); + } + } + return sUrandomIn; + } + } + + private OutputStream getUrandomOutputStream() throws IOException { + synchronized (sLock) { + if (sUrandomOut == null) { + sUrandomOut = new FileOutputStream(URANDOM_FILE); + } + return sUrandomOut; + } + } + } + + /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only + // available since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } + } +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java new file mode 100644 index 0000000..abc9f60 --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java @@ -0,0 +1,327 @@ +package com.mjsheng.myappstore.utils.AES; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class CXAESUtil { + /** + * 加解密算法/工作模式/填充方式 + */ + private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; + private final static String HEX = "0123456789ABCDEF"; + private static final int keyLenght = 16; + private static final String defaultV = "0"; + + /** + * 加密 + * + * @param key + * 密钥 + * @param src + * 加密文本 + * @return + * @throws Exception + */ + public static String encrypt(String key, String src) throws Exception { + // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT); + byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes(); + byte[] result = encrypt(rawKey, src.getBytes("utf-8")); + // result = Base64.encode(result, Base64.DEFAULT); + return toHex(result); + } + + /** + * 加密 + * + * @param key + * 密钥 + * @param src + * 加密文本 + * @return + * @throws Exception + */ + public static String encrypt2Java(String key, String src) throws Exception { + // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT); + byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes(); + byte[] result = encrypt2Java(rawKey, src.getBytes("utf-8")); + // result = Base64.encode(result, Base64.DEFAULT); + return toHex(result); + } + + /** + * 解密 + * + * @param key + * 密钥 + * @param encrypted + * 待揭秘文本 + * @return + * @throws Exception + */ + public static String decrypt(String key, String encrypted) throws Exception { + byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes(); + byte[] enc = toByte(encrypted); + // enc = Base64.decode(enc, Base64.DEFAULT); + byte[] result = decrypt(rawKey, enc); + // /result = Base64.decode(result, Base64.DEFAULT); + return new String(result, "utf-8"); + } + + /** + * 密钥key ,默认补的数字,补全16位数,以保证安全补全至少16位长度,android和ios对接通过 + * @param str + * @param strLength + * @param val + * @return + */ + private static String toMakekey(String str, int strLength, String val) { + + int strLen = str.length(); + if (strLen < strLength) { + while (strLen < strLength) { + StringBuffer buffer = new StringBuffer(); + buffer.append(str).append(val); + str = buffer.toString(); + strLen = str.length(); + } + } + return str; + } + + /** + * 真正的加密过程 + * 1.通过密钥得到一个密钥专用的对象SecretKeySpec + * 2.Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES"); + * @param key + * @param src + * @return + * @throws Exception + */ + private static byte[] encrypt(byte[] key, byte[] src) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()])); + byte[] encrypted = cipher.doFinal(src); + return encrypted; + } + + /** + * 真正的加密过程 + * + * @param key + * @param src + * @return + * @throws Exception + */ + private static byte[] encrypt2Java(byte[] key, byte[] src) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()])); + byte[] encrypted = cipher.doFinal(src); + return encrypted; + } + + /** + * 真正的解密过程 + * + * @param key + * @param encrypted + * @return + * @throws Exception + */ + private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()])); + byte[] decrypted = cipher.doFinal(encrypted); + return decrypted; + } + + public static String toHex(String txt) { + return toHex(txt.getBytes()); + } + + public static String fromHex(String hex) { + return new String(toByte(hex)); + } + + + /** + * 把16进制转化为字节数组 + * @param hexString + * @return + */ + public static byte[] toByte(String hexString) { + int len = hexString.length() / 2; + byte[] result = new byte[len]; + for (int i = 0; i < len; i++) + result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue(); + return result; + } + + + /** + * 二进制转字符,转成了16进制 + * 0123456789abcdefg + * @param buf + * @return + */ + public static String toHex(byte[] buf) { + if (buf == null) + return ""; + StringBuffer result = new StringBuffer(2 * buf.length); + for (int i = 0; i < buf.length; i++) { + appendHex(result, buf[i]); + } + return result.toString(); + } + + private static void appendHex(StringBuffer sb, byte b) { + sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f)); + } + + /** + * 初始化 AES Cipher + * @param sKey + * @param cipherMode + * @return + */ + public static Cipher initAESCipher(String sKey, int cipherMode) { + // 创建Key gen + // KeyGenerator keyGenerator = null; + Cipher cipher = null; + try { + /* + * keyGenerator = KeyGenerator.getInstance("AES"); + * keyGenerator.init(128, new SecureRandom(sKey.getBytes())); + * SecretKey secretKey = keyGenerator.generateKey(); byte[] + * codeFormat = secretKey.getEncoded(); SecretKeySpec key = new + * SecretKeySpec(codeFormat, "AES"); cipher = + * Cipher.getInstance("AES"); //初始化 cipher.init(cipherMode, key); + */ + byte[] rawKey = toMakekey(sKey, keyLenght, defaultV).getBytes(); + SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES"); + cipher = Cipher.getInstance("AES"); + cipher.init(cipherMode, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()])); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } catch (NoSuchPaddingException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } catch (InvalidKeyException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } catch (InvalidAlgorithmParameterException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return cipher; + } + + /** + * 对文件进行AES加密 + * @param sourceFile + * @param fileType + * @param sKey + * @return + */ + public static File encryptFile(File sourceFile, String toFile, String dir, String sKey) { + // 新建临时加密文件 + File encrypfile = null; + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = new FileInputStream(sourceFile); + encrypfile = new File(dir + toFile); + outputStream = new FileOutputStream(encrypfile); + Cipher cipher = initAESCipher(sKey, Cipher.ENCRYPT_MODE); + // 以加密流写入文件 + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + byte[] cache = new byte[1024]; + int nRead = 0; + while ((nRead = cipherInputStream.read(cache)) != -1) { + outputStream.write(cache, 0, nRead); + outputStream.flush(); + } + cipherInputStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use + // File | Settings | File Templates. + } + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use + // File | Settings | File Templates. + } + } + return encrypfile; + } + + /** + * AES方式解密文件 + * @param sourceFile + * @return + */ + public static File decryptFile(File sourceFile, String toFile, String dir, String sKey) { + File decryptFile = null; + InputStream inputStream = null; + OutputStream outputStream = null; + try { + decryptFile = new File(dir + toFile); + Cipher cipher = initAESCipher(sKey, Cipher.DECRYPT_MODE); + inputStream = new FileInputStream(sourceFile); + outputStream = new FileOutputStream(decryptFile); + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + byte[] buffer = new byte[1024]; + int r; + while ((r = inputStream.read(buffer)) >= 0) { + cipherOutputStream.write(buffer, 0, r); + } + cipherOutputStream.close(); + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use File | + // Settings | File Templates. + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use + // File | Settings | File Templates. + } + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); // To change body of catch statement use + // File | Settings | File Templates. + } + } + return decryptFile; + } + +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java b/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java index e3ba98f..2bbe89e 100644 --- a/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java +++ b/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java @@ -13,6 +13,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.graphics.Bitmap; import android.net.Uri; import android.os.BatteryManager; import android.os.Build; @@ -29,6 +30,12 @@ import com.blankj.utilcode.util.PathUtils; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.mjsheng.myappstore.BuildConfig; import com.mjsheng.myappstore.action.JGYActions; import com.mjsheng.myappstore.base.BaseApplication; @@ -84,6 +91,7 @@ public class JGYUtils { public static void init(Context context) { if (sInstance == null) { + Log.e(TAG, "init: "); sInstance = new JGYUtils(context); } } @@ -100,6 +108,12 @@ public class JGYUtils { return "official".equals(channelValue); } + public static boolean isOfficialVersion(Context context) { + Log.e(TAG, "isOfficialVersion: " + ProcessUtil.getCurrentProcessName(context)); + String channelValue = JGYUtils.getInstance().getStringMetaData(); + return "official".equals(channelValue); + } + public static boolean isNewlyVersion() { String channelValue = JGYUtils.getInstance().getStringMetaData(); return "beta".equals(channelValue); @@ -227,7 +241,7 @@ public class JGYUtils { Settings.System.putString(mContext.getContentResolver(), "qch_app_power_on", qch_app_power_on); } // if (BuildConfig.DEBUG) { - // TODO: 2021/7/2 测试写入为空是否断网 + // TODO: 2021/7/2 测试写入为空是否断网 // boolean w = Settings.System.putString(mContext.getContentResolver(), "qch_app_power_on", ""); // Log.e(TAG, "setNetAndlaunch: 测试写入: " + w); // } @@ -732,10 +746,10 @@ public class JGYUtils { } } + @SuppressLint("NewApi") public void writeAppPackageList(Context context, String packageList) { - StringBuilder stringBuilder = new StringBuilder(packageList); ApkUtils.addShortcut(context); - List packages = new ArrayList() {{ + HashSet packages = new HashSet() {{ this.add("com.jiaoguanyi.appstore");//设备信息 this.add("com.jiaoguanyi.store");//教管壹 this.add("com.info.sn"); @@ -743,19 +757,18 @@ public class JGYUtils { this.add("com.uiuios.jgy2"); this.add("com.tt.ttutils"); }}; -// if (!TextUtils.isEmpty(stringBuilder)) { - for (String pkg : packages) { - if (!packageList.contains(pkg)) { - stringBuilder.append(","); - stringBuilder.append(pkg); + HashSet pkgSet = new HashSet(Arrays.asList(packageList.split(","))); + pkgSet.addAll(packages); + pkgSet.removeIf(new Predicate() { + @Override + public boolean test(String s) { + return TextUtils.isEmpty(s); } - } - boolean b = Settings.System.putString(mContext.getContentResolver(), "qch_app_forbid", stringBuilder.toString()); - Log.e(TAG, "writeAppPackageList: " + stringBuilder.toString()); + }); + String qch_app_forbid = String.join(",", pkgSet); + Log.e(TAG, "writeAppPackageList: " + qch_app_forbid); + boolean b = Settings.System.putString(mContext.getContentResolver(), "qch_app_forbid", qch_app_forbid); Log.e("writeAppPackageList: ", "qch_app_forbid is :" + b + " " + Settings.System.getString(mContext.getContentResolver(), "qch_app_forbid")); -// } else { -// Log.e("writeAppPackageList: ", "writeAppPackageList is null:"); -// } } public void forceDownload(List data) { @@ -1463,4 +1476,59 @@ public class JGYUtils { return "https://" + url; } } + + public Bitmap createQRImage(String content, int widthPix, int heightPix) { + try { +// if (content == null || "".equals(content)) { +// return false; +// } + + //配置参数 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); + //容错级别 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + //设置空白边距的宽度 + hints.put(EncodeHintType.MARGIN, 1); //default is 4 + + // 图像数据转换,使用了矩阵转换 + BitMatrix bitMatrix = null; + try { + bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, widthPix, + heightPix, hints); + } catch (WriterException e) { + e.printStackTrace(); + } + int[] pixels = new int[widthPix * heightPix]; + // 下面这里按照二维码的算法,逐个生成二维码的图片, + // 两个for循环是图片横列扫描的结果 + for (int y = 0; y < heightPix; y++) { + for (int x = 0; x < widthPix; x++) { + if (bitMatrix.get(x, y)) { + pixels[y * widthPix + x] = 0xff000000; + } else { + pixels[y * widthPix + x] = 0xffffffff; + } + } + } + + // 生成二维码图片的格式,使用ARGB_8888 + Bitmap bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix); +// +// if (logoBm != null) { +// bitmap = addLogo(bitmap, logoBm); +// } + + //必须使用compress方法将bitmap保存到文件中再进行读取。直接返回的bitmap是没有任何压缩的, + // 内存消耗巨大! + return bitmap; +// return bitmap != null && bitmap.compress(Bitmap.CompressFormat.JPEG, 100); + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + } diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java new file mode 100644 index 0000000..575d356 --- /dev/null +++ b/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java @@ -0,0 +1,96 @@ +package com.mjsheng.myappstore.utils; + +import android.app.ActivityManager; +import android.app.Application; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.reflect.Method; +import java.util.List; + +public class ProcessUtil { + private static String currentProcessName; + + /** + * @return 当前进程名 + */ + @Nullable + public static String getCurrentProcessName(@NonNull Context context) { + if (!TextUtils.isEmpty(currentProcessName)) { + return currentProcessName; + } + + //1)通过Application的API获取当前进程名 + currentProcessName = getCurrentProcessNameByApplication(); + if (!TextUtils.isEmpty(currentProcessName)) { + return currentProcessName; + } + + //2)通过反射ActivityThread获取当前进程名 + currentProcessName = getCurrentProcessNameByActivityThread(); + if (!TextUtils.isEmpty(currentProcessName)) { + return currentProcessName; + } + + //3)通过ActivityManager获取当前进程名 + currentProcessName = getCurrentProcessNameByActivityManager(context); + + return currentProcessName; + } + + + /** + * 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。 + */ + public static String getCurrentProcessNameByApplication() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + return Application.getProcessName(); + } + return null; + } + + /** + * 通过反射ActivityThread获取进程名,避免了ipc + */ + public static String getCurrentProcessNameByActivityThread() { + String processName = null; + try { + final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader()) + .getDeclaredMethod("currentProcessName", (Class[]) new Class[0]); + declaredMethod.setAccessible(true); + final Object invoke = declaredMethod.invoke(null, new Object[0]); + if (invoke instanceof String) { + processName = (String) invoke; + } + } catch (Throwable e) { + e.printStackTrace(); + } + return processName; + } + + /** + * 通过ActivityManager 获取进程名,需要IPC通信 + */ + public static String getCurrentProcessNameByActivityManager(@NonNull Context context) { + if (context == null) { + return null; + } + int pid = android.os.Process.myPid(); + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (am != null) { + List runningAppList = am.getRunningAppProcesses(); + if (runningAppList != null) { + for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) { + if (processInfo.pid == pid) { + return processInfo.processName; + } + } + } + } + return null; + } +} diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java b/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java index 95c438f..40a5e4d 100644 --- a/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java +++ b/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import android.text.TextUtils; -import com.mjsheng.myappstore.server.MainService; +import com.mjsheng.myappstore.service.MainService; import java.text.DateFormat; import java.text.ParseException; diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 7d2f612..fe3b6b3 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -12,24 +12,9 @@ android:layout_width="match_parent" android:layout_height="@dimen/dp_32" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - - - + + + + + + + + + + - - - + app:layout_constraintTop_toBottomOf="@+id/bannerLayout"> - + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/imageView"> - - - + app:layout_constraintTop_toBottomOf="@+id/tv_batch"> - - - - - - - - - - - - - - - - - + android:textSize="@dimen/sp_12" + android:visibility="visible" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView" /> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +