
本文详解 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:
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 加解密的可靠性与安全性。











