JNI报错:返回字符串数组时使用无效jobject问题排查
JNI调用OpenSSL生成RSA密钥对后SetObjectArrayElement崩溃问题解决
我来帮你定位问题并解决这个崩溃,同时也给你一些关于密钥存储的替代建议。
问题根源
你代码里的核心错误是直接把BIO_get_mem_data返回的C字符串指针赋值给jstring类型变量,这完全不符合JNI的对象模型:
jstring是JNI层的对象引用,不是普通的C字符指针BIO_get_mem_data返回的是指向BIO内存缓冲区的char*,你直接把这个指针赋值给jstring变量,相当于把一个普通内存地址当成了JNI对象引用,JNI检测到这个无效的jobject就会触发崩溃。
之前你用env->NewStringUTF能正常工作,正是因为这个函数会正确创建一个合法的jstring对象,而不是直接把指针强转。
修正后的代码
下面是修复后的关键代码片段,我标注了核心修改点:
extern "C" JNIEXPORT jobjectArray JNICALL Java_eu_joober_ui_entry_SplashFragment_generateRSAKeyPair(JNIEnv *env, jobject thiz) { int ret = 0; RSA *r = nullptr; BIGNUM *bne = nullptr; BIO *bp_public = nullptr, *bp_private = nullptr; int bits = 2048; unsigned long e = RSA_F4; // 1. 替换jstring为char*,用来存储BIO的内存数据 char* public_key_buf = nullptr; char* private_key_buf = nullptr; jstring public_key_text; jstring private_key_text; jobjectArray returnPair = env->NewObjectArray(2, env->FindClass("java/lang/String"),nullptr); // 生成RSA密钥的逻辑保持不变 bne = BN_new(); ret = BN_set_word(bne,e); if(ret != 1){ goto free_all; } r = RSA_new(); ret = RSA_generate_key_ex(r, bits, bne, nullptr); if(ret != 1){ goto free_all; } // 2. 获取公钥的修正:用char*接收数据,再转为合法jstring bp_public = BIO_new(BIO_s_mem()); ret = PEM_write_bio_RSAPublicKey(bp_public, r); if(ret != 1){ goto free_all; } BIO_get_mem_data(bp_public, &public_key_buf); // 关键:通过JNI函数创建合法的jstring对象 public_key_text = env->NewStringUTF(public_key_buf); // 3. 获取私钥的修正:遵循同样逻辑 bp_private = BIO_new(BIO_s_mem()); ret = PEM_write_bio_RSAPrivateKey(bp_private, r, nullptr, nullptr, 0, nullptr, nullptr); if(ret != 1){ goto free_all; } BIO_get_mem_data(bp_private, &private_key_buf); private_key_text = env->NewStringUTF(private_key_buf); // 日志打印可以继续使用char*变量 __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Public key is: \n%s",public_key_buf); __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Private key is: \n%s",private_key_buf); free_all: BIO_free_all(bp_public); BIO_free_all(bp_private); RSA_free(r); BN_free(bne); // 返回数组的逻辑保持不变 if (ret == 1) { env->SetObjectArrayElement(returnPair, 0, public_key_text); env->SetObjectArrayElement(returnPair, 1, private_key_text); return returnPair; } else { return nullptr; } }
额外建议:关于密钥存储的替代方案
你提到Android Keystore无法导出私钥的问题,这其实是Keystore的设计初衷(保证私钥永远不离开安全硬件),如果一定要备份私钥,推荐以下更安全的方案:
- 用用户密码派生密钥加密私钥:
- 用
KeyPairGenerator生成RSA密钥对并存入Keystore(创建时需指定KeyProperties.PURPOSE_EXPORT以允许导出,Android 10+有更严格限制) - 导出公钥用于加密数据
- 导出私钥明文后,用用户密码通过PBKDF2算法派生对称密钥,加密私钥后再存储到云端
- 卸载重装后,用户输入密码派生对称密钥,解密云端的私钥后重新使用
- 用
- 如果必须使用可导出的RSA:
修正后的代码可以正常工作,但务必注意私钥的存储安全:绝对不能明文存储,必须加密后再存到本地或云端,加密方式同样推荐用用户密码派生的对称密钥。
内容的提问来源于stack exchange,提问作者Franbede
相关产品推荐
相关产品推荐

