version:beta
update: fix: add:增加电子书包二维码,增加获取激活码接口
This commit is contained in:
@@ -2,7 +2,6 @@ package com.mjsheng.myappstore.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -20,11 +19,10 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.mjsheng.myappstore.BuildConfig;
|
||||
import com.mjsheng.myappstore.R;
|
||||
import com.lzy.okgo.model.HttpHeaders;
|
||||
import com.mjsheng.myappstore.server.LogcatService;
|
||||
import com.mjsheng.myappstore.service.LogcatService;
|
||||
import com.mjsheng.myappstore.utils.ServiceAliveUtils;
|
||||
|
||||
|
||||
|
||||
public class HomeActivity extends AppCompatActivity {
|
||||
private static String TAG = HomeActivity.class.getSimpleName();
|
||||
private Button bt_log;
|
||||
@@ -81,7 +79,7 @@ public class HomeActivity extends AppCompatActivity {
|
||||
Log.e(TAG, "DebugTest: qch_hide_NavigationBar: " + Settings.System.getString(getContentResolver(), "qch_hide_NavigationBar"));
|
||||
String only_jgy_shortcut_list = Settings.System.getString(getContentResolver(), "only_jgy_shortcut_list");
|
||||
Log.e(TAG, "debugTest: only_jgy_shortcut_list:" + only_jgy_shortcut_list);
|
||||
String qch_app_forbid = Settings.System.getString(getContentResolver(), "qch_app_forbid");
|
||||
String qch_app_forbid = Settings.System.getString(getContentResolver(), "qch_app_forbid");
|
||||
Log.e(TAG, "debugTest: qch_app_forbid:" + qch_app_forbid);
|
||||
|
||||
// try {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
@@ -21,7 +22,7 @@ import com.google.gson.JsonObject;
|
||||
import com.mjsheng.myappstore.BuildConfig;
|
||||
import com.mjsheng.myappstore.R;
|
||||
import com.mjsheng.myappstore.base.BaseActivity;
|
||||
import com.mjsheng.myappstore.server.MainService;
|
||||
import com.mjsheng.myappstore.service.MainService;
|
||||
import com.mjsheng.myappstore.utils.ApkUtils;
|
||||
import com.mjsheng.myappstore.utils.ExampleUtil;
|
||||
import com.mjsheng.myappstore.utils.JGYUtils;
|
||||
@@ -118,6 +119,7 @@ public class MainActivity extends BaseActivity implements MainContact.MainView {
|
||||
tv_devmac.setText("获取失败");
|
||||
} else {
|
||||
tv_devmac.setText(macaddr);
|
||||
mPresenter.getQRImage(macaddr);
|
||||
}
|
||||
tv_version.setText(BuildConfig.VERSION_NAME);
|
||||
//获取自定义版本
|
||||
@@ -165,6 +167,11 @@ public class MainActivity extends BaseActivity implements MainContact.MainView {
|
||||
mPresenter.getLockedState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQRImage(Bitmap qrImage) {
|
||||
imageView.setImageBitmap(qrImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchText(String text, int visibility) {
|
||||
tv_batch.setText(text);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.mjsheng.myappstore.activity;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -8,6 +9,8 @@ import com.mjsheng.myappstore.base.BaseView;
|
||||
|
||||
public class MainContact {
|
||||
public interface MainView extends BaseView {
|
||||
//获取二维码
|
||||
void setQRImage(Bitmap qrImage);
|
||||
//设置批次信息
|
||||
void setBatchText(String text, int visibility);
|
||||
//设置班级信息
|
||||
@@ -79,6 +82,8 @@ public class MainContact {
|
||||
}
|
||||
|
||||
public interface Presenter extends BasePresenter<MainView> {
|
||||
//设置二维码
|
||||
void getQRImage(String mac);
|
||||
//获取学生信息
|
||||
void getStudesInfo();
|
||||
//获取设备锁定状态
|
||||
|
||||
@@ -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获取用户信息
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.mjsheng.myappstore.server;
|
||||
package com.mjsheng.myappstore.service;
|
||||
|
||||
/**
|
||||
* 作者 mjsheng
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.mjsheng.myappstore.server;
|
||||
package com.mjsheng.myappstore.service;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
package com.mjsheng.myappstore.server;
|
||||
package com.mjsheng.myappstore.service;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.mjsheng.myappstore.server;
|
||||
package com.mjsheng.myappstore.service;
|
||||
|
||||
/**
|
||||
* 作者 mjsheng
|
||||
199
app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java
Normal file
199
app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java
Normal file
@@ -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.
|
||||
* <p/>
|
||||
* 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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
170
app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java
Normal file
170
app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <code>base64(iv):base64(ciphertext)</code>.
|
||||
*
|
||||
* @param base64IvAndCiphertext A string of the format
|
||||
* <code>iv:ciphertext</code> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> packages = new ArrayList<String>() {{
|
||||
HashSet<String> packages = new HashSet<String>() {{
|
||||
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<String> pkgSet = new HashSet<String>(Arrays.asList(packageList.split(",")));
|
||||
pkgSet.addAll(packages);
|
||||
pkgSet.removeIf(new Predicate<String>() {
|
||||
@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<ForceDownloadData> 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<EncodeHintType, Object> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
|
||||
if (runningAppList != null) {
|
||||
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
|
||||
if (processInfo.pid == pid) {
|
||||
return processInfo.processName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user