android开发过程中,对一些需要保护的数据,不能以明文形式出现,需要加密,特别是在网络传输中,永远要给自己一个网络不安全的思想来传输数据。加密方式
1、对称加密 加密和解密都是使用一个秘钥
优点: 对数据没有长度限制,加解密速度快
缺点: 秘钥的传输及保管是个问题,任何一方的秘钥泄漏都将导致数据的不安全
在众多的对称加密算法中,目前来讲AES是最好的,最安全的
2、非对称加密 非对称加密首先要生成一对秘钥,分为公钥和私钥
公钥和私钥能够相互解密。
优点:无需关心秘钥的传输问题,只要在服务端把私钥保管好即可
缺点:加密速度慢,对加密的数据长度有限制
费对称加密算法中,RSA用的最多的
好了,上面说了那么多,和主题有点偏了,就当普及知识点吧。一般考虑到对称加密和非对称加密的优缺点,如果将二者综合一下,就能较完美解决加密需求了。AES 秘钥保护,RSA 加密数据限制和速度,可以选择RSA结合AES进行加密。
我们知道了RSA虽然能过做到身份验证,但是其加密速度较慢,并且不适合对大数据进行加密。
那么,单纯的使用RSA加密肯定是不可取的。
而AES的加密速度快且对加密数据没有大小限制,但是,秘钥的传输及管理是个问题,那么,我们把两者结合起来取长补短不就好了吗?
我们可以这样:
以服务端给客户端传输数据为例
1. 服务端生成一对RSA秘钥,私钥 RSA_PRV_KEY 放在服务端,公钥RSA_PUB_KEY 下发给客户端
2. 客户端生成AES加密要用的 AES_KEY
3. 客户端 通过拿到的 RSA_PUB_KEY 对自己生成的AES_KEY 加密后下发给服务端
**4.**服务端 通过RSA_PRV_KEY 解密客户端发送过来的加密过的AES_KEY,得到了客户端的AES_KEY
5. 其实走完第4步,服务端有了AES_KEY ,客户端在传输AES_KEY 安全也得到了保障,别人也没有RSA_PRV_KEY,拿到了也没有,当然RSA_PRV_KEY泄露就没办法了。
6. 客户端 使用AES_KEY 加密数据传输给 服务端,
7. 服务端获取数据,通过AES_KEY 解密数据
8. 服务端 同样使用 AES_KEY 加密数据 给客户端
9. 客户端收到数据,通过AES_KEY 解密数据
好了上面就是一个很简单的AES 和 RSA结合的加密方案,如果要更安全还可以加入签名以及双方认证方式。在这就不延伸拓展了。
AES作为对称加密,加密和解密都是使用一个秘钥,对数据没有长度限制,加解密速度快,使用常常用来对数据的加密,
AES秘钥支持128bit/192bit/256bit三种长度的秘钥,一个字节等于8bit, 因此生成的字符串的长度应该是 16/24/32这三种长度
AES加密模式 四种:ECB、CBC、CFB、OFB
我选在使用 128 256 秘钥 AES/CBC/PKCS5Padding了。AES的秘钥保护是主要的问题 ,秘钥的传输及保管是个问题,任何一方的秘钥泄漏都将导致数据的不安全。
这就引发了一个新问题
链接: link. Android开发如何保障本地加密密钥的安全?
有以下方法:
1、密钥直接明文存在sharedprefs文件中,这是最不安全的。
2、密钥直接硬编码在Java代码中,这很不安全,dex文件很容易被逆向成java代码。
3、将密钥分成不同的几段,有的存储在文件中、有的存储在代码中,最后将他们拼接起来,可以将整个操作写的很复杂,这因为还是在java层,逆向者只要花点时间,也很容易被逆向。
4、用ndk开发,将密钥放在so文件,加密解密操作都在so文件里,这从一定程度上提高了的安全性,挡住了一些逆向者,但是有经验的逆向者还是会使用IDA破解的。
5、在so文件中不存储密钥,so文件中对密钥进行加解密操作,将密钥加密后的密钥命名为其他普通文件,存放在assets目录下或者其他目录下,接着在so文件里面添加无关代码(花指令),虽然可以增加静态分析难度,但是可以使用动态调式的方法,追踪加密解密函数,也可以查找到密钥内容。
可以说在设备上安全存储密钥这个基本无解,只能选择增大逆向成本。而要是普通开发者的话,这需要耗费很大的心血,要评估你的app应用的重要程度来选择相应的技术方案。
本人在客户端使用了方法4 用ndk开发,将密钥放在so文件,加密解密操作都在so文件里。
当然我还有一个方法6已经实现了,但是没有必要用上,等后续项目可以使用上,使用TEE,把秘钥放到 TEE TA端,CA调用TA,java JNI 调用 CA 使用。
TA的破解会有很大难度,之前的项目,指纹数据就是存放在TA端,大家如果有兴趣可以去了解一下TEE
客户端使用JNI C++实现 AES 秘钥存放,加密和解密,同时还提供了接口 C++秘钥获取,向量获取。
//定义一个方法,该方法在C中实现
public native String encrypt(String plainText);
public native String decrypt(String cipherText);
public native String getKeyValue();
public native String getIv();
客户端java层通过JNI ,调用so文件直接加密解密。不同平台上加密出来的密文是不一样的,这样导致互相之间密文无法使用。于是决定使用C/C++完成加密解密,其他平台调用的方式进行处理。
为了兼容java实现了,也可以 getKeyValue() 获取秘钥 和 getIv() 获取向量 通过纯java来实现AES 加密解密。
AES加密的具体实现过程本文暂不讨论,实现代码是直接从openssl源码中抽出来。
public native String encrypt(String plainText);
public native String decrypt(String cipherText);
输入明文 和秘文 加密 解密处理。
加密和解密 C源码如下
JNIEXPORT jstring JNICALL Java_com_revo_aesrsa_JniUtil_encrypt
(JNIEnv *env, jobject instance, jstring jstr){
if (jstr == NULL) {
return NULL;
}
return encrypt(env,jstr);
}
JNIEXPORT jstring JNICALL Java_com_revo_aesrsa_JniUtil_decrypt
(JNIEnv *env, jobject instance, jstring jstr){
if (jstr == NULL) {
return NULL;
}
return decrypt(env,jstr);
}
//转成成字符串类型
jstring charToJstring(JNIEnv* envPtr, char *src) {
JNIEnv env = *envPtr;
jsize len = strlen(src);
jclass clsstring = env->FindClass(envPtr, "java/lang/String");
jstring strencode = env->NewStringUTF(envPtr, "UTF-8");
jmethodID mid = env->GetMethodID(envPtr, clsstring, "" , "([BLjava/lang/String;)V");
jbyteArray barr = env->NewByteArray(envPtr, len);
env->SetByteArrayRegion(envPtr, barr, 0, len, (jbyte*) src);
return (jstring) env->NewObject(envPtr, clsstring, mid, barr, strencode);
}
jstring encrypt(JNIEnv* envPtr, jstring mingwen) {
JNIEnv env = *envPtr;
//wojiubugaosunimenglalisadeyanlei 秘钥
unsigned char key[32] = {0x77,0x6f,0x6a,0x69,
0x75,0x62,0x75,0x67,
0x61,0x6f,0x73,0x75,
0x6e,0x69,0x6d,0x65,
0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69};
//****************************************开始加密******************************************************
//1.初始化数据
//初始化向量 nglalisadeyanlei
uint8_t iv[16] = { 0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69 };
//初始化加密参数
aes256_context ctx;
aes256_init(&ctx, key);
//2.将jstring转为char
const char *mwChar = env->GetStringUTFChars(envPtr, mingwen, JNI_FALSE);
//3.分组填充加密
int i;
int mwSize = strlen(mwChar);
int remainder = mwSize % 16;
jstring entryptString;
if (mwSize < 16) { //小于16字节,填充16字节,后面填充几个几 比方说10个字节 就要补齐6个6 11个字节就补齐5个5
uint8_t input[16];
for (i = 0; i < 16; i++) {
if (i < mwSize) {
input[i] = (unsigned char) mwChar[i];
} else {
input[i] = (unsigned char) (16 - mwSize);
}
}
//加密
uint8_t output[16];
aes256_encrypt_cbc(&ctx, input, iv, output);
//base64加密后然后jstring格式输出
char *enc = base64_encode((const char *) output, sizeof(output));
entryptString = charToJstring(envPtr, enc);
free(enc);
} else { //如果是16的倍数,填充16字节,后面填充0x10
int group = mwSize / 16;
int size = 16 * (group + 1);
uint8_t input[size];
for (i = 0; i < size; i++) {
if (i < mwSize) {
input[i] = (unsigned char) mwChar[i];
} else {
if (remainder == 0) {
input[i] = 0x10;
} else { //如果不足16位 少多少位就补几个几 如:少4为就补4个4 以此类推
int dif = size - mwSize;
input[i] = (unsigned char) dif;
}
}
}
//加密
uint8_t output[size];
aes256_encrypt_cbc(&ctx, input, iv, output);
//base64加密后然后jstring格式输出
//LOGD("encrypt output size=%d",size);
char *enc = base64_encode((const char *) output, sizeof(output));
// LOGD("encrypt enc=%s",enc);
entryptString = charToJstring(envPtr, enc);
free(enc);
}
//释放mwChar
env->ReleaseStringUTFChars(envPtr, mingwen, mwChar);
return entryptString;
}
jstring decrypt(JNIEnv* env, jstring miwen) {
jstring result;
//wojiubugaosunimenglalisadeyanlei 秘钥
unsigned char key[32] = {0x77,0x6f,0x6a,0x69,
0x75,0x62,0x75,0x67,
0x61,0x6f,0x73,0x75,
0x6e,0x69,0x6d,0x65,
0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69};
//****************************************开始解密******************************************************
//1.初始化数据
//初始化向量 nglalisadeyanlei
uint8_t iv[16] = { 0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69 };
aes256_context ctx;
aes256_init(&ctx, key);
//2.将jstring转为char
const char *mwChar = (*env)->GetStringUTFChars(env, miwen, JNI_FALSE);
char *enc = base64_decode(mwChar, strlen(mwChar));
uint8_t output[4096];
aes256_decrypt_cbc(&ctx, (unsigned char *) enc, iv, output);
int size = strlen((const char *) output);
LOGD("output size=%d",size);
int i;
for(i=0;i<size;i++){
//LOGD("cha %d = %c",i,output[i]);
if(output[i]>=1&&output[i]<=16){
output[i] = 0;
}
}
result = charToJstring(env, (char *) output);
//LOGD("result=%s",(char *) output);
free(enc);
//释放mwChar
(*env)->ReleaseStringUTFChars(env, miwen, mwChar);
aes256_done(&ctx);
return result;
}
还提供二个接口方法
const uint8_t AesKey[32] = {
0x77,0x6f,0x6a,0x69,
0x75,0x62,0x75,0x67,
0x61,0x6f,0x73,0x75,
0x6e,0x69,0x6d,0x65,
0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69
};
const uint8_t AesIv[16] = {
0x6e,0x67,0x6c,0x61,
0x6c,0x69,0x73,0x61,
0x64,0x65,0x79,0x61,
0x6e,0x6c,0x65,0x69
};
/*
* Class: com_revo_aesrsa_JniUtil
* Method: getKeyValue
* Signature: ()Ljava/lang/String;
*/
extern "C" {
JNIEXPORT jstring JNICALL Java_com_revo_aesrsa_JniUtil_getKeyValue
(JNIEnv *env, jobject obj){
jbyteArray KeyArray = env->NewByteArray(sizeof(AesKey));
jbyte *bytes = env->GetByteArrayElements(KeyArray, 0);
int i;
for (i = 0; i < sizeof(AesKey); i++){
//__android_log_print(ANDROID_LOG_ERROR,LOG,"AesIv的是%d",AesKey[i]);
bytes[i] = (jbyte) (AesKey[i]);
//__android_log_print(ANDROID_LOG_ERROR,LOG,"bytes[i] %d",bytes[i]);
}
env->SetByteArrayRegion(KeyArray, 0, sizeof(iv), bytes);
env->ReleaseByteArrayElements(KeyArray,bytes,0);
jclass strClass = env->FindClass("java/lang/String");
jmethodID ctorID = env->GetMethodID(strClass, "" , "([BLjava/lang/String;)V");
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, KeyArray, encoding);
}
}
/*
* Class: com_revo_aesrsa_JniUtil
* Method: getIv
* Signature: ()Ljava/lang/String;
*/
extern "C" {
JNIEXPORT jstring JNICALL Java_com_revo_aesrsa_JniUtil_getIv
(JNIEnv *env, jobject obj){
jbyteArray ivArray = env->NewByteArray(sizeof(AesIv));
jbyte *bytes = env->GetByteArrayElements(ivArray, 0);
int i;
for (i = 0; i < sizeof(AesIv); i++){
// __android_log_print(ANDROID_LOG_ERROR,LOG,"AesIv的是%d",AesIv[i]);
bytes[i] = (jbyte) (AesIv[i]);
// __android_log_print(ANDROID_LOG_ERROR,LOG,"bytes[i] %d",bytes[i]);
}
env->SetByteArrayRegion(ivArray, 0, sizeof(iv), bytes);
env->ReleaseByteArrayElements(ivArray,bytes,0);
jclass strClass = env->FindClass("java/lang/String");
jmethodID ctorID = env->GetMethodID(strClass, "" , "([BLjava/lang/String;)V");
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, ivArray, encoding);
}
}
java层调用so文件JniUtil.java
public class JniUtil {
static {
System.loadLibrary("JniUtil");
}
public native String encrypt(String plainText);
public native String decrypt(String cipherText);
public native String getKeyValue();
public native String getIv();
}
具体使用 MainActivity.java
public class MainActivity extends AppCompatActivity {
Button button,enbtn,debtn;
TextView tv;
private JniUtil jniUtil;
private String ming = "woshiyigemingwen";
private String encrypmi,decrypmi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
enbtn = findViewById(R.id.button_encrpty);
debtn = findViewById(R.id.button_decrpty);
tv = findViewById(R.id.tv);
jniUtil = new JniUtil();
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tv.setText("获取key:"+ jniUtil.getKeyValue());
}
});
enbtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
encrypmi = jniUtil.encrypt(ming);
Log.e("fuckdemo","JNI加密AES256--base64后:"+ encrypmi);
tv.setText("加密后:"+ encrypmi);
}
});
debtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
decrypmi = jniUtil.decrypt(encrypmi);
Log.e("fuckdemo","JNI解密AES256--base64后:"+ decrypmi);
tv.setText("解密后:"+ decrypmi);
}
});
tv.setText("\n加密前:" + ming );
//java加解密
String encyptdateStr = AES.encryptToBase64(ming, jniUtil.getKeyValue().substring(0,16), jniUtil.getIv());
Log.e("fuckdemo","java加密AES128--base64后:"+ encyptdateStr);
String decyptdateStr = AES.decryptFromBase64(encyptdateStr, jniUtil.getKeyValue().substring(0,16), jniUtil.getIv());
Log.e("fuckdemo","java解密AES128--base64后:"+ decyptdateStr);
String javaEncrypt= AesUtils.aesEncrypt(ming, jniUtil.getKeyValue() + jniUtil.getIv(), jniUtil.getIv());
Log.e("fuckdemo","java解密AES256--base64后:"+ javaEncrypt);
String javaDecrypt = AesUtils.aesDecrypt(javaEncrypt, jniUtil.getKeyValue() + jniUtil.getIv(), jniUtil.getIv());
Log.e("fuckdemo","java解密AES256--base64后:"+ javaDecrypt);
}
}
AES 256 加密和解密 还使用了编码 base64
aes256.c aes256.h base64.c base64.h 没有展示出来,直接是从openssl源码拿的。
好了如果需要源码可以直接跳到最后
AesUtils.java 实现 256 AES/CBC/PKCS5Padding
public class AesUtils {
public static final String bm = "utf-8";
/**
* 字节数组转化为大写16进制字符串
*
* @param b
* @return
*/
private static String byte2HexStr(byte[] b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; i++) {
String s = Integer.toHexString(b[i] & 0xFF);
if (s.length() == 1) {
sb.append("0");
}
sb.append(s.toUpperCase());
}
return sb.toString();
}
/**
* 16进制字符串转字节数组
*
* @param s
* @return
*/
private static byte[] str2ByteArray(String s) {
int byteArrayLength = s.length() / 2;
byte[] b = new byte[byteArrayLength];
for (int i = 0; i < byteArrayLength; i++) {
byte b0 = (byte) Integer.valueOf(s.substring(i * 2, i * 2 + 2), 16)
.intValue();
b[i] = b0;
}
return b;
}
/**
* AES 加密
*
* @param content
* 明文
* @param password
* 生成秘钥的关键字
* @return
*/
public static String aesEncrypt(String content, String password,String ivparm) {
try {
IvParameterSpec zeroIv = new IvParameterSpec(ivparm.getBytes());
SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, zeroIv);
byte[] encryptedData = cipher.doFinal(content.getBytes(bm));
return new String(Base64.encode(encryptedData));
//return Base64ex.encode(encryptedData);
// return byte2HexStr(encryptedData);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
/**
* AES 解密
*
* @param content
* 密文
* @param password
* 生成秘钥的关键字
* @return
*/
public static String aesDecrypt(String content, String password,String ivparm) {
try {
byte[] byteMi = Base64.decode(content.getBytes());
//byte[] byteMi = Base64ex.decode(content);
// byte[] byteMi= str2ByteArray(content);
IvParameterSpec zeroIv = new IvParameterSpec(ivparm.getBytes());
SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, zeroIv);
byte[] decryptedData = cipher.doFinal(byteMi);
return new String(decryptedData, "utf-8");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
}
Base64.java base64编码 结合AES。
public class Base64 {
/**
* Chunk size per RFC 2045 section 6.8.
*
* The {@value} character limit does not count the trailing CRLF, but counts
* all other characters, including any equal signs.
*
* @see RFC 2045 section 6.8
*/
static final int CHUNK_SIZE = 76;
/**
* Chunk separator per RFC 2045 section 2.1.
*
* @see RFC 2045 section 2.1
*/
static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
/**
* The base length.
*/
static final int BASELENGTH = 255;
/**
* Lookup length.
*/
static final int LOOKUPLENGTH = 64;
/**
* Used to calculate the number of bits in a byte.
*/
static final int EIGHTBIT = 8;
/**
* Used when encoding something which has fewer than 24 bits.
*/
static final int SIXTEENBIT = 16;
/**
* Used to determine how many bits data contains.
*/
static final int TWENTYFOURBITGROUP = 24;
/**
* Used to get the number of Quadruples.
*/
static final int FOURBYTE = 4;
/**
* Used to test the sign of a byte.
*/
static final int SIGN = -128;
/**
* Byte used to pad output.
*/
static final byte PAD = (byte) '=';
// Create arrays to hold the base64 characters and a
// lookup for base64 chars
private static byte[] base64Alphabet = new byte[BASELENGTH];
private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
// Populating the lookup and character arrays
static {
for (int i = 0; i < BASELENGTH; i++) {
base64Alphabet[i] = (byte) -1;
}
for (int i = 'Z'; i >= 'A'; i--) {
base64Alphabet[i] = (byte) (i - 'A');
}
for (int i = 'z'; i >= 'a'; i--) {
base64Alphabet[i] = (byte) (i - 'a' + 26);
}
for (int i = '9'; i >= '0'; i--) {
base64Alphabet[i] = (byte) (i - '0' + 52);
}
base64Alphabet['+'] = 62;
base64Alphabet['/'] = 63;
for (int i = 0; i <= 25; i++) {
lookUpBase64Alphabet[i] = (byte) ('A' + i);
}
for (int i = 26, j = 0; i <= 51; i++, j++) {
lookUpBase64Alphabet[i] = (byte) ('a' + j);
}
for (int i = 52, j = 0; i <= 61; i++, j++) {
lookUpBase64Alphabet[i] = (byte) ('0' + j);
}
lookUpBase64Alphabet[62] = (byte) '+';
lookUpBase64Alphabet[63] = (byte) '/';
}
private static boolean isBase64(byte octect) {
if (octect == PAD) {
return true;
} else if (base64Alphabet[octect] == -1) {
return false;
} else {
return true;
}
}
/**
* Tests a given byte array to see if it contains
* only valid characters within the Base64 alphabet.
*
* @param arrayOctect byte array to test
* @return true if all bytes are valid characters in the Base64
* alphabet or if the byte array is empty; false, otherwise
*/
public static boolean isArrayByteBase64(byte[] arrayOctect) {
arrayOctect = discardWhitespace(arrayOctect);
int length = arrayOctect.length;
if (length == 0) {
// shouldn't a 0 length array be valid base64 data?
// return false;
return true;
}
for (int i = 0; i < length; i++) {
if (!isBase64(arrayOctect[i])) {
return false;
}
}
return true;
}
/**
* Encodes binary data using the base64 algorithm but
* does not chunk the output.
*
* @param binaryData binary data to encode
* @return Base64 characters
*/
public static byte[] encodeBase64(byte[] binaryData) {
return encodeBase64(binaryData, false);
}
/**
* Encodes binary data using the base64 algorithm and chunks
* the encoded output into 76 character blocks
*
* @param binaryData binary data to encode
* @return Base64 characters chunked in 76 character blocks
*/
public static byte[] encodeBase64Chunked(byte[] binaryData) {
return encodeBase64(binaryData, true);
}
/**
* Decodes a byte[] containing containing
* characters in the Base64 alphabet.
*
* @param pArray A byte array containing Base64 character data
* @return a byte array containing binary data
*/
public static byte[] decode(byte[] pArray) {
return decodeBase64(pArray);
}
/**
* Encodes binary data using the base64 algorithm, optionally
* chunking the output into 76 character blocks.
*
* @param binaryData Array containing binary data to encode.
* @param isChunked if isChunked is true this encoder will chunk
* the base64 output into 76 character blocks
* @return Base64-encoded data.
*/
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
int lengthDataBits = binaryData.length * EIGHTBIT;
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
byte encodedData[] = null;
int encodedDataLength = 0;
int nbrChunks = 0;
if (fewerThan24bits != 0) {
//data not divisible by 24 bit
encodedDataLength = (numberTriplets + 1) * 4;
} else {
// 16 or 8 bit
encodedDataLength = numberTriplets * 4;
}
// If the output is to be "chunked" into 76 character sections,
// for compliance with RFC 2045 MIME, then it is important to
// allow for extra length to account for the separator(s)
if (isChunked) {
nbrChunks =
(CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE));
encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length;
}
encodedData = new byte[encodedDataLength];
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
int encodedIndex = 0;
int dataIndex = 0;
int i = 0;
int nextSeparatorIndex = CHUNK_SIZE;
int chunksSoFar = 0;
//log.debug("number of triplets = " + numberTriplets);
for (i = 0; i < numberTriplets; i++) {
dataIndex = i * 3;
b1 = binaryData[dataIndex];
b2 = binaryData[dataIndex + 1];
b3 = binaryData[dataIndex + 2];
//log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 =
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 =
((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
byte val3 =
((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
//log.debug( "val2 = " + val2 );
//log.debug( "k4 = " + (k<<4) );
//log.debug( "vak = " + (val2 | (k<<4)) );
encodedData[encodedIndex + 1] =
lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex + 2] =
lookUpBase64Alphabet[(l << 2) | val3];
encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
encodedIndex += 4;
// If we are chunking, let's put a chunk separator down.
if (isChunked) {
// this assumes that CHUNK_SIZE % 4 == 0
if (encodedIndex == nextSeparatorIndex) {
System.arraycopy(
CHUNK_SEPARATOR,
0,
encodedData,
encodedIndex,
CHUNK_SEPARATOR.length);
chunksSoFar++;
nextSeparatorIndex =
(CHUNK_SIZE * (chunksSoFar + 1)) +
(chunksSoFar * CHUNK_SEPARATOR.length);
encodedIndex += CHUNK_SEPARATOR.length;
}
}
}
// form integral number of 6-bit groups
dataIndex = i * 3;
if (fewerThan24bits == EIGHTBIT) {
b1 = binaryData[dataIndex];
k = (byte) (b1 & 0x03);
//log.debug("b1=" + b1);
//log.debug("b1<<2 = " + (b1>>2) );
byte val1 =
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
encodedData[encodedIndex + 2] = PAD;
encodedData[encodedIndex + 3] = PAD;
} else if (fewerThan24bits == SIXTEENBIT) {
b1 = binaryData[dataIndex];
b2 = binaryData[dataIndex + 1];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 =
((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 =
((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex + 1] =
lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
encodedData[encodedIndex + 3] = PAD;
}
if (isChunked) {
// we also add a separator to the end of the final chunk.
if (chunksSoFar < nbrChunks) {
System.arraycopy(
CHUNK_SEPARATOR,
0,
encodedData,
encodedDataLength - CHUNK_SEPARATOR.length,
CHUNK_SEPARATOR.length);
}
}
return encodedData;
}
/**
* Decodes Base64 data into octects
*
* @param base64Data Byte array containing Base64 data
* @return Array containing decoded data.
*/
public static byte[] decodeBase64(byte[] base64Data) {
// RFC 2045 requires that we discard ALL non-Base64 characters
base64Data = discardNonBase64(base64Data);
// handle the edge case, so we don't have to worry about it later
if (base64Data.length == 0) {
return new byte[0];
}
int numberQuadruple = base64Data.length / FOURBYTE;
byte decodedData[] = null;
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
// Throw away anything not in base64Data
int encodedIndex = 0;
int dataIndex = 0;
{
// this sizes the output array properly - rlw
int lastData = base64Data.length;
// ignore the '=' padding
while (base64Data[lastData - 1] == PAD) {
if (--lastData == 0) {
return new byte[0];
}
}
decodedData = new byte[lastData - numberQuadruple];
}
for (int i = 0; i < numberQuadruple; i++) {
dataIndex = i * 4;
marker0 = base64Data[dataIndex + 2];
marker1 = base64Data[dataIndex + 3];
b1 = base64Alphabet[base64Data[dataIndex]];
b2 = base64Alphabet[base64Data[dataIndex + 1]];
if (marker0 != PAD && marker1 != PAD) {
//No PAD e.g 3cQl
b3 = base64Alphabet[marker0];
b4 = base64Alphabet[marker1];
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex + 1] =
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
} else if (marker0 == PAD) {
//Two PAD e.g. 3c[Pad][Pad]
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
} else if (marker1 == PAD) {
//One PAD e.g. 3cQ[Pad]
b3 = base64Alphabet[marker0];
decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex + 1] =
(byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
}
encodedIndex += 3;
}
return decodedData;
}
/**
* Discards any whitespace from a base-64 encoded block.
*
* @param data The base-64 encoded data to discard the whitespace
* from.
* @return The data, less whitespace (see RFC 2045).
*/
static byte[] discardWhitespace(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case (byte) ' ' :
case (byte) '\n' :
case (byte) '\r' :
case (byte) '\t' :
break;
default:
groomedData[bytesCopied++] = data[i];
}
}
byte packedData[] = new byte[bytesCopied];
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
return packedData;
}
/**
* Discards any characters outside of the base64 alphabet, per
* the requirements on page 25 of RFC 2045 - "Any characters
* outside of the base64 alphabet are to be ignored in base64
* encoded data."
*
* @param data The base-64 encoded data to groom
* @return The data, less non-base64 characters (see RFC 2045).
*/
static byte[] discardNonBase64(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
for (int i = 0; i < data.length; i++) {
if (isBase64(data[i])) {
groomedData[bytesCopied++] = data[i];
}
}
byte packedData[] = new byte[bytesCopied];
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
return packedData;
}
/**
* Encodes a byte[] containing binary data, into a byte[] containing
* characters in the Base64 alphabet.
*
* @param pArray a byte array containing binary data
* @return A byte array containing only Base64 character data
*/
public static byte[] encode(byte[] pArray) {
return encodeBase64(pArray, false);
}
public static String encode(String str) throws UnsupportedEncodingException
{
String baseStr = new String(encode(str.getBytes("UTF-8")));
String tempStr = Digest.digest(str).toUpperCase();
String result = tempStr+baseStr;
return new String(encode(result.getBytes("UTF-8")));
}
public static String decode(String cryptoStr) throws
UnsupportedEncodingException {
if(cryptoStr.length()<40)
return "";
try
{
String tempStr = new String(decode(cryptoStr.getBytes("UTF-8")));
String result = tempStr.substring(40, tempStr.length());
return new String(decode(result.getBytes("UTF-8")));
}
catch(ArrayIndexOutOfBoundsException ex)
{
return "";
}
}
/**
* Decodes Base64 data into octects
*
* @param encoded string containing Base64 data
* @return Array containind decoded data.
*/
public static byte[] decode2(String encoded) {
if (encoded == null) {
return null;
}
char[] base64Data = encoded.toCharArray();
// remove white spaces
int len = removeWhiteSpace(base64Data);
if (len % FOURBYTE != 0) {
return null;//should be divisible by four
}
int numberQuadruple = (len / FOURBYTE);
if (numberQuadruple == 0) {
return new byte[0];
}
byte decodedData[] = null;
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
int i = 0;
int encodedIndex = 0;
int dataIndex = 0;
decodedData = new byte[(numberQuadruple) * 3];
for (; i < numberQuadruple - 1; i++) {
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
|| !isData((d3 = base64Data[dataIndex++]))
|| !isData((d4 = base64Data[dataIndex++]))) {
return null;
}//if found "no data" just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
return null;//if found "no data" just return null
}
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
d3 = base64Data[dataIndex++];
d4 = base64Data[dataIndex++];
if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters
if (isPad(d3) && isPad(d4)) {
if ((b2 & 0xf) != 0)//last 4 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 1];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
return tmp;
} else if (!isPad(d3) && isPad(d4)) {
b3 = base64Alphabet[d3];
if ((b3 & 0x3) != 0)//last 2 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 2];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
return tmp;
} else {
return null;
}
} else { //No PAD e.g 3cQl
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
return decodedData;
}
private static boolean isWhiteSpace(char octect) {
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
}
private static boolean isData(char octect) {
return (octect < BASELENGTH && base64Alphabet[octect] != -1);
}
private static boolean isPad(char octect) {
return (octect == PAD);
}
/**
* remove WhiteSpace from MIME containing encoded Base64 data.
*
* @param data the byte array of base64 data (with WS)
* @return the new length
*/
private static int removeWhiteSpace(char[] data) {
if (data == null) {
return 0;
}
// count characters that's not whitespace
int newSize = 0;
int len = data.length;
for (int i = 0; i < len; i++) {
if (!isWhiteSpace(data[i])) {
data[newSize++] = data[i];
}
}
return newSize;
}
}
好了在MainActivity.java 使用
String javaEncrypt= AesUtils.aesEncrypt(ming, jniUtil.getKeyValue(), jniUtil.getIv());
Log.e("fuckdemo","java解密AES256--base64后:"+ javaEncrypt);
String javaDecrypt = AesUtils.aesDecrypt(javaEncrypt, jniUtil.getKeyValue(), jniUtil.getIv());
Log.e("fuckdemo","java解密AES256--base64后:"+ javaDecrypt);
AesUtils.aesEncrypt(ming, jniUtil.getKeyValue(), jniUtil.getIv());
使用jni获取so库里面的秘钥和向量。
上面C源码 加密 解密,也有java 加密 解密,打印log出来,二种方式相互直接 加密和解密 都可以正常解密和加密。其实本来的目的是通过so文件兼容多个平台。但是为了服务器端纯java源码,我这边也做了个java实现。只要商量和AES的模式和秘钥等,当然源码我还补上了RSA.java ,来支持前文说的 ##加密方案 RSA 和 AES的结合。
你只要使用
String ncrypt_AESKey = RSA.encrypt(jniUtil.getKeyValue(),RSA.serverPublicKey); 就对AES秘钥RSA加密了,
服务器就可以通过 RSA.decrypt(ncrypt_AESKey,RSA.serverPrivateKey); 解密出AES 秘钥了。
源码在此
如果对JNI开发还不熟悉的话,可以看我的上一篇 AndroidStudio3.5 NDK JNI开发
正如我们已经有了so文件了,如果服务端也使用so文件,或者其他项目使用该so文件进行加密和解密怎么使用呢。
在其他工程下面要对应有jni 头文件的 包名和类型,新建一个so 头文件 包名和类名一样。
build.gradle 写入 jniLibs重定向到libs目录
ndk{
moduleName "JniLib"
abiFilters "arm64-v8a","armeabi-v7a","x86","x86_64" //输出指定的三种abi体系下的so库"armeabi",只加载armabi架构(目录下)的so库,如果是别的架构,就会找不到
}
sourceSets{ //不配的话都会有一个默认值 可以指定哪些源文件(或文件夹下的源文件)要被编译,哪些源文件要被排除
main{
//jni.srcDirs = [] //禁用as自动生成mk
//jniLibs.srcDirs=["src/main/libs" ] //so包就去src/main/libs目录下找
jniLibs.srcDirs=['libs']
}
}
操作完了,就可以像上面MainActivity 调用使用了。
好了,先这样了,篇幅有点长,还有很多没写,也感觉还有很多没说清楚,就到此为止吧。谢谢各位看官