当前位置: 首页 > news >正文

【Settings】恢复出厂设置密码校验

核心思路

由于从 Android 10 (API 级别 29) 开始,出于隐私保护的考虑,普通应用无法再获取设备的 IMEI。尝试获取会抛出 SecurityException

因此,我们将采用一个同样具有唯一性的设备标识符 ANDROID_ID 来替代 IMEI。ANDROID_ID 是一个在设备首次启动时随机生成的64位数字,对于应用的每个签名密钥、用户和设备组合都是唯一的。这同样能满足您“不同设备的ID不同”的需求。

加密方式选择:

我们将使用 HMAC-SHA256 算法。这是一种基于密钥的哈希算法。

  • 加密 (生成密码): 使用一个密钥(Secret Key)对 ANDROID_ID 进行 HMAC-SHA256 运算,得到一个哈希摘要。然后,我们将这个较长的摘要转换为一个6位的数字密码。这个过程是单向的,但对于相同的 ANDROID_ID 和密钥,结果始终是相同的。
  • 解密 (验证密码): “解密”在这里实际上是“验证”。当用户输入一个6位密码时,我们用同样的方法(使用密钥和 ANDROID_ID)再生成一次密码,然后比对两个密码是否一致。如果一致,就代表输入正确。

这种方式完美符合您的需求:

  1. 加密: 从设备ID和密钥生成6位密码。
  2. 反解密: 实际上是验证过程,可以通过密钥和设备ID验证密码是否正确。
  3. 密码检测: 可以校验输入的6位密码。
  4. 唯一性: 密钥是固定的,而不同设备的 ANDROID_ID 不同,因此生成的密码也不同。

完整代码实现

这是一个可以直接在 Android Studio 中运行的项目代码。

1. 项目结构
app
└── src└── main├── java│   └── com│       └── example│           └── deviceidapp│               ├── CryptoUtils.java   // 加密工具类│               └── MainActivity.java  // 主界面Activity├── res│   └── layout│       └── activity_main.xml  // 界面布局└── AndroidManifest.xml
2. 界面布局 (activity_main.xml)

这个文件定义了应用的界面,包含显示ID、显示密码、输入密码和两个操作按钮。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="24dp"android:gravity="center_horizontal"tools:context=".MainActivity"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="设备唯一标识符 (Android ID)"android:textSize="18sp"android:textStyle="bold" /><TextViewandroid:id="@+id/tv_device_id"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:textSize="16sp"tools:text="c123456789abcdef" /><Buttonandroid:id="@+id/btn_generate_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="24dp"android:text="生成6位设备密码" /><TextViewandroid:id="@+id/tv_generated_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:textSize="28sp"android:textStyle="bold"android:textColor="@android:color/holo_blue_dark"tools:text="123456" /><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:layout_marginVertical="32dp"android:background="?android:attr/listDivider" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="输入6位密码进行验证"android:textSize="18sp"android:textStyle="bold"/><EditTextandroid:id="@+id/et_password_input"android:layout_width="200dp"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:hint="请输入6位密码"android:inputType="numberPassword"android:maxLength="6"android:gravity="center"android:textSize="24sp"/><Buttonandroid:id="@+id/btn_verify_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="16dp"android:text="验证密码" /></LinearLayout>
3. 加密工具类 (CryptoUtils.java)

这个类包含了获取设备ID和生成密码的核心逻辑。

