You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的设计初衷(保证私钥永远不离开安全硬件),如果一定要备份私钥,推荐以下更安全的方案:

  • 用用户密码派生密钥加密私钥:
    1. 用KeyPairGenerator生成RSA密钥对并存入Keystore(创建时需指定KeyProperties.PURPOSE_EXPORT以允许导出,Android 10+有更严格限制)
    2. 导出公钥用于加密数据
    3. 导出私钥明文后,用用户密码通过PBKDF2算法派生对称密钥,加密私钥后再存储到云端
    4. 卸载重装后,用户输入密码派生对称密钥,解密云端的私钥后重新使用
  • 如果必须使用可导出的RSA:
    修正后的代码可以正常工作,但务必注意私钥的存储安全:绝对不能明文存储,必须加密后再存到本地或云端,加密方式同样推荐用用户密码派生的对称密钥。

内容的提问来源于stack exchange,提问作者Franbede

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:34:51