日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android 在 NDK 层使用 OpenSSL 进行 RSA 加密

發布時間:2025/3/15 Android 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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個改動:

  • 將最低版本支持從Android 21改到了Android 14
  • 修復一個armeabi-v7a無法編譯出來的問題
  • 升級了openssl的版本到openssl-1.1.0e
  • 之后將項目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文件

    12345678APP_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文件

    123456789101112131415161718192021222324252627282930LOCAL_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#include "jni.h"template<typename T, int N>char (&ArraySizeHelper(T (&array)[N]))[N];#define NELEMS(x) (sizeof(ArraySizeHelper(x)))#ifndef CLASSNAME#define CLASSNAME "com/fucknmb/Test"#endifjstring 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函數

    1234567891011package 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;}

    這個函數有一點需要注意的就是這一行

    1std::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;}

    同樣這個函數也有幾個地方需要注意:

    第一點:

    1static std::string result((char *) to, status);

    第二個參數表示密文長度,一般來說,這個值會是128,如果第二個值不傳,會導致加密后的密文經過string的構造函數后,丟失一部分數據,導致數據的不正確

    第二點:

    1rsa_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#include <openssl/bio.h>#include <openssl/buffer.h>#include <openssl/evp.h>#include <openssl/rsa.h>#include <openssl/pem.h>#include <iostream>using std::string;

    需要的函數都有了,實現以下native_rsa函數,簡單組裝一下以上函數即可

    123456789101112131415161718192021222324252627282930jstring 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公鑰和私鑰的生成

    生成私鑰

    1openssl genrsa -out rsa_private_key.pem 1024

    這條命令讓openssl隨機生成了一份私鑰,加密長度是1024位。加密長度是指理論上最大允許”被加密的信息“長度的限制,也就是明文的長度限制。隨著這個參數的增大(比方說2048),允許的明文長度也會增加,但同時也會造成計算復雜度的極速增長。一般推薦的長度就是1024位(128字節,之前的代碼的最大加密長度128就是這么來的)。

    生成公鑰

    1openssl 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編碼:

    1openssl 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層調用公鑰加密函數部分

    12String base64PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDP0tzYxBF5IGfNvuIHzAqvza/ZxfH8aEiPFA4nY/W3js+cG3JUU86Jkc7jUG9XfGdW6SJ38ANs5tyWqYkJyoUErB2PjQQQDmHhbgpBUSeOdwGr/LPtrTrotrNXwpRY9eodkcbcMlbT0gvdnohRSISCjJ2KmFcBMkeO9R2DWe6oIwIDAQAB";String result = com.fucknmb.Test.native_rsa(base64PublicKey,"I am test");

    http://fucknmb.com/2017/04/09/Android%E5%9C%A8NDK%E5%B1%82%E4%BD%BF%E7%94%A8OpenSSL%E8%BF%9B%E8%A1%8CRSA%E5%8A%A0%E5%AF%86/

    總結

    以上是生活随笔為你收集整理的Android 在 NDK 层使用 OpenSSL 进行 RSA 加密的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。