package com.example.deviceidapp;import android.annotation.SuppressLint;
import android.content.Context;
import android.provider.Settings;import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.Objects;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;/*** 加密和设备ID工具类*/
public class CryptoUtils {// !!! 重要 !!!// 这是一个密钥,必须妥善保管。在真实项目中,不要硬编码在这里。// 可以考虑从服务器获取,或者使用更安全的存储方式。// 密钥必须是唯一的且保持不变。private static final String SECRET_KEY = "Your_Unique_And_Secret_Key_Here";/*** 获取设备的 ANDROID_ID** @param context 上下文* @return 设备的ANDROID_ID,如果获取失败则返回 "unknown"*/@SuppressLint("HardwareIds")public static String getDeviceID(Context context) {try {String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);return Objects.requireNonNull(androidId);} catch (Exception e) {e.printStackTrace();return "unknown";}}/*** 根据设备ID和密钥生成6位密码** @param deviceId 设备ID (例如 ANDROID_ID)* @return 生成的6位数字密码,格式为 "XXXXXX"*/public static String generatePassword(String deviceId) throws NoSuchAlgorithmException, InvalidKeyException {// 1. 定义加密算法为 HmacSHA256final String ALGORITHM = "HmacSHA256";Mac mac = Mac.getInstance(ALGORITHM);// 2. 使用我们的密钥初始化Mac实例SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);mac.init(secretKeySpec);// 3. 对设备ID进行加密,得到字节数组形式的哈希摘要byte[] hash = mac.doFinal(deviceId.getBytes(StandardCharsets.UTF_8));// 4. 将哈希摘要转换为6位数字// 我们取哈希结果的前4个字节,将其转换为一个正整数int offset = hash.length - 4; // 从末尾取4字节,增加随机性long truncatedHash = 0;for (int i = 0; i < 4; ++i) {truncatedHash <<= 8;truncatedHash |= (hash[offset + i] & 0xFF);}// 确保结果为正数truncatedHash &= 0x7FFFFFFF;// 5. 对这个整数取模,得到一个 0 到 999999 之间的数long sixDigitNumber = truncatedHash % 1000000;// 6. 格式化为6位数字,不足6位的前面补0return String.format("%06d", sixDigitNumber);}
}
4. 主界面 (MainActivity.java)

这个文件负责处理界面交互逻辑,调用 CryptoUtils 来完成功能。

package com.example.deviceidapp;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;public class MainActivity extends AppCompatActivity {private TextView tvDeviceId;private TextView tvGeneratedPassword;private EditText etPasswordInput;private Button btnGeneratePassword;private Button btnVerifyPassword;private String currentDeviceId;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化视图tvDeviceId = findViewById(R.id.tv_device_id);tvGeneratedPassword = findViewById(R.id.tv_generated_password);etPasswordInput = findViewById(R.id.et_password_input);btnGeneratePassword = findViewById(R.id.btn_generate_password);btnVerifyPassword = findViewById(R.id.btn_verify_password);// 1. 获取并显示设备IDcurrentDeviceId = CryptoUtils.getDeviceID(this);tvDeviceId.setText(currentDeviceId);// 2. 设置“生成密码”按钮的点击事件btnGeneratePassword.setOnClickListener(v -> {try {// 使用工具类生成密码String password = CryptoUtils.generatePassword(currentDeviceId);// 显示生成的密码tvGeneratedPassword.setText(password);Toast.makeText(this, "密码已生成", Toast.LENGTH_SHORT).show();} catch (NoSuchAlgorithmException | InvalidKeyException e) {// 异常处理e.printStackTrace();Toast.makeText(this, "错误:无法生成密码", Toast.LENGTH_LONG).show();}});// 3. 设置“验证密码”按钮的点击事件btnVerifyPassword.setOnClickListener(v -> {// 获取用户输入的密码String userInput = etPasswordInput.getText().toString();// 检查输入是否为6位if (userInput.length() != 6) {Toast.makeText(this, "请输入6位数字密码进行验证", Toast.LENGTH_SHORT).show();return; // 提前退出}try {// 再次生成正确的密码,用于比对String correctPassword = CryptoUtils.generatePassword(currentDeviceId);// 比对用户输入和正确的密码if (userInput.equals(correctPassword)) {// 验证成功Toast.makeText(this, "验证成功!密码正确。", Toast.LENGTH_LONG).show();} else {// 验证失败Toast.makeText(this, "验证失败!密码错误。", Toast.LENGTH_LONG).show();}} catch (NoSuchAlgorithmException | InvalidKeyException e) {// 异常处理e.printStackTrace();Toast.makeText(this, "错误:无法进行验证", Toast.LENGTH_LONG).show();}});}
}
5. Android 清单文件 (AndroidManifest.xml)

这是应用必需的配置文件,声明了应用的入口。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/dataExtractionRules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.DeviceIDApp"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>

如何运行

  1. 打开 Android Studio
  2. 创建一个新的空项目 (Empty Views Activity)。
  3. 替换 activity_main.xml: 将上面提供的 XML 代码复制并覆盖到 app/src/main/res/layout/activity_main.xml 文件中。
  4. 创建 CryptoUtils.java: 在 com.example.yourappname 包下(与MainActivity.java同级),右键 -> New -> Java Class,命名为 CryptoUtils,然后将上面提供的 Java 代码复制进去。
  5. 修改 SECRET_KEY: 在 CryptoUtils.java 文件中,将 SECRET_KEY 的值 "Your_Unique_And_Secret_Key_Here" 修改为你自己的、独特的、保密的字符串。
  6. 替换 MainActivity.java: 将上面提供的 MainActivity.java 代码复制并覆盖到项目已有的同名文件中。
  7. 运行应用: 点击 Android Studio 的 “Run” 按钮,将应用安装到模拟器或真实设备上。

重要安全提示

密钥(SECRET_KEY)的存储

CryptoUtils.java 的代码中,我们直接将密钥硬编码为了一个字符串常量。这在生产环境中是不安全的! 任何反编译您应用的人都可以轻易地找到这个密钥。

在真实项目中,请考虑使用更安全的方式来存储和管理您的密钥,例如:

  • Android Keystore 系统:将密钥存储在硬件支持的安全容器中。
  • NDK 和 C++ 代码:将密钥存储在原生代码中,增加反编译的难度。
  • 从服务器获取:在应用启动时通过安全的网络连接从您的服务器获取密钥。

对于您的需求,保持这个密钥在应用中的唯一性和不变性是关键。

在AOSP源码环境中编译系统应用(privileged-app或platform app),你拥有比普通SDK开发高得多的权限和更灵活的手段来处理这类安全问题。将SECRET_KEY硬编码在Java代码中是绝对不推荐的做法。

从受保护的文件系统分区读取(推荐,最安全)

这是最安全的方法。在编译时,将密钥写入到一个只有你的应用(以及少数系统进程)有权读取的文件中,并使用 SELinux策略 严格限制对该文件的访问。

实现步骤:
  1. 创建密钥文件
    在你的设备目录下 (例如 device/<vendor>/<product>/) 创建一个名为 my_app_secret.key 的文件,里面只包含你的密钥字符串。

  2. 在编译时将文件复制到设备分区
    在你的产品 makefile (product.mk) 中,添加规则将该文件复制到设备的一个受保护目录,例如 /data/misc/myapp/

    PRODUCT_COPY_FILES += \device/<vendor>/<product>/my_app_secret.key:$(TARGET_COPY_OUT_DATA)/misc/myapp/secret.key
    
  3. 定义严格的SELinux策略
    这是最关键的一步,它保证了文件的安全性。
    a. 为文件定义一个类型 (file_contexts):在 sepolicy 目录下,通常在 file_contexts 文件中添加:
    /data/misc/myapp(/.*)? u:object_r:myapp_key_file:s0
    b. 为你的应用定义一个域 (seapp_contexts):确保你的应用在一个独立的SELinux域中运行,例如 myapp_app
    c. 授予你的应用域读写权限 (app.te):在你的应用的策略文件 myapp_app.te 中,添加如下规则:
    # 允许 myapp_app 域搜索 /data/misc/myapp 目录 allow myapp_app myapp_key_file:dir search; # 允许 myapp_app 域读取 myapp_key_file 类型的文件 allow myapp_app myapp_key_file:file { read open getattr };
    这条规则意味着,只有被标记为 myapp_app 域的进程才能读取被标记为 myapp_key_file 类型的文件。其他任何应用(即使是root用户启动的进程,在SELinux Enforcing模式下)都无法访问。

  4. 在你的App中读取文件
    现在你的应用可以直接通过标准的文件IO操作来读取这个密钥。

    import java.io.File;
    import java.nio.file.Files;
    import java.nio.file.Paths;private String readSecretKeyFromFile() {File keyFile = new File("/data/misc/myapp/secret.key");try {// 需要确保你的应用有权限创建/访问这个目录// 在init.rc中添加 chown 和 chmod 命令String key = new String(Files.readAllBytes(keyFile.toPath()));return key.trim();} catch (IOException e) {e.printStackTrace();// 处理异常,密钥获取失败return null;}
    }
    

    你还需要在 init.rc 文件中确保 /data/misc/myapp 目录被创建并且有正确的属主和权限。

优缺点:
  • 优点:
    • 极高的安全性。密钥既不在APK中,也不在任何人都能读的属性里。访问被强大的SELinux强制访问控制(MAC)机制所保护。
    • 完全将密钥的配置和应用代码解耦。
  • 缺点:
    • 实现最为复杂,需要你对AOSP的编译系统、init系统以及SELinux策略有深入的了解。

将Java中的 generatePassword 方法的逻辑翻译成Python 脚本是完全可行的,只要确保每一步的加密和数据处理逻辑都完全一致,就能为相同的 deviceIdSECRET_KEY 生成完全相同的6位密码。


Python 脚本 (generate_password.py)

Python 拥有强大的 hmachashlib 库,可以非常精确地实现这个逻辑。这是推荐的脚本方式,因为它更清晰易读。

脚本代码
#!/usr/bin/env python3
import sys
import hmac
import hashlib# !!! 重要 !!!
# 这个密钥必须和你的Java代码/其他脚本中的密钥完全一致
SECRET_KEY = "Your_Unique_And_Secret_Key_Here"def generate_password(device_id: str, secret_key: str) -> str:"""根据设备ID和密钥生成6位密码,逻辑与Java版本完全相同。Args:device_id: 设备唯一标识符 (例如 ANDROID_ID).secret_key: 用于HMAC加密的密钥.Returns:一个6位的数字密码字符串."""try:# 1. 将密钥和设备ID转换为UTF-8编码的字节key_bytes = secret_key.encode('utf-8')data_bytes = device_id.encode('utf-8')# 2. 使用HmacSHA256计算哈希摘要# hmac.new().digest() 返回的是原始字节 (raw bytes)hash_bytes = hmac.new(key_bytes, data_bytes, hashlib.sha256).digest()# 3. 截取哈希摘要的最后4个字节# 这等同于Java代码中的 offset = hash.length - 4offset_bytes = hash_bytes[-4:]# 4. 将这4个字节转换为一个32位的大端序 (big-endian) 整数truncated_hash = int.from_bytes(offset_bytes, 'big')# 5. 将整数的最高位(符号位)清零,确保结果为正数# 这等同于Java代码中的 truncatedHash &= 0x7FFFFFFFpositive_hash = truncated_hash & 0x7FFFFFFF# 6. 对1,000,000取模,得到一个0到999999之间的数字six_digit_number = positive_hash % 1000000# 7. 格式化为6位数字,不足的前面补0# 这等同于Java代码中的 String.format("%06d", ...)return f'{six_digit_number:06d}'except Exception as e:print(f"发生错误: {e}", file=sys.stderr)return Noneif __name__ == "__main__":# 检查命令行参数是否足够if len(sys.argv) < 2:print(f"用法: python {sys.argv[0]} <device_id>")sys.exit(1)# 从命令行第一个参数获取device_idinput_device_id = sys.argv[1]password = generate_password(input_device_id, SECRET_KEY)if password:print(password)
如何使用
  1. 将上面的代码保存为 generate_password.py 文件。

  2. 确保 SECRET_KEY 的值与你 Android 项目中的完全一样。

  3. 在命令行中运行,并提供一个 deviceId 作为参数:

    # 赋予执行权限 (可选)
    chmod +x generate_password.py# 运行脚本
    python3 generate_password.py "c123456789abcdef"
    

    或者

    ./generate_password.py "c123456789abcdef"
    

脚本会输出计算得到的6位密码。


文章转载自:

http://QuouhyOI.dbhnx.cn
http://KnZ2lDNT.dbhnx.cn
http://l8Ozvbkk.dbhnx.cn
http://r2vMGNHM.dbhnx.cn
http://7BPPOM1s.dbhnx.cn
http://31TskvGY.dbhnx.cn
http://HW85zXqe.dbhnx.cn
http://7pHJ3z2L.dbhnx.cn
http://Qty1Sapf.dbhnx.cn
http://CEdmbZjR.dbhnx.cn
http://mpBSYH5h.dbhnx.cn
http://duKy7xC0.dbhnx.cn
http://NI56xEQ4.dbhnx.cn
http://N4eOTwlz.dbhnx.cn
http://FQ08ZnSZ.dbhnx.cn
http://VOSfLRXb.dbhnx.cn
http://yTuKnMtj.dbhnx.cn
http://wdgfHmHx.dbhnx.cn
http://G7N0cNBJ.dbhnx.cn
http://b00d2olM.dbhnx.cn
http://np7JWx8i.dbhnx.cn
http://Ee1ez9Nb.dbhnx.cn
http://f6c1WLkn.dbhnx.cn
http://prJPhtGn.dbhnx.cn
http://832xjYN2.dbhnx.cn
http://hd0OnJhv.dbhnx.cn
http://oVguVplR.dbhnx.cn
http://spypmXCz.dbhnx.cn
http://uN4mMCw2.dbhnx.cn
http://KrItZyvQ.dbhnx.cn
http://www.dtcms.com/a/379630.html

相关文章:

  • 机器人控制器开发(通讯——ros话题转为websocket)
  • Go 1.25.1 自定义包调用
  • go语言,彩色验证码生成,加减法验证,
  • 深入解析 AST2600 H2B 接口:架构、原理与完整开发指南
  • 手机ip隔离方法
  • RAG检索增强生成:让AI拥有“外部记忆“的黑科技
  • Jmter接口网站压力测试工具使用记录
  • Agentic BI技术解构:多智能体协作框架如何实现“分析-决策-执行”闭环?
  • 如何用AI做海报、IP设计,稿定AI一站式创作
  • Threejs案例实践笔记
  • React18学习笔记(一) 如何创建一个React项目,JSX的基础应用,案例---视频网站评论区
  • 【Threejs】学习笔记
  • 图像显示技术与色彩转换:从基础原理到实际应用
  • C 语言实现 I.MX6ULL 点灯(续上一篇)、SDK、deep及bsp工程管理
  • 飞桨paddlepaddle旧版本2.4.2安装
  • 2.5 DNS(Domain Name System)
  • CK: 03靶场渗透
  • User类CRUD实现
  • AFSim2.9.0学习笔记 —— 4.2、ArkSIM文件结构介绍及项目结构整理
  • JavaScript WebAPI 指南
  • 计算机毕业设计 基于Hadoop的南昌房价数据分析系统的设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 电路学习(六)三极管
  • 照度传感器考虑笔记
  • 在springboot中使用okhttp3
  • Android开发之Android官方模拟器启动失败问题跟踪排查
  • 第4节-排序和限制-FETCH
  • 2025.9.11总结
  • 三大范式是什么?
  • 传统文化与现代科技的完美融合:文昌帝君灵签系统开发实践
  • 避坑指南:从原理出发解决常见问题