Android 通过 SO 库安全存储敏感数据,解决接口劫持问题

前言:为什么需要这么做?

最近后台频繁反馈接口被劫持、注入攻击,即使配置了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加密后的数据 所以他提供了keyiv给移动端,移动端拿着这个key和iv去解密,他么你接口安全了 我移动端又不安全了啊(我严重怀疑这是在甩锅.).所以我们只能尽量增加大佬们得到我的可能性.

既然是要keyiv尽量不被得到,所以只能藏了,但是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存储明文数据增加,破解难度.简单实现了一番,欢迎来个大哥,给个其他思路处理这种问题.码字不易,感谢点赞...

相关推荐
coderlin_7 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
2501_915918417 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview
wen's8 小时前
React Native安卓刘海屏适配终极方案:仅需修改 AndroidManifest.xml!
android·xml·react native
编程乐学9 小时前
网络资源模板--基于Android Studio 实现的聊天App
android·android studio·大作业·移动端开发·安卓移动开发·聊天app
hsx66612 小时前
使用一个 RecyclerView 构建复杂多类型布局
android
hsx66612 小时前
利用 onMeasure、onLayout、onDraw 创建自定义 View
android
守城小轩12 小时前
Chromium 136 编译指南 - Android 篇:开发工具安装(三)
android·数据库·redis
whysqwhw12 小时前
OkHttp平台抽象机制分析
android
hsx66613 小时前
Android 内存泄漏避坑
android