android 使用jni AES C++ java 实现数据加密解密

android 使用jni AES 对数据进行加密解密

    • 前言
    • 加密方案
    • AES简介
    • AES C++实现
    • AES Java实现
    • 尾巴
    • 彩蛋
            • Android Studio下JNI编程(引入外部so文件)

前言

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作为对称加密,加密和解密都是使用一个秘钥,对数据没有长度限制,加解密速度快,使用常常用来对数据的加密,
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 C++实现

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源码拿的。

好了如果需要源码可以直接跳到最后

AES Java实现

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开发

Android Studio下JNI编程(引入外部so文件)

正如我们已经有了so文件了,如果服务端也使用so文件,或者其他项目使用该so文件进行加密和解密怎么使用呢。
android 使用jni AES C++ java 实现数据加密解密_第1张图片
在其他工程下面要对应有jni 头文件的 包名和类型,新建一个so 头文件 包名和类名一样。

android 使用jni AES C++ java 实现数据加密解密_第2张图片
在工程下 app\libs文件放入 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 调用使用了。

好了,先这样了,篇幅有点长,还有很多没写,也感觉还有很多没说清楚,就到此为止吧。谢谢各位看官

你可能感兴趣的:(android,android,加密解密,jni)