win95_keygen/
lib.rs

1#![warn(missing_docs)]
2//! A lightweight library used to generate and validate Win95 keys
3//!
4//! Usage of the library is pretty simple, for more info check the [`generate`] and [`validate`] modules
5//!
6//! References:
7//! - Key generation algorithm: <https://gurney.dev/posts/mod7/>
8
9pub mod generate {
10    //! Generate new valid Win95 keys
11    //!
12    //! Each function in this module takes no arguments and returns a [`String`] containing a valid key
13    //!
14    //! # Example
15    //! ```
16    //! // Import the library
17    //! use win95_keygen::generate as keygen;
18    //!
19    //! fn main() {
20    //!     println!("Generating a valid Windows 95 CD activation key...");
21    //!
22    //!     // Generate a valid CD key and print it to the console
23    //!     let key: String = keygen::cd_normal();
24    //!     println!("Key: {}", key);
25    //! }
26    //! ```
27
28    use rand::Rng;
29
30    fn random_within_range(start: usize, end: usize) -> usize {
31        rand::thread_rng().gen_range(start..end)
32    }
33
34    fn seven_div_generator(length: usize) -> String {
35        let mut num_array: Vec<usize> = Vec::with_capacity(length);
36
37        for _ in 0..length - 1 {
38            num_array.push(random_within_range(0, 9));
39        }
40
41        num_array.push(random_within_range(1, 7));
42
43        while num_array.iter().sum::<usize>() % 7 != 0 {
44            num_array[random_within_range(0, length - 1)] = random_within_range(0, 9)
45        }
46
47        num_array
48            .iter()
49            .map(|num| num.to_string())
50            .collect::<String>()
51    }
52
53    /// Returns a random valid CD key
54    ///
55    /// This kind of key is in the following format: XXX-XXXXXXX
56    ///
57    /// The first segment can be anything between 000 and 999, except 333, 444, 555, 666, 777, 888 and 999
58    ///
59    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
60    pub fn cd_normal() -> String {
61        let first_digits: usize;
62
63        loop {
64            let temp_num = random_within_range(0, 998);
65            match temp_num {
66                333 | 444 | 555 | 666 | 777 | 888 => (),
67                _ => {
68                    first_digits = temp_num;
69                    break;
70                }
71            }
72        }
73
74        format!("{:0>3}-{}", first_digits, seven_div_generator(7))
75    }
76
77    /// Returns a random valid 11-digit long CD key (used for activating Office 97)
78    ///
79    /// This kind of key is in the following format: XXXX-XXXXXXX
80    ///
81    /// The first segment can be anything between 0000 and 9999, as long as the last digit is equal to the last digit + 1 or 2 (when the result is greater than 9, it "overflows" to 0 or 1)
82    ///
83    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
84    pub fn cd_long() -> String {
85        let first_digits: usize = random_within_range(0, 999);
86        let mut check_digit = first_digits % 10 + 1;
87        if check_digit > 9 {
88            check_digit = 0
89        }
90
91        format!(
92            "{:0>3}{}-{}",
93            first_digits,
94            check_digit.to_string(),
95            seven_div_generator(7)
96        )
97    }
98
99    /// Returns a random valid OEM key
100    ///
101    /// This kind of key is in the following format: XXXXX-OEM-0XXXXXX-XXXXX
102    ///
103    /// The first 3 digits can be anything from 001 to 366 and the following 2 anything from 95 to 02 (represents the day when the key was printed)
104    ///
105    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
106    ///
107    /// The last segment is valid as long as all the digits are numerical (so, anything from 00000 to 99999)
108    pub fn oem() -> String {
109        let date = random_within_range(1, 366);
110
111        let year = if rand::thread_rng().gen_bool((5.0 / 2.0) / 3.0) {
112            random_within_range(95, 99)
113        } else {
114            random_within_range(0, 2)
115        };
116
117        let seven_digits = seven_div_generator(6);
118
119        format!(
120            "{:0>3}{:0>2}-OEM-0{}-{:0>5}",
121            date,
122            year,
123            seven_digits,
124            random_within_range(0, 99999)
125        )
126    }
127}
128
129pub mod validate {
130    //! Check validity of Win95 keys
131    //!
132    //! # Example
133    //! ```
134    //! // This example generates a random Win95 key, then checks it's validity
135    //!
136    //! // Import the library
137    //! use win95_keygen::{generate as keygen, validate as keyvalid};
138    //!
139    //! fn main() {
140    //!     println!("Generating a valid Windows 95 OEM activation key...");
141    //!
142    //!     // Generate a valid OEM key and print it to the console
143    //!     let key: String = keygen::cd_normal();
144    //!     println!("Key: {}", key);
145    //!
146    //!     // Check if the key generated is valid
147    //!     println!("Checking key validity...");
148    //!     let is_valid = keyvalid::cd_normal(&key);
149    //!
150    //!     // If yes, log to console. Otherwise, panic
151    //!     if is_valid {
152    //!         println!("Key generated is valid!");
153    //!     } else {
154    //!         panic!("Generated erroneous key!");
155    //!     }
156    //! }
157    //! ```
158
159    fn numerical_overflow(number: usize, limit: usize) -> usize {
160        let remainder = number % limit;
161        if remainder >= limit {
162            remainder - limit
163        } else {
164            remainder
165        }
166    }
167
168    fn get_nth_digit_from_end(number: usize, index: usize) -> usize {
169        number / 10_usize.pow(index as u32) % 10
170    }
171
172    fn check_mod7(segment: usize, len: usize) -> bool {
173        let mut sum = 0;
174
175        for counter in 0..len {
176            sum += get_nth_digit_from_end(segment, counter)
177        }
178
179        sum % 7 == 0
180    }
181
182    /// Check if a [`String`] is a valid CD key
183    ///
184    /// This kind of key is in the following format: XXX-XXXXXXX
185    ///
186    /// The first segment can be anything between 000 and 999, except 333, 444, 555, 666, 777, 888 and 999
187    ///
188    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
189    pub fn cd_normal(key: &String) -> bool {
190        if key.len() == 11 {
191            if let (Ok(first_segment), Ok(last_segment)) =
192                (key[..3].parse::<usize>(), key[4..].parse::<usize>())
193            {
194                return match first_segment {
195                    333 | 444 | 555 | 666 | 777 | 888 | 999 => false,
196                    _ => true,
197                } && key.chars().nth(3).unwrap() == '-'
198                    && check_mod7(last_segment, 7);
199            }
200        }
201
202        false
203    }
204
205    /// Checks if a [`String`] is a valid 11-digit long CD key (used for activating Office 97)
206    ///
207    /// This kind of key is in the following format: XXXX-XXXXXXX
208    ///
209    /// The first segment can be anything between 0000 and 9999, as long as the last digit is equal to the last digit + 1 or 2 (when the result is greater than 9, it "overflows" to 0 or 1)
210    ///
211    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
212    pub fn cd_long(key: &String) -> bool {
213        if key.len() == 12 {
214            if let (Ok(first_segment), Ok(last_segment)) =
215                (key[..4].parse::<usize>(), key[5..].parse::<usize>())
216            {
217                let third_digit = get_nth_digit_from_end(first_segment, 1);
218                let fourth_digit = get_nth_digit_from_end(first_segment, 0);
219                return (fourth_digit == numerical_overflow(third_digit + 1, 10)
220                    || fourth_digit == numerical_overflow(third_digit + 2, 10))
221                    && key.chars().nth(4).unwrap() == '-'
222                    && check_mod7(last_segment, 7);
223            }
224        }
225
226        false
227    }
228
229    /// Checks if a [`String`] is a valid OEM key
230    ///
231    /// This kind of key is in the following format: XXXXX-OEM-0XXXXXX-XXXXX
232    ///
233    /// The first 3 digits can be anything from 001 to 366 and the following 2 anything from 95 to 02 (represents the day when the key was printed)
234    ///
235    /// The second segment can be anything, as long as the sum of all the digits is divisible with the number 7 (the so-called mod7 algorithm)
236    ///
237    /// The last segment is valid as long as all the digits are numerical (so, anything from 00000 to 99999)
238    pub fn oem(key: &String) -> bool {
239        if key.len() == 23 {
240            if let (Ok(date), Ok(year), Ok(numerical_segment), Ok(_)) = (
241                key[..3].parse::<usize>(),
242                key[3..5].parse::<usize>(),
243                key[10..17].parse::<usize>(),
244                key[18..].parse::<usize>(),
245            ) {
246                return date >= 1
247                    && date <= 366
248                    && ((year >= 95 && year <= 99) || year <= 2)
249                    && &key[5..10] == "-OEM-"
250                    && get_nth_digit_from_end(numerical_segment, 7) == 0
251                    && check_mod7(numerical_segment, 6);
252            }
253        }
254
255        false
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use crate::{generate as keygen, validate as keyvalid};
262
263    fn general_test(create_func: fn() -> String, check_func: fn(&String) -> bool, n_times: usize) {
264        for iteration in 0..n_times {
265            let key = create_func();
266
267            if !check_func(&key) {
268                panic!(
269                    "Test failed at {} out of {} iterations. The erroneous key was: {}",
270                    iteration, n_times, key
271                )
272            }
273        }
274    }
275
276    #[test]
277    fn cd_normal() {
278        general_test(keygen::cd_normal, keyvalid::cd_normal, 1000);
279    }
280
281    #[test]
282    fn cd_long() {
283        general_test(keygen::cd_long, keyvalid::cd_long, 1000);
284    }
285
286    #[test]
287    fn oem() {
288        general_test(keygen::oem, keyvalid::oem, 1000);
289    }
290}