JNI 中 OpenSSL RSA 解密结果异常的排查与修复

小涛小哥_4300

小涛小哥_4300

2026-09-26

957人浏览

原创

JNI 中 OpenSSL RSA 解密结果异常的排查与修复

本文详解 JNI 层使用 OpenSSL 进行 RSA 加解密时因参数误传导致解密输出乱码的根本原因,重点指出 RSA_public_encrypt 中错误传递 jbyteArray 引用而非实际数据指针的问题,并提供完整、安全、可复用的 C++ 实现方案。

本文详解 jni 层使用 openssl 进行 rsa 加解密时因参数误传导致解密输出乱码的根本原因,重点指出 `rsa_public_encrypt` 中错误传递 `jbytearray` 引用而非实际数据指针的问题,并提供完整、安全、可复用的 c++ 实现方案。

在 Android NDK 开发中,通过 JNI 调用 OpenSSL 实现 RSA 加解密是常见需求,但极易因内存管理或 API 参数误用引发静默错误——如加密成功、解密返回正确长度却内容错乱。本案例的核心问题正源于此:RSA_public_encrypt 被错误地传入了 jbyteArray 对象引用(即 plain_text),而非其解包后的原始字节数组指针(即 plainText)。

该错误虽未直接导致 JVM 崩溃(因 jbyteArray 在内存中表现为一个对象句柄,强制转为 unsigned char* 后可能偶然指向某段可读内存),但实际加密的是 Java 对象头或随机内存数据,致使密文无效;而后续 RSA_private_decrypt 使用标准私钥对“伪密文”解密,虽能完成运算并返回预期长度(256 字节,对应 2048 位 RSA),但输出自然是不可预测的乱码。

✅ 正确实现要点

1. 严格区分 JNI 输入参数与本地数据指针

  • ❌ 错误写法(原文问题代码):
    RSA_public_encrypt(plainText_len, (unsigned char*)plain_text, ...); // plain_text 是 jbyteArray!
  • ✅ 正确写法:
    RSA_public_encrypt(plainText_len, plainText, chEncryptedData, rsa, RSA_PKCS1_OAEP_PADDING);

2. 安全释放 JNI 引用(关键!)

原文代码中未调用 env->ReleaseByteArrayElements(),造成内存泄漏与潜在数据不一致。必须配对使用 Get/Release:

SSL
SSL

配置HTTPS,管理TLS证书,并调试安全连接问题。

下载
jbyte* temp = env->GetByteArrayElements(plain_text, &isCopy);
if (temp == nullptr) {
    LOGE("Failed to get plain text bytes");
    return nullptr;
}
unsigned char* plainText = reinterpret_cast<unsigned char>(temp);

// ... 执行加密 ...

env->ReleaseByteArrayElements(plain_text, temp, JNI_ABORT); // JNI_ABORT 表示不回写(只读场景)</unsigned>

3. 私钥解密需匹配加密填充方式与密文长度

  • 加密端使用 RSA_PKCS1_OAEP_PADDING,解密端必须一致;
  • RSA_private_decrypt 的第一个参数应为密文实际长度(即 encText_len),而非硬编码 256:
    int result = RSA_private_decrypt(encText_len, encText, chDecryptedData, rsa, RSA_PKCS1_OAEP_PADDING);

    ? 注:2048 位 RSA 使用 OAEP 填充时,最大明文长度约为 190 字节,密文固定为 256 字节。但 encText_len 必须动态传入,否则会读取越界或截断。

4. 完整修正后的 JNI 方法示例(C++)

