
前言:为什么需要这么做?
最近后台频繁反馈接口被劫持、注入攻击,即使配置了CertificatePinner
证书固定,效果也不理想。后台同学一句 "加密!所有重要接口必须加密",让我不得不重新思考移动端的安全策略 ------核心问题在于:密钥(Key)和向量(IV)的存储安全。
直接明文存储密钥?反编译轻松获取,等于没加密。
放在 Java 代码里?ProGuard 混淆也挡不住静态分析。
最终结论:将密钥 / IV 藏在 SO 库中,通过 Native 层解密使用,最大限度提高破解成本。
ini
$key = 'xxxxx';
$iv = 'xxxxx';
$encryptedData = openssl_encrypt($data, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encryptedData);
哈哈就这样甩我脸上一段代码(key,iv隐藏了),解密去吧,还善意,提醒我key和iv建议别明文存储
关键点: 别明文存储
关键数据,今天这篇文章主要做,SO存储关键数据(解密)
思考:非明文存储
吐槽一下:万恶的反编译大佬们真牛逼!!!!
由于灰产大佬在money的驱使下,奋发图强,再加上Android 确实不安全.所以,尽管近几年,混淆加固等方式保证代码安全,但是它还是不安全
啊.所以我们关键的数据还是,自己尽量藏好吧.
回到主题,后台数据是aes加密后的数据 所以他提供了key
和iv
给移动端,移动端拿着这个key和iv去解密,他么你接口安全了 我移动端又不安全了啊(我严重怀疑这是在甩锅.).所以我们只能尽量增加大佬们得到我的可能性.
既然是要key
和iv
尽量不被得到,所以只能藏了,但是Android因为历史问题,确实容易被得到,那么就引入了今天的功能了so库存储重要数据
流程
加密-->so解密-->项目集成so -->jni调用解密方法
1:项目配置
1.1.build.gradle关键配置
csharp
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
}
android {
namespace 'com.wkq.tools'
compileSdk libs.versions.compileSdk.get().toInteger()
useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "com.wkq.tools"
minSdk libs.versions.minSdk.get().toInteger()
targetSdk libs.versions.targetSdk.get().toInteger()
versionCode 1
versionName libs.versions.versionName.get()
vectorDrawables {
useSupportLibrary true
}
externalNativeBuild {
cmake {
cppFlags '-std=c++11'
}
}
ndk {
//设置支持的SO库架构
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs'] // 如果SO库放在app/libs目录下
}
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.1'
}
}
dependencies {
...
}

1.2 配置生成so
bash
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# so库名字
project("decrypt")
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
1.3 调用注意事项


1:给明文数据加密
使用XOR(可逆)算法用xorkey的数据和字符串生成一个数组,将此数据放入c++代码做解密,这样就达到了不直接使用明文的字符串,通过过程得到结果,相对安全.

