serial_key/
lib.rs

1extern crate rand;
2extern crate regex;
3
4// based on http://www.brandonstaggs.com/2007/07/26/implementing-a-partial-serial-number-verification-system-in-delphi/
5use regex::Regex;
6use rand::Rng;
7
8#[derive(Debug, PartialEq)]
9pub enum Status {
10    Good,
11    Invalid,
12    Blacklisted,
13    Phony
14}
15
16fn get_key_byte(seed: &i64, a: i16, b: i16, c: i16) -> String {
17    let a_shift = a % 25;
18    let b_shift = b % 3;
19    let mut result;
20
21    if a_shift % 2 == 0 {
22        result = ((seed >> a_shift) & 0x000000FF) ^ ((seed >> b_shift) | c as i64);
23    } else {
24        result = ((seed >> a_shift) & 0x000000FF) ^ ((seed >> b_shift) & c as i64);
25    }
26
27    result = result & 0xFF;
28
29    // formats to uppercase hex, must be 2 chars with leading 0s
30    // https://doc.rust-lang.org/std/fmt/#width
31    format!("{:01$X}", result, 2)
32}
33
34fn get_checksum(s: &str) -> String {
35    let mut left = 0x0056; // 101
36    let mut right: u16 = 0x00AF; // 175
37
38    for ch in s.chars() {
39        right += ch as u16;
40        if right > 0x00FF {
41            right -= 0x00FF;
42        }
43
44        left += right;
45        if left > 0x00FF {
46            left -= 0x00FF;
47        }
48    }
49
50    let sum = (left << 8) + right;
51
52    // format as upperhex with leading 0s, must be 4 chars long
53    format!("{:01$X}", sum, 4)
54}
55
56pub fn make_key(seed: &i64, num_bytes: &i8, byte_shifts: &Vec<(i16, i16, i16)>) -> String {
57    let mut key_bytes: Vec<String> = vec![];
58
59    for i in 0..*num_bytes {
60        let index = i as usize;
61        let shift = byte_shifts[index];
62        key_bytes.push(get_key_byte(&seed, shift.0, shift.1, shift.2));
63    }
64
65    let mut result = format!("{:01$X}", seed, 8);
66    for byte in key_bytes {
67        result = format!("{}{}", result, byte);
68    }
69
70    result = format!("{}{}", result, get_checksum(&result[..]));
71
72    // keys should always be 20 digits, but use chunks rather than loop to be sure
73    let subs: Vec<&str> = result.split("").filter(|s| s.len() > 0).collect();
74    let mut key: Vec<String> = vec![];
75    for chunk in subs.chunks(4) {
76        key.push(chunk.join(""));
77    }
78
79    key.join("-")
80}
81
82pub fn check_key_checksum(key: &str, num_bytes: &i8) -> bool {
83    let s = key.replace("-", "").to_uppercase();
84    let length = s.len();
85
86    // length = 8 (seed) + 4 (checksum) + 2 * num_bytes
87    if length != (12 + (2 * num_bytes)) as usize {
88        return false;
89    }
90
91    let checksum = &s[length - 4..length];
92    let slice = &s[..length - 4];
93
94    checksum == get_checksum(&slice)
95}
96
97pub fn check_key(s: &str, blacklist: &Vec<String>, num_bytes: &i8, byte_shifts: &Vec<(i16, i16, i16)>, positions: Option<Vec<i16>>) -> Status {
98    if !check_key_checksum(s, num_bytes) {
99        return Status::Invalid;
100    }
101
102    if *num_bytes < 3 {
103        return Status::Invalid;
104    }
105
106    let key = s.replace("-", "").to_uppercase();
107    let seed: String = key.chars().take(8).collect();
108
109    for item in blacklist {
110        if seed == item.to_uppercase() {
111            return Status::Blacklisted;
112        }
113    }
114
115    let re = Regex::new(r"[A-F0-9]{8}").unwrap();
116    if !re.is_match(&seed) {
117        return Status::Phony;
118    }
119
120    let seed_num = match i64::from_str_radix(&seed[..], 16) {
121        Err(_) => return Status::Invalid,
122        Ok(s) => s
123    };
124
125    match positions {
126        Some(pos) => check_bytes(key, seed_num, byte_shifts, pos),
127        None => check_random_bytes(key, seed_num, num_bytes, byte_shifts)
128    }
129}
130
131/// Check specific bytes: this means you don't need to pass all the byte shifts in every check
132/// Also need to pass array of byte positions to check
133/// ie key: 4206-1A9F-FDD6-4D48-A1C8-ED15-FB36
134/// expected byte shifts: [(74, 252, 42), (42, 116, 226)]
135/// byte positions to check: [0, 4]
136/// this will check FD and A1 match (74, 252, 42) and (42, 116, 226) respectively
137fn check_bytes(key: String, seed_num: i64, byte_shifts: &Vec<(i16, i16, i16)>, positions: Vec<i16>) -> Status {
138    if positions.len() < 2 {
139        println!("Must check at least 2 bytes");
140        return Status::Invalid;
141    }
142
143    for (i, pos) in positions.into_iter().enumerate() {
144        let start = ((pos * 2) + 8) as usize;
145        let end = start + 2;
146
147        if end > key.len() {
148            return Status::Invalid;
149        }
150
151        let key_byte = &key[start..end];
152        let shifts = &byte_shifts[i as usize];
153
154        let byte = get_key_byte(&seed_num, shifts.0, shifts.1, shifts.2);
155        if key_byte != byte {
156            return Status::Phony;
157        }
158    }
159
160    Status::Good
161}
162
163/// Pick random bytes in the key and check that they're as expected
164/// This means you don't check the same keys in each run, but also means you need to pass all the
165/// byte shifts with every call (compared with `check_bytes` where not all the byte shifts are exposed with each call)
166fn check_random_bytes(key: String, seed_num: i64, num_bytes: &i8, byte_shifts: &Vec<(i16, i16, i16)>) -> Status {
167    let mut bytes_to_check = 3;
168    if *num_bytes > 5 {
169        bytes_to_check = num_bytes / 2;
170    }
171
172    // pick random unchecked entry in bytes array and check it
173    let mut checked: Vec<i8> = Vec::new();
174
175    for _ in 0..bytes_to_check {
176        let mut byte_to_check = rand::thread_rng().gen_range(0, num_bytes - 1);
177        while checked.contains(&byte_to_check) {
178            byte_to_check = rand::thread_rng().gen_range(0, num_bytes - 1);
179        }
180        checked.push(byte_to_check);
181
182        let start = ((byte_to_check * 2) + 8) as usize;
183        let end = start + 2;
184
185        if end > key.len() {
186            return Status::Invalid;
187        }
188
189
190        let key_byte = &key[start..end];
191        println!("num {:?}, shifts {:?}", &byte_to_check, &byte_shifts);
192
193        let shifts = &byte_shifts[byte_to_check as usize];
194
195        let byte = get_key_byte(&seed_num, shifts.0, shifts.1, shifts.2);
196        if key_byte != byte {
197            return Status::Phony;
198        }
199    }
200
201    Status::Good
202}
203
204#[cfg(test)]
205mod test {
206    #[test]
207    fn test_bytes() {
208        let seed = 0xA2791717;
209        assert_eq!(super::get_key_byte(&seed, 24, 3, 200), "7D");
210        assert_eq!(super::get_key_byte(&seed, 10, 0, 56), "7A");
211        assert_eq!(super::get_key_byte(&seed, 1, 2, 91), "CA");
212        assert_eq!(super::get_key_byte(&seed, 7, 1, 100), "2E");
213    }
214
215    #[test]
216    fn test_get_checksum() {
217        let key = "A279-1717-7D7A-CA2E-7154";
218        assert_eq!(super::get_checksum(key), "49DA");
219
220        let second_key = "3ABC-9099-E39D-4E65-E060";
221        assert_eq!(super::get_checksum(second_key), "82F0");
222    }
223
224    #[test]
225    fn test_make_key() {
226        let seed = 0x3abc9099;
227        let num_bytes = 4;
228        let byte_shifts = vec![(24, 3, 200), (10, 0, 56), (1, 2, 91), (7, 1, 100)];
229        assert_eq!("3ABC-9099-E39D-4E65-E060", super::make_key(&seed, &num_bytes, &byte_shifts));
230    }
231
232    #[test]
233    fn test_check_key() {
234        let key = "3ABC-9099-E39D-4E65-E060";
235        let blacklist = vec![];
236        let num_bytes = 4;
237        let byte_shifts = vec![(24, 3, 200), (10, 0, 56), (1, 2, 91), (7, 1, 100)];
238        assert_eq!(super::check_key(&key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Good);
239
240        let byte_shifts = vec![(24, 3, 200), (1, 2, 91)];
241        let positions = Option::Some(vec![0, 2]);
242        assert_eq!(super::check_key(&key, &blacklist, &num_bytes, &byte_shifts, positions), super::Status::Good);
243
244        let byte_shifts = vec![(1, 2, 91), (10, 0, 56)];
245        let positions = Option::Some(vec![2, 1]);
246        assert_eq!(super::check_key(&key, &blacklist, &num_bytes, &byte_shifts, positions), super::Status::Good);
247
248        let inconsistent_key = "3abC-9099-e39D-4E65-E060";
249        let byte_shifts = vec![(24, 3, 200), (10, 0, 56), (1, 2, 91), (7, 1, 100)];
250        assert_eq!(super::check_key(&inconsistent_key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Good);
251
252        let wrong_checksum = "3ABC-9099-E39D-4E65-E061";
253        assert_eq!(super::check_key(&wrong_checksum, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Invalid);
254
255        let second_fake_key = "3ABC-9099-E49D-4E65-E761";
256        assert_eq!(super::check_key(&second_fake_key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Phony);
257
258        let third_fake_key = "3ABC-9199-E39D-4E65-EB61";
259        assert_eq!(super::check_key(&third_fake_key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Phony);
260
261        let invalid_key = "BC-9099-E39D-4E65-E061";
262        assert_eq!(super::check_key(&invalid_key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Invalid);
263
264        let second_invalid_key = "3AXC-9099-E39D-4E65-E061";
265        assert_eq!(super::check_key(&second_invalid_key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Invalid);
266    }
267
268    #[test]
269    fn test_blacklist() {
270        let key = "3ABC-9099-E39D-4E65-E060";
271        let blacklist = vec!["3abc9099".to_string()];
272        let num_bytes = 4;
273        let byte_shifts = vec![(24, 3, 200), (10, 0, 56), (1, 2, 91), (7, 1, 100)];
274
275        assert_eq!(super::check_key(&key, &blacklist, &num_bytes, &byte_shifts, Option::None), super::Status::Blacklisted);
276
277        let byte_shifts = vec![(24, 3, 200), (1, 2, 91)];
278        let positions = Option::Some(vec![0, 2]);
279        assert_eq!(super::check_key(&key, &blacklist, &num_bytes, &byte_shifts, positions), super::Status::Blacklisted);
280    }
281}