
本文详解 JNI 层使用 OpenSSL 进行 RSA 加解密时因参数误传导致解密结果乱码的核心问题,重点指出 RSA_public_encrypt 错误传入 jbyteArray 而非实际字节数组指针,并提供修正后的 C++ 实现、内存管理规范及安全实践建议。
本文详解 jni 层使用 openssl 进行 rsa 加解密时因参数误传导致解密结果乱码的核心问题,重点指出 rsa_public_encrypt 错误传入 jbytearray 而非实际字节数组指针,并提供修正后的 c++ 实现、内存管理规范及安全实践建议。
在 Android JNI 开发中,通过 OpenSSL 调用 RSA 公钥加密、私钥解密是常见需求,但极易因底层内存操作不严谨引发“加密成功、解密乱码”的隐蔽问题。如题所述:虽然 RSA_private_decrypt 返回了正确长度(256 字节),但解密后字节内容完全错误——这并非密钥或填充方式问题,而是 JNI 数据传递过程中的致命参数误用。
? 根本原因:RSA_public_encrypt 传参错误
关键错误出现在加密函数调用中:
// ❌ 错误写法(原文代码)
int iResult = RSA_public_encrypt(
plainText_len,
(unsigned char*)plain_text, // ← plain_text 是 jbyteArray 对象!不是字节数组地址!
chEncryptedData,
createRSApub(sKey),
RSA_PKCS1_OAEP_PADDING
);
plain_text 是 Java 侧传入的 jbyteArray 引用(即 JVM 内部对象句柄),而非指向原始字节的指针。强制 (unsigned char*) 类型转换会将该句柄值解释为内存地址,造成越界读取或非法访问——侥幸未崩溃仅因地址偶然可读,但数据必然错误。
正确做法是使用 env->GetByteArrayElements() 获取真实字节指针,并严格配对 ReleaseByteArrayElements() 防止内存泄漏:
// ✅ 正确写法(修正后)
jbyte* plainBytes = env->GetByteArrayElements(plain_text, &isCopy);
if (!plainBytes) {
LOGE("Failed to get plain text bytes");
return nullptr;
}
int result = RSA_public_encrypt(
plainText_len,
reinterpret_cast<const unsigned char>(plainBytes), // ← 使用获取到的真实指针
chEncryptedData,
createRSApub(sKey),
RSA_PKCS1_OAEP_PADDING
);
env->ReleaseByteArrayElements(plain_text, plainBytes, JNI_ABORT); // 仅读取,不回写</const>
同理,解密函数中也需修正 encText 的来源:
// ✅ 解密端修正
jbyte* encBytes = env->GetByteArrayElements(enc_text, &isCopy);
if (!encBytes) {
LOGE("Failed to get encrypted bytes");
return nullptr;
}
int result = RSA_private_decrypt(
encText_len,
reinterpret_cast<const unsigned char>(encBytes), // ← 关键修正
chDecryptedData,
createRSApriv(sKey),
RSA_PKCS1_OAEP_PADDING
);
env->ReleaseByteArrayElements(enc_text, encBytes, JNI_ABORT);</const>
⚠️ 其他必须检查的关键点
-
密钥格式一致性
- 公钥必须使用
PEM_read_bio_RSA_PUBKEY()(对应-----BEGIN PUBLIC KEY-----); - 私钥必须使用
PEM_read_bio_RSAPrivateKey()(对应-----BEGIN RSA PRIVATE KEY-----)。
若混用PEM_read_bio_RSAPublicKey()(要求-----BEGIN RSA PUBLIC KEY-----),将导致密钥加载失败且无明确报错。
- 公钥必须使用
缓冲区大小与返回长度校验
RSA-2048 + OAEP 填充的最大明文长度为2048/8 - 2*SHA1_DIGEST_LENGTH - 2 = 190字节。务必确保plainText_len ≤ 190,否则RSA_public_encrypt直接返回-1。解密后应验证result > 0 && result 。-
OpenSSL 错误处理强化
原代码中ERR_load_crypto_strings()仅需调用一次(通常在 JNI_OnLoad 中),且ERR_error_string()已废弃,推荐使用ERR_error_string_n()避免缓冲区溢出:if (result == -1) { char errBuf[256]; ERR_error_string_n(ERR_get_error(), errBuf, sizeof(errBuf)); LOGE("RSA encryption failed: %s", errBuf); return nullptr; } -
内存安全:避免栈缓冲区溢出
原代码使用固定大小栈数组unsigned char chEncryptedData[4098]存储密文。对于 RSA-2048,OAEP 加密后密文长度恒为 256 字节(2048 位 / 8),但硬编码过大易掩盖栈溢出风险。建议改用std::vector<uint8_t></uint8_t>动态分配:std::vector<uint8_t> encrypted(256); // 精确尺寸 int len = RSA_public_encrypt(plainText_len, plainPtr, encrypted.data(), rsa, RSA_PKCS1_OAEP_PADDING);</uint8_t>
✅ 完整修正后的加密函数示例
extern "C"
JNIEXPORT jbyteArray JNICALL
Java_com_example_myapplication_MainActivity_publicencryptRsa(
JNIEnv *env, jobject thiz, jstring key, jbyteArray plain_text) {
// 1. 获取公钥字符串
const char *keyStr = env->GetStringUTFChars(key, nullptr);
if (!keyStr) return nullptr;
std::string sKey(keyStr);
env->ReleaseStringUTFChars(key, keyStr);
// 2. 获取明文字节
jsize plainLen = env->GetArrayLength(plain_text);
jbyte *plainBytes = env->GetByteArrayElements(plain_text, nullptr);
if (!plainBytes) return nullptr;
// 3. 创建 RSA 结构体
RSA *rsa = createRSApub(sKey);
if (!rsa) {
env->ReleaseByteArrayElements(plain_text, plainBytes, JNI_ABORT);
LOGE("Failed to load public key");
return nullptr;
}
// 4. 分配输出缓冲区(RSA-2048 OAEP 固定 256 字节)
std::vector<uint8_t> encrypted(256);
int ret = RSA_public_encrypt(
static_cast<int>(plainLen),
reinterpret_cast<const uint8_t>(plainBytes),
encrypted.data(),
rsa,
RSA_PKCS1_OAEP_PADDING
);
// 5. 清理资源
RSA_free(rsa);
env->ReleaseByteArrayElements(plain_text, plainBytes, JNI_ABORT);
if (ret != 256) {
LOGE("RSA encryption failed: %d", ret);
return nullptr;
}
// 6. 返回结果
jbyteArray result = env->NewByteArray(ret);
env->SetByteArrayRegion(result, 0, ret, reinterpret_cast<const jbyte>(encrypted.data()));
return result;
}</const></const></int></uint8_t>
? 总结
JNI 层 OpenSSL RSA 实现失败的主因往往不在密码学逻辑,而在 JNI 数据桥接的细节疏漏。本文指出并修复了最典型的 jbyteArray 误当指针使用的错误,同时强调:
✅ 始终通过 GetXXXElements() 获取原生数据指针;
✅ 严格配对 ReleaseXXXElements();
✅ 校验密钥格式与算法参数一致性;
✅ 使用动态容器替代固定栈数组提升安全性;
✅ 在 JNI_OnLoad 中初始化 OpenSSL 错误字符串(OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr))。
遵循以上规范,即可稳定实现跨语言 RSA 加解密,彻底规避“解密长度正确但内容错误”的陷阱。