加密代码
java
package com.wkq.tools.decrypt;
/**
* @Author: wkq
* @Time: 2025/7/11 15:37
* @Desc: 加密字符串
*/
public class EncryptUtils {
/**
* 使用XOR算法加密字符串
* @param originalString 原始字符串
* @param xorKey 加密密钥字节数组
* @return 加密后的字节数组
*/
public static byte[] encrypt(String originalString, byte[] xorKey) {
byte[] encryptedBytes = new byte[originalString.length()];
for (int i = 0; i < originalString.length(); i++) {
// 获取字符的Unicode码点并与密钥字节进行XOR
int charCode = originalString.charAt(i);
encryptedBytes[i] = (byte) (charCode ^ xorKey[i % xorKey.length]);
}
return encryptedBytes;
}
/**
* 使用XOR算法解密字节数组
* @param encryptedBytes 加密后的字节数组
* @param xorKey 解密密钥字节数组(需与加密时相同)
* @return 解密后的原始字符串
*/
public static String decrypt(byte[] encryptedBytes, byte[] xorKey) {
StringBuilder decrypted = new StringBuilder();
for (int i = 0; i < encryptedBytes.length; i++) {
// 将加密字节与密钥字节进行XOR还原字符
char decryptedChar = (char) (encryptedBytes[i] ^ xorKey[i % xorKey.length]);
decrypted.append(decryptedChar);
}
return decrypted.toString();
}
/**
* 将字节数组转换为C++格式的静态数组声明
* @param bytes 字节数组
* @param variableName 变量名
* @return C++格式的字符串
*/
public static String toCPlusPlusFormat(byte[] bytes, String variableName) {
StringBuilder sb = new StringBuilder();
sb.append("static const unsigned char ").append(variableName).append("[] = {\n ");
for (int i = 0; i < bytes.length; i++) {
// 转换为无符号整数
int unsignedByte = bytes[i] & 0xFF;
sb.append(unsignedByte);
if (i != bytes.length - 1) {
sb.append(", ");
}
if ((i + 1) % 8 == 0) {
sb.append("\n ");
}
}
sb.append("\n};");
return sb.toString();
}
}
2:获取so库存储的数据
将上边生成的数据 通过xor算法 逆向出来所需的字符串并且通过 getDecryptedData() 方法返回 ,
这里关闭调试的判断 正式报可以开启这个调试的判断
c++代码解密
c
#include <jni.h>
#include <string>
#include <sys/ptrace.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
// 加密后的字符串1
static const unsigned char segment1[] = { 34, 84, 17, 100, 33, 87, 18, 103, 100, 18, 87, 34 };
// 加密用的异或 key
const unsigned char xorKey[] = {0x55, 0x23, 0x66, 0x13};
// 反调试检测:通过 ptrace
bool detectPtrace() {
int ret = ptrace(PTRACE_TRACEME, 0, nullptr, nullptr);
if (ret == -1) {
return true; // 已被调试
}
ptrace(PTRACE_DETACH, 0, nullptr, nullptr);
return false;
}
// 反调试检测:通过 /proc/self/status 的 TracerPid
bool checkTracerPid() {
FILE* f = fopen("/proc/self/status", "r");
if (!f) return false;
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "TracerPid:", 10) == 0) {
int tracerPid = atoi(line + 10);
fclose(f);
return tracerPid != 0;
}
}
fclose(f);
return false;
}
// 反调试检测:通过 prctl 获取 dumpable 状态
bool checkPtracePrctl() {
int status = prctl(PR_GET_DUMPABLE);
return status == 0;
}
// 综合反调试判断
bool isBeingDebugged() {
return detectPtrace() || checkTracerPid() || checkPtracePrctl();
}
// 解密函数
std::string decryptSegment(const unsigned char* segment, int len) {
std::string result;
for (int i = 0; i < len; ++i) {
char decrypted = segment[i] ^ xorKey[i % sizeof(xorKey)];
result.push_back(decrypted);
}
return result;
}
// 清理敏感内存(简单示例)
void secureClear(std::string& str) {
volatile char* p = const_cast<volatile char*>(str.data());
for (size_t i = 0; i < str.size(); ++i) {
p[i] = 0;
}
}
// 解密
extern "C"
JNIEXPORT jstring JNICALL
Java_com_wkq_tools_decrypt_DecryptUtil_getDecryptedData(JNIEnv* env, jobject /* this */) {
// if (isBeingDebugged()) {
// // 被调试,返回空或崩溃
// return env->NewStringUTF("");
// }
std::string decrypted = decryptSegment(segment1, sizeof(segment1));
jstring result = env->NewStringUTF(decrypted.c_str());
// 解密字符串用完后,清理内存
secureClear(decrypted);
return result;
}
3.生成so文件
so生成目录 项目/app/build/cxx/xx/obj
注意
千万要锤一下

3.项目调用
步骤
- 创建jniLibs 将so放入
- bulid.gradle 配置so库位置
- 初始化库 然后jini调用



kotlin
/**
*
*@Author: wkq
*
*@Time: 2025/7/11 15:32
*
*@Desc:获取So库中的数据
*/
object DecryptUtil {
init {
System.loadLibrary("decrypt")
}
//获取加密后的数据
private external fun getDecryptedData(): String
fun getDecryptedStr(): String? {
val seed = (System.currentTimeMillis() and 0xFFFFFFFF).toInt()
return getDecryptedData()
}
}
总结
整理了一下 明文存储关键数据不安全,换成so存储明文数据增加,破解难度.简单实现了一番,欢迎来个大哥,给个其他思路处理这种问题.码字不易,感谢点赞...