需求
在Android上实现RSA 2048 PKCS#1加密,秘钥不能放在Java层(其实公钥被别人看了也没啥吧?)。
几个思路:
- 所有加密的程序都在native层。
- 秘钥在Native层,加密在Java实现。
- 所有都在Java实现。(不满足公钥不能放在Java层的要求)
RSA加密
RSA是一种非对称加密算法。
通常的使用流程是A生成一对秘钥,公钥和私钥,而这对秘钥有这样的特性:
使用公钥加密的数据,利用私钥进行解密,使用私钥加密的数据,利用公钥进行解密
A把公钥交给B,B使用公钥加密后把数据给回A,A使用私钥进行解密。
2048是RSA密钥长度bit数,数字越大安全性越大。
PKCS#1是公钥加密标准(Public Key Cryptography Standards, PKCS),是秘钥的一种格式。
在这里可以体验一下。
Android在native中使用OpenSSL
项目地址:https://github.com/etet2007/RSAencrypt
在Android中添加native代码的步骤在官网中说得很清楚了。
总的来说有几个Point。
- cpp文件夹下用于解密的cpp文件。Java层调用的native方法与其关联。
//Java
public static native byte[] encodeByRSAPubKey(byte[] src);
//CPP
extern "C" JNIEXPORT jbyteArray JNICALL
Java_lau_stephen_rsaencrypt_EncryptUtils_encodeByRSAPubKey(JNIEnv *env, jclass type,
jbyteArray src_) {
std::string keys = "-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwm/vZlgdvA/s3o+Epq2B\n"
"TF9Pk1goY4+wji1fjkmePasZkt12Rx0A0qXuzBfe8K4Y1uf/sKD1XHeXtvPol5TF\n"
"ZKq6dEQpd3PsweFMFGYfZbA5IdwEQWXFJqJSpru/jXENCanUARVV5Au0fjaMw71x\n"
"dGbHQ7gNdxln9xeoPkyCLBuWor5B3U47NFGEz8ZMELCib0+9bPtzIVuBYA5BsT9A\n"
"WgHZpuRZRgQ2r3a0ehe7gO1H+SKrLStVzUZ7EUW4PBM4IhIrR+BfORHi4PgD+4rZ\n"
"IuMzg99Y20ytaHIm6tw6+dvt3gSY2q2VWITCVE2dtH167R/AR+mJDFhp89Ss1sGE\n"
"wQIDAQAB\n"
"-----END PUBLIC KEY-----";
jbyte *src = env->GetByteArrayElements(src_, NULL);
jsize src_Len = env->GetArrayLength(src_);
int encryptedValueSize = 0, src_flen = 0, cipherText_offset = 0, desText_len = 0, src_offset = 0;
//BIO_new_mem_buf() creates a memory BIO using len bytes of data at buf,
// if len is -1 then the buf is assumed to be nul terminated and its length is determined by strlen.
BIO *keyBio = BIO_new_mem_buf((void *) keys.c_str(), -1);
//The RSA structure consists of several BIGNUM components.
// It can contain public as well as private RSA keys:
RSA *publicKey = PEM_read_bio_RSA_PUBKEY(keyBio, NULL, NULL, NULL);
//释放BIO
BIO_free_all(keyBio);
//RSA_size returns the RSA modulus size in bytes.
// It can be used to determine how much memory must be allocated for an RSA encrypted value.
int flen = RSA_size(publicKey);
//复制src到srcOrigin
unsigned char *srcOrigin = (unsigned char *) malloc(src_Len);
memset(srcOrigin, 0, src_Len);
memcpy(srcOrigin, src, src_Len);
//每次加密后的长度
unsigned char *encryptedValue = (unsigned char *) malloc(flen);
desText_len = flen * (src_Len / (flen - 11) + 1);
unsigned char *desText = (unsigned char *) malloc(desText_len);
memset(desText, 0, desText_len);
//对数据进行公钥加密运算
//对于1024bit,2048应该为256
//RSA_PKCS1_PADDING 最大加密长度 为 128 -11
//RSA_NO_PADDING 最大加密长度为 128
//rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE;
for (int i = 0; i <= src_Len / (flen - 11); i++) {
src_flen = (i == src_Len / (flen - 11)) ? src_Len % (flen - 11) : flen - 11;
if (src_flen == 0) {
break;
}
//重置encryptedValue
memset(encryptedValue, 0, flen);
//encrypt srcOrigin + src_offset到encryptedValue
//returns the size of the encrypted data
encryptedValueSize = RSA_public_encrypt(src_flen, srcOrigin + src_offset, encryptedValue,
publicKey, RSA_PKCS1_PADDING);
if (encryptedValueSize == -1) {
RSA_free(publicKey);
CRYPTO_cleanup_all_ex_data();
env->ReleaseByteArrayElements(src_, src, 0);
free(srcOrigin);
free(encryptedValue);
free(desText);
return NULL;
}
//复制encryptedValue到desText + cipherText_offset
memcpy(desText + cipherText_offset, encryptedValue, encryptedValueSize);
cipherText_offset += encryptedValueSize;
src_offset += src_flen;
}
RSA_free(publicKey);
//清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
CRYPTO_cleanup_all_ex_data();
//从jni释放数据指针
env->ReleaseByteArrayElements(src_, src, 0);
jbyteArray cipher = env->NewByteArray(cipherText_offset);
//在堆中分配ByteArray数组对象成功,将拷贝数据到数组中
env->SetByteArrayRegion(cipher, 0, cipherText_offset, (jbyte *) desText);
//释放内存
free(srcOrigin);
free(encryptedValue);
free(desText);
return cipher;
}
- 放置OpenSSL的库于项目中。这里的库是.a的静态库或者.so的动态库,可以自行编译或下载别人的来用。
动态库,静态库,编译。
在Gradle中设置jniLibs的地址,把带有so库目录地址告诉Gradle,在打包的时候才会把so库打进APK。
sourceSets.main {
jniLibs.srcDirs = ['libs/lib']
}
- 提供CMake文件
Gradle提供CMakeList.txt文件的路径,我这里是放在app下。记住路径都是相对路径来的。
externalNativeBuild {
cmake {
path file('CMakeLists.txt')
}
}
CMakeList.txt文件,告诉系统用encrypt.cpp编译动态库,其中引用到OpenSSL的crypto、ssl动态库。
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
include_directories(libs/include)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
encrypt
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/encrypt.cpp)
add_library(crypto SHARED IMPORTED)
add_library(ssl SHARED IMPORTED)
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libcrypto.so)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libssl.so)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries( # Specifies the target library.
encrypt
crypto
ssl
# Links the target library to the log library
# included in the NDK.
${log-lib} )
加密在Java实现
Android 密钥库系统