目标
在配置 Java 项目时,配制参数通过 AES 来加密,但反编译 class,很容易拿到秘钥, Rust 开发 JNI 提供给 java 接口,是一个相对安全的方法(不是绝对安全)。
实现步骤
要 实现 jniutil 库将支持 armeabi-v7a、arm64-v8a、x86 和 x86_64 四种 ABI。
你需要:
- 配置 Rust 目标。
- 设置 NDK 工具链。
- 分别编译每个目标的 .so 文件。
- 将生成的库集成到 Android 项目中。
编译工具
NDK
安装完NDK后,配置环境变量:
set ANDROID_NDK_HOME="C:\Program Files (x86)\Android\AndroidNDK\android-ndk-r23c"
对应 armeabi-v7a
rustup target add armv7-linux-androideabi
对应 arm64-v8a
rustup target add aarch64-linux-android
对应 x86
rustup target add i686-linux-android
对应 x86_64
rustup target add x86_64-linux-android
配置 Cargo.toml
[package]
name = "jniutil"
version = "0.1.0"
edition = "2024"
[dependencies]
jni = "0.19.0"
rust-crypto = "0.2"
clap = "2.33"
hex = "0.4"
base64 = "0.13.0"
# reqwest = { version = "0.11", features = ["json"] }
# tokio = { version = "1.0", features = ["full"] }
# futures = { version = "0.3.*" }
# async-std = { version = "1", features = ["attributes", "tokio1"] }
[lib]
crate-type = ["cdylib"]
注意:
crate-type 只适用于 2024版本的rust,如果是2024之前的版本要用crate_type
代码 /src/lib.rs
// This is the interface to the JVM that we'll
// call the majority of our methods on.
use jni::JNIEnv;
// These objects are what you should use as arguments to your native function.
// They carry extra lifetime information to prevent them escaping this context
// and getting used after being GC'd.
use jni::objects::{JClass, JString};
// This is just a pointer. We'll be returning it from our function.
// We can't return one of the objects with lifetime information because the
// lifetime checker won't let us.
use jni::sys::jstring;
use std::error::Error;
use crypto::aes::KeySize::KeySize128;
use crypto::blockmodes::PkcsPadding;
use crypto::buffer::{RefReadBuffer, RefWriteBuffer};
const DP_CBC_KEY: &str = "1111111111111111";
const DP_CBC_OFFSET: &str = "1111111111111111";
// 注意包名 com_doopp_common_util_JniUtil
#[no_mangle]
pub extern "system" fn Java_com_doopp_common_util_JniUtil_dpEnc(
env: JNIEnv,
// this is the class that owns our
// static method. Not going to be
// used, but still needs to have
// an argument slot
_class: JClass,
input: JString,
) -> jstring {
// First, we have to get the string out of java. Check out the `strings`
// module for more info on how this works.
let input: String = env
.get_string(input)
.expect("Couldn't get java string!")
.into();
// Then we have to create a new java string to return. Again, more info
// in the `strings` module.
let enc_str: String = encrypt(DP_CBC_KEY, DP_CBC_OFFSET, input.as_str()).unwrap();
let output = env
.new_string(enc_str)
.expect("Couldn't get encrypt string!");
// Finally, extract the raw pointer to return.
output.into_inner()
}
// 注意包名 com_doopp_common_util_JniUtil
#[no_mangle]
pub extern "system" fn Java_com_doopp_common_util_JniUtil_dpDec(
env: JNIEnv,
// this is the class that owns our
// static method. Not going to be
// used, but still needs to have
// an argument slot
_class: JClass,
input: JString,
) -> jstring {
// First, we have to get the string out of java. Check out the `strings`
// module for more info on how this works.
let input: String = env
.get_string(input)
.expect("Couldn't get java string!")
.into();
// Then we have to create a new java string to return. Again, more info
// in the `strings` module.
let dec_str: String = decrypt(DP_CBC_KEY, DP_CBC_OFFSET, input.as_str()).unwrap();
let output = env
.new_string(dec_str)
.expect("Couldn't get decrypt string!");
// Finally, extract the raw pointer to return.
output.into_inner()
}
/// 加密
fn encrypt(key: &str, iv: &str, text: &str) -> Result<String, Box<dyn Error>> {
let mut encrypt = crypto::aes::cbc_encryptor(
KeySize128,
key.as_bytes(),
iv.as_bytes(),
PkcsPadding
);
let mut read_buffer = RefReadBuffer::new(text.as_bytes());
let mut result = vec![0; 4096];
let mut write_buffer = RefWriteBuffer::new(&mut result);
encrypt.encrypt(&mut read_buffer, &mut write_buffer, true).unwrap();
let enc_str = base64::encode_config(trim(&result)?, base64::STANDARD);
Ok(enc_str)
}
/// 解密
fn decrypt(key: &str, iv: &str, input: &str) -> Result<String, Box<dyn Error>> {
let mut decrypt = crypto::aes::cbc_decryptor(
KeySize128,
key.as_bytes(),
iv.as_bytes(),
PkcsPadding
);
let base_text = base64::decode_config(input, base64::STANDARD)?;
let mut read_buffer = RefReadBuffer::new(&base_text);
let mut result = vec![0; input.len()];
let mut write_buffer = RefWriteBuffer::new(&mut result);
decrypt.decrypt(&mut read_buffer, &mut write_buffer, true).unwrap();
let dec_str = String::from_utf8(trim(&result)?)?;
Ok(dec_str)
}
fn trim(input: &Vec<u8>) -> Result<Vec<u8>, Box<dyn Error>> {
let zero : u8 = 0;
let mut fz_idx = input.len();
for (idx, val) in input.iter().rev().enumerate() {
if &zero!=val
{
fz_idx = fz_idx - idx;
break;
}
}
Ok(input[0..fz_idx].to_vec())
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}
编译 libjniutil.dylib 并拷贝在 /resources/jni_lib 目录下
构建不同 ABI 的库
在命令行中,使用 cargo build 为每个目标分别编译,生成对应的 .so 文件。例如:
armeabi-v7a
cargo build --target armv7-linux-androideabi --release
输出文件位于:target/armv7-linux-androideabi/release/libjniutil.so
arm64-v8a
cargo build --target aarch64-linux-android --release
输出文件位于:target/aarch64-linux-android/release/libjniutil.so
x86
cargo build --target i686-linux-android --release
输出文件位于:target/i686-linux-android/release/libjniutil.so
x86_64
cargo build --target x86_64-linux-android --release
输出文件位于:target/x86_64-linux-android/release/libjniutil.so 使用 –release 参数可以生成优化的发布版本。
在 Android 项目中使用
将生成的 .so 文件放入 Android 项目的 app/src/main/jniLibs 目录,结构如下:
目录结构
app/src/main/jniLibs/
├── armeabi-v7a/
│ └── libjniutil.so
├── arm64-v8a/
│ └── libjniutil.so
├── x86/
│ └── libjniutil.so
└── x86_64/
└── libjniutil.so
Android应用中调用接口
在 build.gradle 中配置 abiFilters:
android {
defaultConfig {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
Android的JNI调用代码 注意包名和 lib.rs 的一致
package com.example.myapp; // 替换为你的包名
public class JniUtil {
static {
// 加载 Rust 编译生成的动态库,文件名是 libjniutil.so,但加载时只写 "jniutil"
System.loadLibrary("jniutil");
}
// 声明本地方法(这些方法需要在 Rust 中实现)
// 示例 1:无参数,返回字符串
public native String getHelloMessage();
// 示例 2:带整数参数,返回计算结果
public native int addNumbers(int a, int b);
// 示例 3:带字符串参数,返回处理后的字符串
public native String processText(String input);
// 可选:一个简单的 Java 方法用于测试
public String getWelcomeMessage() {
return "Welcome from Java!";
}
// 主函数用于测试(如果直接运行 Java 类)
public static void main(String[] args) {
JniUtil util = new JniUtil();
// 调用本地方法
System.out.println("Hello Message: " + util.getHelloMessage());
System.out.println("Add 3 + 5: " + util.addNumbers(3, 5));
System.out.println("Processed Text: " + util.processText("Hello from Java"));
// 调用 Java 方法
System.out.println(util.getWelcomeMessage());
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JniUtil util = new JniUtil();
String message = util.getHelloMessage();
Log.d("JniUtil", "Message from Rust: " + message);
}
}