首先,AES的加解密算法来自于OpenSSL库,Android系统已经集成了这个算法,Java中可以直接导入类,在Android系统的native层中c和c++的使用时也可以直接从系统库中链接到OpenSSL库(NDK使用和c++是一样的,都是在Android.mk或者CMakeLists.txt中链接),关于头文件 因为使用的是系统库,因此可以直接include,当然自己编的库那就一定要有头文件了,可以从OpenSSL官网获取,还有一个问题:如果加解密时需要用到base64去编解码,那就得自己编库,系统库里好像不包含,编译会报错
1、Android.mk中链接OpenSSL库:
LOCAL_SHARED_LIBRARIES := \
libcrypto\
libssl
2、CMakeLists.txt中链接OpenSSL库:
include_directories(include)
# import openssl library files BEGIN
set(SSL_LIB_NAME ssl)
add_library(${SSL_LIB_NAME} SHARED IMPORTED)
set_target_properties(${SSL_LIB_NAME} PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/lib${SSL_LIB_NAME}.so)
set(CRYPTO_LIB_NAME crypto)
add_library(${CRYPTO_LIB_NAME} SHARED IMPORTED)
set_target_properties(${CRYPTO_LIB_NAME} PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/lib${CRYPTO_LIB_NAME}.so)
# import openssl library files END
target_link_libraries(
native-lib
ssl
crypto
${log-lib})
一、介绍AES加密:
AES是一种对称加密的算法,可以自己指定密钥key参与明文的加密,加解密使用同一个密钥key。共有五种加密模式,
- 电码本模式(Electronic Codebook Book (ECB)):这种模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
2.密码分组链接模式(Cipher Block Chaining (CBC)):这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
3.计算器模式(Counter (CTR)):计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是 在计算器不能维持很长的情况下,密钥只能使用一次 。
4.密码反馈模式(Cipher FeedBack (CFB)):
5.输出反馈模式(Output FeedBack (OFB))
由于第4和第5种很复杂,用示意图来展示
4.密码反馈模式(Cipher FeedBack (CFB)):
5、输出反馈模式(Output FeedBack (OFB))
1.1下面用一个图片来整体说明下AES对称加密的流程:
1.2分组密码的填充
这种是PKCS#5填充方式,如果明文刚好是16字节不需要填充的时候,就再加16字节的16,后边看代码可以一目了然。这里我是使用cbc的加密模式,我这里是使用c++来实现的,Java已经基础了api直接调用即可,c++代码也可以写成jni方式去调用,跨平台是一个优势,c++代码的性能优越也是一个优势,下面看代码实现:
先看下使用到的头文件:
#include <openssl/obj_mac.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/crypto.h>
#include <openssl/buffer.h>
#include <openssl/bio.h>
#include <openssl/opensslconf.h>
#include <openssl/bn.h>
#include <sys/time.h>
#include <ctime>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <utils/Log.h>
#include <cutils/log.h>
//测试入口写在了main函数中
int main(int argc, char** argv)
{
//记录开始加密时间,用来评估加密耗时
struct timeval tv;
gettimeofday(&tv, NULL);
int64_t start = tv.tv_sec * 1000 + tv.tv_usec / 1000;
//手动写一串明文用来加密,读者可以将这段明文替换成字节需要加密的明文字节
std::string data = "abcdefghijklmnopqrstovwxyz";
int16_t dateLen = data.length();
/**一般AES是用128bit作为加密块长度,也就是16字节,因此密钥长度一般也用16个字节的长度去做加密*/
int AES_block_num = dateLen / AES_BLOCK_SIZE + 1;
int AES_encrypt_data_len = AES_block_num * AES_BLOCK_SIZE;
unsigned char *encryptBuffer = (unsigned char *)malloc(AES_encrypt_data_len);
unsigned char *ciphertext= (unsigned char *)malloc(AES_encrypt_data_len);
if( encryptBuffer == NULL || out == NULL)
{
ALOGE("Error,malloc fail:encryptBuffer or out");
return;
}
memset(encryptBuffer,0,AES_encrypt_data_len);
memset(ciphertext,0,AES_encrypt_data_len);
/**填充的原则是:
1、如果源字节长度不是16的整数倍,需要补满16个字节的整数倍,在不够的地方补(16-len)个16-len(值),
例如:原始字节是0x10 11 01 02 05 04,总共6个字节,就需要补16-6个字节,每个字节填充16-6 = 10(十六进制是0x0a)
填充后就变成:0x10 11 01 02 05 04 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a
2、如果源字节长度正好是16的整数倍,则需要再补16个字节的十进制的16*/
int fillDataAndNum = 0;
if( dateLen % AES_BLOCK_SIZE >0 )
{
fillDataAndNum = AES_encrypt_data_len - dateLen;
}
else
{
fillDataAndNum = AES_BLOCK_SIZE;
}
memset(encryptBuffer,fillDataAndNum,AES_encrypt_data_len);
memcpy(encryptBuffer,date, dateLen);
AES_encryptOrEncrypt(encryptBuffer,AES_encrypt_data_len,TRUE,ciphertext);
if(ciphertext)
{
//这里可以把ciphertext中的值打印出来,加密后的密文就在ciphertext里
}
else
{
ALOGE("[AES_encrypt]:faild!\n");
break;
}
struct timeval tv1;
gettimeofday(&tv1, NULL);
int64_t end = tv1.tv_sec * 1000 + tv1.tv_usec / 1000;
ALOGE("11111ddddd:AES_encryptDataTime: %d",end - start);
ALOGE("11111aaa:encryptData len = %d",AES_encrypt_data_len );
//decrypt
//解密的时候直接把密文buffer传过去就行,无需再16字节对齐了
unsigned char *out1 = (unsigned char *)malloc(AES_decrypt_data_len);
if( out1 == NULL)
{
ALOGE("Error,malloc fail:decryptBuffer or out");
return;
}
memset(out1,0,AES_decrypt_data_len);
AES_encryptOrEncrypt(ciphertext,AES_decrypt_data_len,FALSE,out1);
ALOGE("11111bbb:decryptData");
if(out1)
{
//这里可以把out1中的值打印出来,解密后的明文就在out1里
}
else
{
ALOGE("[AES_encrypt]:faild!\n");
break;
}
return 0;
}
void GBDataHandle::AES_encryptOrEncrypt(uint8_t *data,uint16_t len,bool isEncrypt,unsigned char* out)
{
AES_KEY aes;
char ivValue[] = "0000000000000000";
unsigned char key[17] = {0};
memset(key, '0', 16);//赋值默认的加密key,只用于测试
if(isEncrypt)
{
//设置加密密钥,16字节
int keyLen = strlen((char*)(key)) * 8;
ALOGD(" key = %s, keyLen = %d", key, keyLen);
if (AES_set_encrypt_key((unsigned char*)key, keyLen, &aes) < 0)
{
ALOGD("Unable to set encryption key in AES");
return;
}
}
else
{
//设置解密密钥,16字节
int keyLen = strlen((char*)(key)) * 8;
ALOGD(" key = %s, keyLen = %d", key, keyLen);
if (AES_set_decrypt_key((unsigned char*)key, keyLen, &aes) < 0)
{
ALOGD("Unable to set encryption key in AES");
return;
}
}
//std::string data_bak = (char*)pcInput;
AES_cbc_encrypt((const unsigned char*)pcInput, (unsigned char*)out, nLen,
&aes, (unsigned char*)ivValue, isEncrypt ? AES_ENCRYPT : AES_DECRYPT);
}
这样使用,加解密即可完成,因为是demo使用时可以注意封装与优化,总体思路是没错的