extern "C" {

JNIEXPORT jbyteArray JNICALL
Java_com_example_myapplication_MainActivity_publicencryptRsa(
        JNIEnv *env, jobject thiz, jstring key, jbyteArray plain_text) {

    // 1. 提取公钥字符串
    const char *key_str = env->GetStringUTFChars(key, nullptr);
    if (!key_str) return nullptr;
    std::string sKey(key_str);
    env->ReleaseStringUTFChars(key, key_str);

    // 2. 提取明文字节数组
    jboolean isCopy;
    jbyte *plain_bytes = env->GetByteArrayElements(plain_text, &isCopy);
    if (!plain_bytes) return nullptr;
    int plain_len = env->GetArrayLength(plain_text);
    unsigned char *plain_data = reinterpret_cast<unsigned char>(plain_bytes);

    // 3. 加载公钥
    BIO *bio = BIO_new_mem_buf(sKey.c_str(), static_cast<int>(sKey.length()));
    if (!bio) {
        LOGE("BIO_new_mem_buf failed for public key");
        env->ReleaseByteArrayElements(plain_text, plain_bytes, JNI_ABORT);
        return nullptr;
    }
    RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr);
    BIO_free(bio);
    if (!rsa) {
        LOGE("PEM_read_bio_RSA_PUBKEY failed");
        env->ReleaseByteArrayElements(plain_text, plain_bytes, JNI_ABORT);
        return nullptr;
    }

    // 4. 分配输出缓冲区(2048-bit → 256 bytes)
    std::vector<unsigned char> encrypted(256);
    int len = RSA_public_encrypt(plain_len, plain_data, encrypted.data(), rsa, RSA_PKCS1_OAEP_PADDING);
    RSA_free(rsa);
    env->ReleaseByteArrayElements(plain_text, plain_bytes, JNI_ABORT);

    if (len NewByteArray(len);
    env->SetByteArrayRegion(result, 0, len, reinterpret_cast<jbyte>(encrypted.data()));
    return result;
}

JNIEXPORT jbyteArray JNICALL
Java_com_example_myapplication_MainActivity_privatedecryptRsa(
        JNIEnv *env, jobject thiz, jstring private_key, jbyteArray enc_text) {

    const char *pkey_str = env->GetStringUTFChars(private_key, nullptr);
    if (!pkey_str) return nullptr;
    std::string sKey(pkey_str);
    env->ReleaseStringUTFChars(private_key, pkey_str);

    jboolean isCopy;
    jbyte *enc_bytes = env->GetByteArrayElements(enc_text, &isCopy);
    if (!enc_bytes) return nullptr;
    int enc_len = env->GetArrayLength(enc_text);
    unsigned char *enc_data = reinterpret_cast<unsigned char>(enc_bytes);

    BIO *bio = BIO_new_mem_buf(sKey.c_str(), static_cast<int>(sKey.length()));
    if (!bio) {
        LOGE("BIO_new_mem_buf failed for private key");
        env->ReleaseByteArrayElements(enc_text, enc_bytes, JNI_ABORT);
        return nullptr;
    }
    RSA *rsa = PEM_read_bio_RSAPrivateKey(bio, nullptr, nullptr, nullptr);
    BIO_free(bio);
    if (!rsa) {
        LOGE("PEM_read_bio_RSAPrivateKey failed");
        env->ReleaseByteArrayElements(enc_text, enc_bytes, JNI_ABORT);
        return nullptr;
    }

    std::vector<unsigned char> decrypted(2048); // 足够容纳解密后明文
    int len = RSA_private_decrypt(enc_len, enc_data, decrypted.data(), rsa, RSA_PKCS1_OAEP_PADDING);
    RSA_free(rsa);
    env->ReleaseByteArrayElements(enc_text, enc_bytes, JNI_ABORT);

    if (len NewByteArray(len);
    env->SetByteArrayRegion(result, 0, len, reinterpret_cast<jbyte>(decrypted.data()));
    return result;
}

} // extern "C"</jbyte></unsigned></int></unsigned></jbyte></unsigned></int></unsigned>

⚠️ 注意事项与最佳实践

  • 始终校验 OpenSSL 错误栈:每次调用失败后立即用 ERR_get_error() + ERR_error_string() 获取可读错误信息;
  • *避免全局 `RSA缓存**:密钥解析开销低,且多线程下共享RSA*` 需加锁,建议每次操作新建/销毁;
  • 密钥格式确认:公钥必须为 -----BEGIN PUBLIC KEY-----(PKIX 格式),而非 -----BEGIN RSA PUBLIC KEY-----;私钥应为 PKCS#1 或 PKCS#8(本文使用 PKCS#1);
  • 字符编码一致性:Java 层 getBytes(StandardCharsets.UTF_8) 与 C++ 解密后 std::string(decrypted.data(), len) 应保持 UTF-8 解释,避免 char 符号扩展问题;
  • OpenSSL 初始化:确保在 JNI JNI_OnLoad 中调用 OPENSSL_init_crypto(OPENSSL_INIT_ATFORK, nullptr)(OpenSSL 1.1.1+)或 SSL_library_init()(旧版)。

通过修正参数传递、补全资源释放、动态传入密文长度,即可彻底解决“解密长度正确但内容错误”的典型陷阱,保障 JNI 层 RSA 加解密的可靠性与安全性。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

ssl

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

9097

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

6262

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

5612

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1004

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

848

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1196

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2389

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19731

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1095

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 0.1万人学习