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}