Android 在 NDK 层使用 OpenSSL 进行 RSA 加密
前言
需求:需要在NDK層對一個Java層的字符串進行RSA加密,然后對加密的結果進行Base64返回到Java層
方案:選擇使用OpenSSL來實現。
編譯libssl.a和libcrypto.a靜態庫
在github上找到了一個項目,可以直接將OpenSSL編譯成Android可以使用的,項目地址為
- openssl_for_ios_and_android
但是這個項目有點小問題,部分編譯腳本需要做點改動,改動后的項目見
- openssl_for_ios_and_android
主要做了3個改動:
之后將項目clone下來,進入到tools目錄,執行build-openssl4android.sh編譯腳本
| 12 | ./build-openssl4android.sh android-armeabi armeabi-v7a./build-openssl4android.sh android armeabi |
這里只編譯了armeabi-va7和armeabi架構CPU的so,如果有需要,請自行更改命令參數編譯X86等架構的so。
經過很長時間的編譯。。。大概要10來分鐘吧。。。在根目錄下的output會產生一個android目錄,里面有openssl-armeabi和openssl-armeabi-v7a兩個文件夾,包含了openssl的頭文件以及編譯好的.a靜態庫
實現JNI函數
編譯好后.a靜態庫,就可以創建jni項目了
進入jni項目根目錄,創建Application.mk文件
| 12345678 | APP_ABI := armeabi armeabi-v7aAPP_PLATFORM := android-14APP_OPTIM := releaseAPP_STL := c++_staticAPP_THIN_ARCHIVE := trueAPP_CPPFLAGS := -fpic -fexceptions -frttiAPP_GNUSTL_FORCE_CPP_FEATURES := pic exceptions rtti |
進入jni項目根目錄,創建Android.mk文件
| 123456789101112131415161718192021222324252627282930 | LOCAL_PATH := $(call my-dir)#引用libcrypto.ainclude $(CLEAR_VARS)LOCAL_MODULE := libcryptoLOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libcrypto.ainclude $(PREBUILT_STATIC_LIBRARY)#引用libssl.ainclude $(CLEAR_VARS)LOCAL_MODULE := libsslLOCAL_SRC_FILES := $(LOCAL_PATH)/openssl/$(TARGET_ARCH_ABI)/lib/libssl.ainclude $(PREBUILT_STATIC_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := testLOCAL_SRC_FILES := \native.cpp \LOCAL_C_INCLUDES :=$(LOCAL_PATH)/openssl/openssl-$(TARGET_ARCH_ABI)/includeTARGET_PLATFORM := android-14#靜態庫依賴LOCAL_STATIC_LIBRARIES := libssl libcryptoLOCAL_LDLIBS += -latomic -lz -lloginclude $(BUILD_SHARED_LIBRARY) |
進入jni項目根目錄,拷貝編譯好的openssl文件
接著將第一步編譯好的靜態庫文件進行拷貝,將output目錄下android整個目錄進行拷貝,拷貝到jni項目根目錄下,拷貝完成后將android目錄重命名為openssl
進入jni項目根目錄,創建native.cpp,搭建基礎的結構
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 | template<typename T, int N>char (&ArraySizeHelper(T (&array)[N]))[N];jstring native_rsa(JNIEnv *env, jobject thiz, jstring base64PublicKey, jstring content) { return NULL;}static const JNINativeMethod sMethods[] = {{ const_cast<char *>("native_rsa"), const_cast<char *>("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), reinterpret_cast<void *>(native_rsa)}};int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *methods, const int numMethods) {jclass clazz = env->FindClass(className); if (!clazz) { return JNI_FALSE;} if (env->RegisterNatives(clazz, methods, numMethods) != 0) {env->DeleteLocalRef(clazz); return JNI_FALSE;}env->DeleteLocalRef(clazz); return JNI_TRUE;}jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1;}registerNativeMethods(env, CLASSNAME, sMethods, NELEMS(sMethods)); return JNI_VERSION_1_6;} |
聲明java層函數
在Java層創建com/fucknmb/Test類,聲明一個native函數
| 1234567891011 | package com.fucknmb;import java.util.List;public class Test { public static native final String native_rsa(String base64PublicKey, String content); static {System.loadLibrary("test");}} |
實現native_rsa函數
native_rsa函數有兩個參數,一個是base64之后的公鑰(不含頭部和尾部,以及沒換行),第二個是待加密的明文內容,該函數的返回值是加密后的密文進行base64。
對于第一個參數,我們需要將其轉為公鑰文件字符串,追加頭部和尾部,其實現如下:
| 12345678910111213141516171819202122 | /** * 根據公鑰base64字符串(沒換行)生成公鑰文本內容 * @param base64EncodedKey * @return */std::string generatePublicKey(std::string base64EncodedKey) { std::string publicKey = base64EncodedKey; size_t base64Length = 64;//每64個字符一行 size_t publicKeyLength = base64EncodedKey.size(); for (size_t i = base64Length; i < publicKeyLength; i += base64Length) { //每base64Length個字符,增加一個換行 if (base64EncodedKey[i] != '\n') {publicKey.insert(i, "\n");}i++;} //最前面追加公鑰begin字符串publicKey.insert(0, "-----BEGIN PUBLIC KEY-----\n"); //最前面追加公鑰end字符串publicKey.append("\n-----END PUBLIC KEY-----"); return publicKey;} |
openssl rsa加密后,我們需要對密文進行Base64,openssl同樣提供了Base64算法,實現如下
| 1234567891011121314151617181920212223 | /** * base64 encode * @param decoded_bytes * @return */std::string base64_encode(const std::string &decoded_bytes) { BIO *bio, *b64; BUF_MEM *bufferPtr; b64 = BIO_new(BIO_f_base64()); //不換行 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); //encode BIO_write(bio, decoded_bytes.c_str(), (int) decoded_bytes.length()); BIO_flush(bio); BIO_get_mem_ptr(bio, &bufferPtr); //這里的第二個參數很重要,必須賦值 std::string result(bufferPtr->data, bufferPtr->length); BIO_free_all(bio); return result;} |
這個函數有一點需要注意的就是這一行
| 1 | std::string result(bufferPtr->data, bufferPtr->length); |
第二個參數表示長度,不能少,否則base64后的字符串長度會出現異常,導致decode的時候末尾會出現一大堆的亂碼,而網上大多數的代碼,是缺失這一個參數的。
接下來就是rsa的實現了
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 | /** * 使用公鑰對明文加密 * @param publicKey * @param from * @return */std::string encryptRSA(const std::string &publicKey, const std::string &from) {BIO *bio = NULL;RSA *rsa_public_key = NULL; //從字符串讀取RSA公鑰串 if ((bio = BIO_new_mem_buf((void *) publicKey.c_str(), -1)) == NULL) {std::cout << "BIO_new_mem_buf failed!" << std::endl; return "";} //讀取公鑰rsa_public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); //異常處理 if (rsa_public_key == NULL) { //資源釋放BIO_free_all(bio); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return "";} //rsa模的位數 int rsa_size = RSA_size(rsa_public_key); //RSA_PKCS1_PADDING 最大加密長度 為 128 -11 //RSA_NO_PADDING 最大加密長度為 128 //rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE; //動態分配內存,用于存儲加密后的密文unsigned char *to = (unsigned char *) malloc(rsa_size + 1); //填充0memset(to, 0, rsa_size + 1); //明文長度 int flen = from.length(); //加密,返回值為加密后的密文長度,-1表示失敗 int status = RSA_public_encrypt(flen, (const unsigned char *) from.c_str(), to, rsa_public_key,RSA_PKCS1_PADDING); //異常處理 if (status < 0) { //資源釋放free(to);BIO_free_all(bio);RSA_free(rsa_public_key); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return "";} //賦值密文 static std::string result((char *) to, status); //資源釋放free(to);BIO_free_all(bio);RSA_free(rsa_public_key); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return result;} |
同樣這個函數也有幾個地方需要注意:
第一點:
| 1 | static std::string result((char *) to, status); |
第二個參數表示密文長度,一般來說,這個值會是128,如果第二個值不傳,會導致加密后的密文經過string的構造函數后,丟失一部分數據,導致數據的不正確
第二點:
| 1 | rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE; |
對于RSA_PKCS1_PADDING_SIZE,最大加密長度為需要減去11
2017.7.17修改,第二點經過試驗,廢棄!
第三點:
| 123456 | //明文長度int flen = from.length();//加密,返回值為加密后的密文長度,-1表示失敗int status = RSA_public_encrypt(flen, (const unsigned char *) from.c_str(), to, rsa_public_key,RSA_PKCS1_PADDING); |
RSA_public_encrypt函數的第一個參數傳的是明文長度,而不是最大加密長度rsa_size,網上的所有代碼這個參數都是傳錯的,傳了rsa_size,而實際上這個參數的參數名是flen,表示from字符串的length。如果這個參數傳了最大加密長度,將直接導致java層無法正確解密JNI層加密后的數據。
最后不要忘記加頭文件的引用
| 1234567 | using std::string; |
需要的函數都有了,實現以下native_rsa函數,簡單組裝一下以上函數即可
| 123456789101112131415161718192021222324252627282930 | jstring native_rsa(JNIEnv *env, jobject thiz, jstring base64PublicKey, jstring content) { //jstring 轉 char* char *base64PublicKeyChars = (char *) env->GetStringUTFChars(base64PublicKey, NULL); //char* 轉 string string base64PublicKeyString = string(base64PublicKeyChars); //生成公鑰字符串 string generatedPublicKey = generatePublicKey(base64PublicKeyString); //釋放env->ReleaseStringUTFChars(base64PublicKey, base64PublicKeyChars); //jstring 轉 char* char *contentChars = (char *) env->GetStringUTFChars(content, NULL); //char* 轉 string string contentString = string(contentChars); //釋放env->ReleaseStringUTFChars(content, contentChars); //調用RSA加密函數加密 string rsaResult = encryptRSA(generatedPublicKey, contentString); if (rsaResult.empty()) { return NULL;} //將密文進行base64 string base64RSA = base64_encode(rsaResult); if (base64RSA.empty()) { return NULL;} //string -> char* -> jstring 返回jstring result = env->NewStringUTF(base64RSA.c_str()); return result;} |
私鑰解密
如果你還需要用的私鑰解密部分,可以繼續實現base64的decode函數,以及rsa的私鑰串生成函數,rsa的解密函數
base64 decode函數的實現如下:
| 12345678910111213141516171819202122232425262728 | /** * base64 decode * @param encoded_bytes * @return */std::string base64_decode(const std::string &encoded_bytes) {BIO *bioMem, *b64;bioMem = BIO_new_mem_buf((void *) encoded_bytes.c_str(), -1);b64 = BIO_new(BIO_f_base64());BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);bioMem = BIO_push(b64, bioMem); //獲得解碼長度 size_t buffer_length = BIO_get_mem_data(bioMem, NULL); char *decode = (char *) malloc(buffer_length + 1); //填充0 memset(decode, 0, buffer_length + 1);BIO_read(bioMem, (void *) decode, (int) buffer_length); static std::string decoded_bytes(decode);BIO_free_all(bioMem); return decoded_bytes;} |
rsa的私鑰串生成函數的試下如下:
| 12345678910111213141516171819202122 | /** * 根據私鑰base64字符串(沒換行)生成私鑰文本內容 * @param base64EncodedKey * @return */std::string generatePrivateKey(std::string base64EncodedKey) { std::string privateKey = base64EncodedKey; size_t base64Length = 64;//每64個字符一行 size_t privateKeyLength = base64EncodedKey.size(); for (size_t i = base64Length; i < privateKeyLength; i += base64Length) { //每base64Length個字符,增加一個換行 if (base64EncodedKey[i] != '\n') {privateKey.insert(i, "\n");}i++;} //最前面追加私鑰begin字符串privateKey.insert(0, "-----BEGIN PRIVATE KEY-----\n"); //最后面追加私鑰end字符串privateKey.append("\n-----END PRIVATE KEY-----"); return privateKey;} |
私鑰解密函數的實現如下:
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 | /** * 使用私鑰對密文解密 * @param privetaKey * @param from * @return */std::string decryptRSA(const std::string &privetaKey, const std::string &from) {BIO *bio = NULL;RSA *rsa_private_key = NULL; //從字符串讀取RSA公鑰串 if ((bio = BIO_new_mem_buf((void *) privetaKey.c_str(), -1)) == NULL) {std::cout << "BIO_new_mem_buf failed!" << std::endl; return "";} //讀取私鑰rsa_private_key = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL); //異常處理 if (rsa_private_key == NULL) { //資源釋放BIO_free_all(bio); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return "";} //rsa模的位數 int rsa_size = RSA_size(rsa_private_key); //動態分配內存,用于存儲解密后的明文unsigned char *to = (unsigned char *) malloc(rsa_size + 1); //填充0memset(to, 0, rsa_size + 1); //密文長度 int flen = from.length(); // RSA_NO_PADDING // RSA_PKCS1_PADDING //解密,返回值為解密后的名文長度,-1表示失敗 int status = RSA_private_decrypt(flen, (const unsigned char *) from.c_str(), to, rsa_private_key,RSA_PKCS1_PADDING); //異常處理率 if (status < 0) { //釋放資源free(to);BIO_free_all(bio);RSA_free(rsa_private_key); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return "";} //賦值明文,是否需要指定to的長度? static std::string result((char *) to); //釋放資源free(to);BIO_free_all(bio);RSA_free(rsa_private_key); //清除管理CRYPTO_EX_DATA的全局hash表中的數據,避免內存泄漏CRYPTO_cleanup_all_ex_data(); return result;} |
如果你要解密公鑰加密后的密文,只需要這樣調用即可返回明文
| 1234567891011121314 | //公鑰串和私鑰串string generatedPublicKey = generatePublicKey(base64PublicKey);string generatedPrivetKey = generatePrivateKey(base64PrivateKey); string content("just a test");//加密string result = encryptRSA(generatedPublicKey, content);//encodestring base64RSA = base64_encode(result);//decodestring decodeBase64RSA = base64_decode(base64RSA);//解密string origin = decryptRSA(generatedPrivetKey, decodeBase64RSA); |
最后注意一下base64PublicKey和base64PrivateKey,這兩個字符串是不包含換行的,就是私鑰和公鑰的encoded之后的字節數組base64后的值,因此需要自己調用generatePublicKey和generatePrivateKey追加頭和尾。
RSA公鑰和私鑰的生成
生成私鑰
| 1 | openssl genrsa -out rsa_private_key.pem 1024 |
這條命令讓openssl隨機生成了一份私鑰,加密長度是1024位。加密長度是指理論上最大允許”被加密的信息“長度的限制,也就是明文的長度限制。隨著這個參數的增大(比方說2048),允許的明文長度也會增加,但同時也會造成計算復雜度的極速增長。一般推薦的長度就是1024位(128字節,之前的代碼的最大加密長度128就是這么來的)。
生成公鑰
| 1 | openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout |
密鑰文件最終將數據通過Base64編碼進行存儲。可以看到上述生成的密鑰文件內容每一行的長度都很規律。這是由于RFC2045中規定:The encoded output stream must be represented in lines of no more than 76 characters each。也就是說Base64編碼的數據每行最多不超過76字符,對于超長數據需要按行分割。
上面的generatePublicKey和generatePrivateKey函數我們是按64位一行進行分割的,如果你有需要,可以將值修改為76。
第一步生成私鑰文件不能直接使用,需要進行PKCS#8編碼:
| 1 | openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt |
第二步和第三步生成的公鑰和私鑰就可以用了,這里有個問題需要注意,如果你的公鑰和私鑰是類似下面這種格式的
| 12345678 | -----BEGIN PUBLIC KEY-----....-----END PUBLIC KEY----------BEGIN PRIVATE KEY-----....-----END PRIVATE KEY----- |
那么,你無需調用generatePublicKey或者generatePrivateKey函數,此時已經是需要的公鑰串和私鑰串,但是如果你的公鑰和私鑰沒有頭部和尾部,并且不是換行的,就需要調用一下進行轉換,因為我這邊Java層傳入的是后者,所以需要調用generatePublicKey或者generatePrivateKey進行轉換。
Java層調用公鑰加密函數部分
| 12 | String base64PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP0tzYxBF5IGfNvuIHzAqvza/ZxfH8aEiPFA4nY/W3js+cG3JUU86Jkc7jUG9XfGdW6SJ38ANs5tyWqYkJyoUErB2PjQQQDmHhbgpBUSeOdwGr/LPtrTrotrNXwpRY9eodkcbcMlbT0gvdnohRSISCjJ2KmFcBMkeO9R2DWe6oIwIDAQAB";String result = com.fucknmb.Test.native_rsa(base64PublicKey,"I am test"); |
總結
以上是生活随笔為你收集整理的Android 在 NDK 层使用 OpenSSL 进行 RSA 加密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android免Root环境下Hook框
- 下一篇: Tensorflow Lite 编译