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存储明文数据增加,破解难度.简单实现了一番,欢迎来个大哥,给个其他思路处理这种问题.码字不易,感谢点赞...

相关推荐
LING39 分钟前
RN容器启动优化实践
android·react native
恋猫de小郭3 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker8 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴8 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭18 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab19 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin