Files
qiming/qimingclaw/crates/agent-electron-client/docs/architecture/device-id.md

3.8 KiB
Raw Permalink Blame History

version, last-updated, status
version last-updated status
1.1 2026-03-05 implemented

Device ID 生成方案

概述

为 Electron 客户端生成稳定、安全的设备唯一标识符deviceId用于设备识别、授权绑定等场景。


方案

核心公式

deviceId = SHA-256(machineId + appSalt)
  • machineId:操作系统级机器标识(通过 node-machine-id 获取原始值)
  • appSalt:应用固定盐值,避免不同应用使用相同 machineId 产生碰撞

实现

import { machineIdSync } from "node-machine-id";
import { createHash } from "crypto";
import * as os from "os";
import log from "electron-log";

const APP_SALT = "qiming-agent";
let cachedDeviceId: string | null = null;

export function getDeviceId(): string {
  if (cachedDeviceId) return cachedDeviceId;

  let raw: string;
  try {
    raw = machineIdSync(true);
  } catch (e) {
    log.warn("[DeviceId] Failed to read machineId, using hostname fallback:", e);
    raw = os.hostname();
  }

  cachedDeviceId = createHash("sha256")
    .update(raw + APP_SALT)
    .digest("hex");

  log.info(`[DeviceId] ${cachedDeviceId}`);
  return cachedDeviceId;
}

各平台 machineId 数据源

平台 数据源 说明
macOS ioregIOPlatformUUID 硬件 UUID无需管理员权限
Windows 注册表 HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid 系统安装时生成
Linux /var/lib/dbus/machine-id systemd 机器 ID

依赖

包名 版本 说明
node-machine-id ^1.1.12 跨平台机器 ID 读取,零 native 依赖

crypto 为 Node.js 内置模块,无需额外安装。


设计要点

1. 稳定性

  • 同一台机器每次调用返回相同值
  • 进程内缓存,避免重复读取系统命令

2. 安全性

  • SHA-256 单向 hash不暴露原始 machineId
  • 加入应用盐值,防止跨应用关联

3. 唯一性

  • machineId 由操作系统保证唯一
  • 加盐 hash 后不同应用产生不同 deviceId

4. 位置

  • 实现在 main processsrc/main/services/ 下)
  • renderer 通过 IPC + preload bridge 获取

架构集成

Main Process
├── src/main/services/system/deviceId.ts   # 实现 + 缓存 + 日志
├── src/main/services/system/index.ts      # re-export
├── src/main/main.ts                       # 启动时调用并输出日志
└── src/main/ipc/appHandlers.ts            # IPC handler

Preload
└── src/preload/index.ts                   # contextBridge 暴露

Renderer
└── window.electronAPI.app.getDeviceId()   # 调用

IPC 通道

// main (appHandlers.ts)
ipcMain.handle("app:getDeviceId", () => getDeviceId());

// preload (index.ts)
app: {
  getDeviceId: () => ipcRenderer.invoke("app:getDeviceId"),
}

特性对比(为何选此方案)

方案 稳定性 安全性 依赖 备注
hash(machineId + salt) 1 个 npm 包 选用
纯 machineId 低(暴露原始值) 1 个 npm 包 不推荐
hw-fingerprint硬件指纹 中(换硬件会变) 重依赖 systeminformation 过重
本地持久化 UUID 低(删文件会变) 不绑定设备
hash(machineId + randomUUID) 无(每次不同) - - 错误方案

注意事项

  1. 容器/镜像环境:基于镜像的环境可能共享相同 machine-idLinux 下可用 dbus-uuidgen 重新生成
  2. Windows 系统更新MachineGuid 在重大系统更新后极少数情况下可能变化
  3. macOS 权限IOPlatformUUID 无需额外权限,区别于 IOPlatformSerialNumber新版 macOS 受限)

参考: 此方案为 SaaS 客户端常用的设备标识模式