use ring::digest;
use std::process::Command;
#[derive(Debug, Clone)]
pub struct HardwareFingerprint {
pub fingerprint: String,
}
impl HardwareFingerprint {
pub fn collect() -> Result<Self, String> {
let mut components = Vec::new();
if let Ok(cpu_info) = Self::get_cpu_info() {
components.push(format!("CPU:{}", cpu_info));
}
if let Ok(serial) = Self::get_system_serial() {
components.push(format!("SERIAL:{}", serial));
}
if let Ok(mac) = Self::get_mac_address() {
components.push(format!("MAC:{}", mac));
}
if let Ok(disk) = Self::get_disk_serial() {
components.push(format!("DISK:{}", disk));
}
if components.is_empty() {
return Err("无法收集硬件指纹信息".to_string());
}
let combined = components.join("|");
let hash = digest::digest(&digest::SHA256, combined.as_bytes());
let fingerprint = hex::encode(hash.as_ref());
Ok(Self { fingerprint })
}
fn get_cpu_info() -> Result<String, String> {
#[cfg(target_os = "macos")]
{
let output = Command::new("sysctl")
.args(&["-n", "machdep.cpu.brand_string"])
.output()
.map_err(|e| format!("获取CPU信息失败: {}", e))?;
if output.status.success() {
let info = String::from_utf8_lossy(&output.stdout).trim().to_string();
return Ok(info);
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
for line in content.lines() {
if line.starts_with("model name") {
if let Some(name) = line.split(':').nth(1) {
return Ok(name.trim().to_string());
}
}
}
}
}
Err("无法获取CPU信息".to_string())
}
fn get_system_serial() -> Result<String, String> {
#[cfg(target_os = "macos")]
{
let output = Command::new("ioreg")
.args(&["-l"])
.output()
.map_err(|e| format!("获取系统序列号失败: {}", e))?;
if output.status.success() {
let content = String::from_utf8_lossy(&output.stdout);
for line in content.lines() {
if line.contains("IOPlatformSerialNumber") {
if let Some(serial) = line.split('=').nth(1) {
let serial = serial.trim().trim_matches('"').to_string();
if !serial.is_empty() {
return Ok(serial);
}
}
}
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/sys/class/dmi/id/product_serial") {
let serial = content.trim().to_string();
if !serial.is_empty() && serial != "0" {
return Ok(serial);
}
}
}
Err("无法获取系统序列号".to_string())
}
fn get_mac_address() -> Result<String, String> {
#[cfg(target_os = "macos")]
{
let output = Command::new("ifconfig")
.output()
.map_err(|e| format!("获取MAC地址失败: {}", e))?;
if output.status.success() {
let content = String::from_utf8_lossy(&output.stdout);
for line in content.lines() {
if line.contains("ether") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
return Ok(parts[1].to_string());
}
}
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(content) = std::fs::read_to_string("/sys/class/net/eth0/address") {
let mac = content.trim().to_string();
if !mac.is_empty() {
return Ok(mac);
}
}
}
Err("无法获取MAC地址".to_string())
}
fn get_disk_serial() -> Result<String, String> {
#[cfg(target_os = "macos")]
{
let output = Command::new("diskutil")
.args(&["info", "/"])
.output()
.map_err(|e| format!("获取硬盘序列号失败: {}", e))?;
if output.status.success() {
let content = String::from_utf8_lossy(&output.stdout);
for line in content.lines() {
if line.contains("Volume UUID") || line.contains("Disk / Partition UUID") {
if let Some(uuid) = line.split(':').nth(1) {
return Ok(uuid.trim().to_string());
}
}
}
}
}
#[cfg(target_os = "linux")]
{
let output = Command::new("lsblk")
.args(&["-o", "UUID", "-n"])
.output()
.map_err(|e| format!("获取硬盘UUID失败: {}", e))?;
if output.status.success() {
let uuid = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !uuid.is_empty() {
return Ok(uuid);
}
}
}
Err("无法获取硬盘序列号".to_string())
}
pub fn as_str(&self) -> &str {
&self.fingerprint
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_fingerprint() {
match HardwareFingerprint::collect() {
Ok(fp) => {
println!("硬件指纹: {}", fp.as_str());
assert!(!fp.as_str().is_empty());
assert_eq!(fp.as_str().len(), 64); }
Err(e) => {
println!("警告: 无法收集硬件指纹: {}", e);
}
}
}
#[test]
fn test_fingerprint_consistency() {
let fp1 = HardwareFingerprint::collect();
let fp2 = HardwareFingerprint::collect();
match (fp1, fp2) {
(Ok(f1), Ok(f2)) => {
assert_eq!(f1.as_str(), f2.as_str(), "相同硬件应产生相同指纹");
}
_ => {
println!("警告: 跳过一致性测试");
}
}
}
}