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