machineid_crystal/
lib.rs

1//! Get an encrypted unique MachineID/HWID/UUID.
2//!
3//! This crate is inspired by .Net DeviceId
4//!
5//! You can add all the components you need without admin permissions.
6//!
7//! ```
8//! use machineid_rs::{IdBuilder, Encryption, HWIDComponent};
9//!
10//! // There are 3 different encryption types: MD5, SHA1 and SHA256.
11//! let mut builder = IdBuilder::new(Encryption::MD5);
12//!
13//! builder.add_component(HWIDComponent::SystemID).add_component(HWIDComponent::CPUCores);
14//!
15//! let hwid = builder.build(Some("mykey")).unwrap();
16
17#![allow(non_snake_case)]
18
19mod errors;
20mod linux;
21mod macos;
22mod utils;
23mod windows;
24
25use errors::HWIDError;
26#[cfg(target_os = "linux")]
27use linux::{get_disk_id, get_hwid, get_mac_address};
28#[cfg(target_os = "macos")]
29use macos::{get_disk_id, get_hwid, get_mac_address};
30#[cfg(target_os = "windows")]
31use windows::{get_disk_id, get_hwid, get_mac_address};
32
33use hmac::{Hmac, Mac};
34use md5::{Digest as Md5Digest, Md5};
35#[allow(unused_imports)]
36use sha1::{Digest as Sha1Digest, Sha1};
37#[allow(unused_imports)]
38use sha2::{Digest as Sha256Digest, Sha256};
39use sysinfo::{CpuRefreshKind, RefreshKind, System};
40use utils::file_token;
41
42/// The components that can be used to build the HWID.
43
44#[derive(Debug, PartialEq, Eq, Hash)]
45pub enum HWIDComponent {
46    /// System UUID
47    SystemID,
48    /// Number of CPU Cores
49    CPUCores,
50    /// Name of the OS
51    OSName,
52    /// Current Username
53    Username,
54    /// Host machine name
55    MachineName,
56    /// Mac Address
57    MacAddress,
58    /// CPU Vendor ID
59    CPUID,
60    /// The contents of a file
61    FileToken(&'static str),
62    /// UUID of the root disk
63    DriveSerial,
64}
65
66impl HWIDComponent {
67    pub fn to_string(&self) -> Result<String, HWIDError> {
68        use HWIDComponent::*;
69
70        match self {
71            SystemID => get_hwid(),
72            CPUCores => {
73                let sys = System::new_with_specifics(
74                    RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing()),
75                );
76                let cores = sys
77                    .physical_core_count()
78                    .ok_or(HWIDError::new("CPUCores", "Could not retrieve CPU Cores"))?;
79                Ok(cores.to_string())
80            }
81            OSName => {
82                let name = System::long_os_version()
83                    .ok_or(HWIDError::new("OSName", "Could not retrieve OS Name"))?;
84                Ok(name)
85            }
86            Username => Ok(whoami::username()),
87            MachineName => {
88                let name = System::host_name()
89                    .ok_or(HWIDError::new("HostName", "Could not retrieve Host Name"))?;
90                Ok(name)
91            }
92            MacAddress => get_mac_address(),
93            CPUID => {
94                let sys = System::new_with_specifics(
95                    RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing()),
96                );
97                let processor = sys
98                    .cpus()
99                    .first()
100                    .ok_or(HWIDError::new("CPUID", "Could not retrieve CPU ID"))?;
101                Ok(processor.vendor_id().to_string())
102            }
103            FileToken(filename) => file_token(filename),
104            DriveSerial => get_disk_id(),
105        }
106    }
107}
108
109/// The encryptions that can be used to build the HWID.
110pub enum Encryption {
111    MD5,
112    SHA256,
113    SHA1,
114}
115
116type HmacMd5 = Hmac<Md5>;
117type HmacSha1 = Hmac<Sha1>;
118type HmacSha256 = Hmac<Sha256>;
119
120impl Encryption {
121    fn generate_hash(&self, key: Option<&[u8]>, text: String) -> Result<String, HWIDError> {
122        match self {
123            Encryption::MD5 => {
124                if key.is_none() {
125                    let mut hasher = Md5::new();
126                    hasher.update(text.as_bytes());
127                    Ok(hex::encode(hasher.finalize()))
128                } else {
129                    let mut mac = HmacMd5::new_from_slice(key.unwrap())?;
130                    mac.update(text.as_bytes());
131                    let result = mac.finalize();
132                    Ok(hex::encode(result.into_bytes().as_slice()))
133                }
134            }
135            Encryption::SHA1 => {
136                if key.is_none() {
137                    let mut hasher = Sha1::new();
138                    hasher.update(text.as_bytes());
139                    Ok(hex::encode(hasher.finalize()))
140                } else {
141                    let mut mac = HmacSha1::new_from_slice(key.unwrap())?;
142                    mac.update(text.as_bytes());
143                    let result = mac.finalize();
144                    Ok(hex::encode(result.into_bytes().as_slice()))
145                }
146            }
147            Encryption::SHA256 => {
148                if key.is_none() {
149                    let mut hasher = Sha256::new();
150                    hasher.update(text.as_bytes());
151                    Ok(hex::encode(hasher.finalize()))
152                } else {
153                    let mut mac = HmacSha256::new_from_slice(key.unwrap())?;
154                    mac.update(text.as_bytes());
155                    let result = mac.finalize();
156                    Ok(hex::encode(result.into_bytes().as_slice()))
157                }
158            }
159        }
160    }
161}
162
163/// `IdBuilder` is the constructor for the HWID. It can be used with the 3 different options of the `Encryption` enum.
164pub struct IdBuilder {
165    parts: Vec<HWIDComponent>,
166    pub hash: Encryption,
167}
168
169impl IdBuilder {
170    /// Joins every part together and returns a `Result` that may be the hashed HWID or a `HWIDError`.
171    ///
172    /// # Errors
173    ///
174    /// Returns [`Err`] if there is an error while retrieving the component's strings.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// use machineid_rs::{IdBuilder, Encryption, HWIDComponent};
180    ///
181    /// let mut builder = IdBuilder::new(Encryption::MD5);
182    ///
183    /// builder.add_component(HWIDComponent::SystemID);
184    ///
185    ///
186    /// // Will panic if there is an error when the components return his values.
187    /// let key = builder.build(Some("mykey")).unwrap();
188    /// ```
189    pub fn build(&mut self, key: Option<&str>) -> Result<String, HWIDError> {
190        if self.parts.is_empty() {
191            panic!("You must add at least one element to make a machine id");
192        }
193        let final_string = self
194            .parts
195            .iter()
196            .map(|p| p.to_string())
197            .collect::<Result<String, HWIDError>>()?;
198
199        self.hash
200            .generate_hash(key.map(|k| k.as_bytes()), final_string)
201    }
202
203    /// Adds a component to the `IdBuilder` that will be hashed once you call the [`IdBuilder::build`] function.
204    ///
205    /// You can't add the same component twice.
206    ///
207    /// # Examples
208    ///
209    /// ```
210    /// use machineid_rs::{IdBuilder, Encryption, HWIDComponent};
211    ///
212    /// let mut builder = IdBuilder::new(Encryption::MD5);
213    ///
214    /// builder.add_component(HWIDComponent::SystemID);
215    /// ```
216    pub fn add_component(&mut self, component: HWIDComponent) -> &mut Self {
217        if !self.parts.contains(&component) {
218            self.parts.push(component);
219        }
220        self
221    }
222
223    /// Adds all possible components to the `IdBuilder`.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use machineid_rs::{IdBuilder, Encryption};
229    ///
230    /// let mut builder = IdBuilder::new(Encryption::MD5);
231    ///
232    /// builder.add_all();
233    /// ```
234    ///
235    /// It's the same as doing:
236    ///
237    /// ```
238    /// use machineid_rs::{IdBuilder, Encryption, HWIDComponent};
239    ///
240    /// let mut builder = IdBuilder::new(Encryption::MD5);
241    ///
242    /// builder
243    ///     .add_component(HWIDComponent::SystemID)
244    ///     .add_component(HWIDComponent::OSName)
245    ///     .add_component(HWIDComponent::CPUCores)
246    ///     .add_component(HWIDComponent::CPUID)
247    ///     .add_component(HWIDComponent::DriveSerial)
248    ///     .add_component(HWIDComponent::MacAddress)
249    ///     .add_component(HWIDComponent::Username)
250    ///     .add_component(HWIDComponent::MachineName);
251    ///
252    /// ```
253    pub fn add_all(&mut self) -> &mut Self {
254        self.add_component(HWIDComponent::SystemID)
255            .add_component(HWIDComponent::OSName)
256            .add_component(HWIDComponent::CPUCores)
257            .add_component(HWIDComponent::CPUID)
258            .add_component(HWIDComponent::DriveSerial)
259            .add_component(HWIDComponent::MacAddress)
260            .add_component(HWIDComponent::Username)
261            .add_component(HWIDComponent::MachineName)
262    }
263
264    /// Makes a new IdBuilder with the selected Encryption
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// use machineid_rs::{IdBuilder, Encryption};
270    ///
271    /// let mut builder = IdBuilder::new(Encryption::MD5);
272    /// ```
273    pub fn new(hash: Encryption) -> Self {
274        IdBuilder {
275            parts: vec![],
276            hash,
277        }
278    }
279}
280
281#[cfg(test)]
282mod test {
283    use super::*;
284    use std::env;
285
286    #[test]
287    fn every_option_sha256() {
288        let mut builder = IdBuilder::new(Encryption::SHA256);
289        builder
290            .add_component(HWIDComponent::SystemID)
291            .add_component(HWIDComponent::OSName)
292            .add_component(HWIDComponent::CPUCores)
293            .add_component(HWIDComponent::CPUID)
294            .add_component(HWIDComponent::DriveSerial)
295            .add_component(HWIDComponent::MacAddress)
296            .add_component(HWIDComponent::FileToken("test.txt"))
297            .add_component(HWIDComponent::Username)
298            .add_component(HWIDComponent::MachineName);
299        let hash = builder.build(None).unwrap();
300        if let Ok(expected) = env::var("SHA256_MACHINEID_HASH") {
301            assert_eq!(expected, hash);
302        }
303    }
304
305    #[test]
306    fn every_option_sha1() {
307        let mut builder = IdBuilder::new(Encryption::SHA1);
308        builder
309            .add_component(HWIDComponent::SystemID)
310            .add_component(HWIDComponent::OSName)
311            .add_component(HWIDComponent::CPUCores)
312            .add_component(HWIDComponent::CPUID)
313            .add_component(HWIDComponent::DriveSerial)
314            .add_component(HWIDComponent::MacAddress)
315            .add_component(HWIDComponent::FileToken("test.txt"))
316            .add_component(HWIDComponent::Username)
317            .add_component(HWIDComponent::MachineName);
318        let hash = builder.build(None).unwrap();
319        if let Ok(expected) = env::var("SHA1_MACHINEID_HASH") {
320            assert_eq!(expected, hash);
321        }
322    }
323
324    #[test]
325    fn every_option_md5() {
326        let mut builder = IdBuilder::new(Encryption::MD5);
327        builder
328            .add_component(HWIDComponent::SystemID)
329            .add_component(HWIDComponent::OSName)
330            .add_component(HWIDComponent::CPUCores)
331            .add_component(HWIDComponent::CPUID)
332            .add_component(HWIDComponent::DriveSerial)
333            .add_component(HWIDComponent::MacAddress)
334            .add_component(HWIDComponent::FileToken("test.txt"))
335            .add_component(HWIDComponent::Username)
336            .add_component(HWIDComponent::MachineName);
337        let hash = builder.build(None).unwrap();
338        if let Ok(expected) = env::var("MD5_MACHINEID_HASH") {
339            assert_eq!(expected, hash);
340        }
341    }
342
343    #[test]
344    fn every_option_sha256_hmac() {
345        let mut builder = IdBuilder::new(Encryption::SHA256);
346        builder
347            .add_component(HWIDComponent::SystemID)
348            .add_component(HWIDComponent::OSName)
349            .add_component(HWIDComponent::CPUCores)
350            .add_component(HWIDComponent::CPUID)
351            .add_component(HWIDComponent::DriveSerial)
352            .add_component(HWIDComponent::MacAddress)
353            .add_component(HWIDComponent::FileToken("test.txt"))
354            .add_component(HWIDComponent::Username)
355            .add_component(HWIDComponent::MachineName);
356        let hash = builder.build(Some("mykey")).unwrap();
357        if let Ok(expected) = env::var("SHA256_MACHINEID_HASH") {
358            assert_eq!(expected, hash);
359        }
360    }
361
362    #[test]
363    fn every_option_sha1_hmac() {
364        let mut builder = IdBuilder::new(Encryption::SHA1);
365        builder
366            .add_component(HWIDComponent::SystemID)
367            .add_component(HWIDComponent::OSName)
368            .add_component(HWIDComponent::CPUCores)
369            .add_component(HWIDComponent::CPUID)
370            .add_component(HWIDComponent::DriveSerial)
371            .add_component(HWIDComponent::MacAddress)
372            .add_component(HWIDComponent::FileToken("test.txt"))
373            .add_component(HWIDComponent::Username)
374            .add_component(HWIDComponent::MachineName);
375        let hash = builder.build(Some("mykey")).unwrap();
376        if let Ok(expected) = env::var("SHA1_MACHINEID_HASH") {
377            assert_eq!(expected, hash);
378        }
379    }
380
381    #[test]
382    fn every_option_md5_hmac() {
383        let mut builder = IdBuilder::new(Encryption::MD5);
384        builder
385            .add_component(HWIDComponent::SystemID)
386            .add_component(HWIDComponent::OSName)
387            .add_component(HWIDComponent::CPUCores)
388            .add_component(HWIDComponent::CPUID)
389            .add_component(HWIDComponent::DriveSerial)
390            .add_component(HWIDComponent::MacAddress)
391            //.add_component(HWIDComponent::FileToken("test.txt"))
392            .add_component(HWIDComponent::Username)
393            .add_component(HWIDComponent::MachineName);
394        let hash = builder.build(Some("mykey")).unwrap();
395        if let Ok(expected) = env::var("MD5_MACHINEID_HASH") {
396            assert_eq!(expected, hash);
397        }
398    }
399}