short_crypt/
lib.rs

1/*!
2# ShortCrypt
3
4ShortCrypt is a very simple encryption library, which aims to encrypt any data into something random at first glance.
5Even if these data are similar, the ciphers are still pretty different.
6The most important thing is that a cipher is only **4 bits** larger than its plaintext so that it is suitable for data used in a URL or a QR Code. Besides these, it is also an ideal candidate for serial number generation.
7
8## Examples
9
10`encrypt` method can create a `Cipher` tuple separating into a **base** and a **body** of the cipher. The size of a **base** is 4 bits, and the size of a **body** is equal to the plaintext.
11
12```rust
13extern crate short_crypt;
14
15use short_crypt::ShortCrypt;
16
17let sc = ShortCrypt::new("magickey");
18
19assert_eq!((8, [216, 78, 214, 199, 157, 190, 78, 250].to_vec()), sc.encrypt("articles"));
20assert_eq!("articles".as_bytes().to_vec(), sc.decrypt(&(8, vec![216, 78, 214, 199, 157, 190, 78, 250])).unwrap());
21
22```
23
24`encrypt_to_url_component` method is common for encryption in most cases. After ShortCrypt `encrypt` a plaintext, it encodes the cipher into a random-like string based on Base64-URL format so that it can be concatenated with URLs.
25
26```rust
27extern crate short_crypt;
28
29use short_crypt::ShortCrypt;
30
31let sc = ShortCrypt::new("magickey");
32
33assert_eq!("2E87Wx52-Tvo", sc.encrypt_to_url_component("articles"));
34assert_eq!("articles".as_bytes().to_vec(), sc.decrypt_url_component("2E87Wx52-Tvo").unwrap());
35```
36
37`encrypt_to_qr_code_alphanumeric` method is usually used for encrypting something into a QR code. After ShortCrypt `encrypt` a plaintext, it encodes the cipher into a random-like string based on Base32 format so that it can be inserted into a QR code with the compatibility with alphanumeric mode.
38
39```rust
40extern crate short_crypt;
41
42use short_crypt::ShortCrypt;
43
44let sc = ShortCrypt::new("magickey");
45
46assert_eq!("3BHNNR45XZH8PU", sc.encrypt_to_qr_code_alphanumeric("articles"));
47assert_eq!("articles".as_bytes().to_vec(), sc.decrypt_qr_code_alphanumeric("3BHNNR45XZH8PU").unwrap());
48```
49
50Besides, in order to reduce the copy times of strings, you can also use `encrypt_to_url_component_and_push_to_string`, `encrypt_to_qr_code_alphanumeric_and_push_to_string` methods to use the same memory space.
51
52```rust
53extern crate short_crypt;
54
55use short_crypt::ShortCrypt;
56
57let sc = ShortCrypt::new("magickey");
58
59let url = "https://magiclen.org/".to_string();
60
61assert_eq!("https://magiclen.org/2E87Wx52-Tvo", sc.encrypt_to_url_component_and_push_to_string("articles", url));
62
63let url = "https://magiclen.org/".to_string();
64
65assert_eq!("https://magiclen.org/3BHNNR45XZH8PU", sc.encrypt_to_qr_code_alphanumeric_and_push_to_string("articles", url));
66```
67*/
68
69#![no_std]
70
71#[macro_use]
72extern crate alloc;
73
74pub extern crate base32;
75pub extern crate base64_url;
76
77use alloc::{string::String, vec::Vec};
78use core::fmt::{self, Debug, Formatter};
79
80pub use base64_url::base64;
81use crc_any::{CRCu64, CRCu8};
82
83/// A tuple. The first `u8` value is the **base** which only takes 4 bits. The second `Vec<u8>` value is the **body** whose size is equal to the plaintext. You can use your own algorithms to combine them together, or just use `encrypt_to_url_component` or `encrypt_to_qr_code_alphanumeric` to output them as a random-like string.
84pub type Cipher = (u8, Vec<u8>);
85
86pub struct ShortCrypt {
87    hashed_key:  [u8; 8],
88    key_sum_rev: u64,
89}
90
91impl Debug for ShortCrypt {
92    #[inline]
93    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
94        debug_helper::impl_debug_for_struct!(ShortCrypt, f, self, let .hashed_key = self.hashed_key.as_ref(), (.key_sum_rev, "{:X}", self.key_sum_rev));
95    }
96}
97
98macro_rules! u8_to_string_64 {
99    ($i:expr) => {
100        if $i < 10 {
101            $i + b'0'
102        } else if (10..36).contains(&$i) {
103            $i - 10 + b'A'
104        } else if (36..62).contains(&$i) {
105            $i - 36 + b'a'
106        } else if $i == 62 {
107            b'-'
108        } else {
109            b'_'
110        }
111    };
112}
113
114macro_rules! string_64_to_u8 {
115    ($c:expr) => {
116        if $c >= b'0' && $c <= b'9' {
117            $c - b'0'
118        } else if $c >= b'A' && $c <= b'Z' {
119            $c + 10 - b'A'
120        } else if $c >= b'a' && $c <= b'z' {
121            $c + 36 - b'a'
122        } else if $c == b'-' {
123            62
124        } else {
125            63
126        }
127    };
128}
129
130macro_rules! u8_to_string_32 {
131    ($i:expr) => {
132        if $i < 10 {
133            $i + b'0'
134        } else {
135            $i - 10 + b'A'
136        }
137    };
138}
139
140macro_rules! string_32_to_u8 {
141    ($c:expr) => {
142        if $c >= b'0' && $c <= b'9' {
143            $c - b'0'
144        } else {
145            $c + 10 - b'A'
146        }
147    };
148}
149
150impl ShortCrypt {
151    /// Create a new ShortCrypt instance.
152    pub fn new<S: AsRef<str>>(key: S) -> ShortCrypt {
153        let key_bytes = key.as_ref().as_bytes();
154
155        let hashed_key = {
156            let mut hasher = CRCu64::crc64we();
157
158            hasher.digest(key_bytes);
159
160            hasher.get_crc().to_be_bytes()
161        };
162
163        let mut key_sum = 0u64;
164
165        for n in key_bytes.iter().copied() {
166            key_sum = key_sum.wrapping_add(u64::from(n));
167        }
168
169        let key_sum_rev = key_sum.reverse_bits();
170
171        ShortCrypt {
172            hashed_key,
173            key_sum_rev,
174        }
175    }
176
177    pub fn encrypt<T: ?Sized + AsRef<[u8]>>(&self, plaintext: &T) -> Cipher {
178        let data = plaintext.as_ref();
179
180        let len = data.len();
181
182        let hashed_value = {
183            let mut crc8 = CRCu8::crc8cdma2000();
184
185            crc8.digest(data);
186            crc8.get_crc()
187        };
188
189        let base = hashed_value % 32;
190
191        let mut encrypted = Vec::with_capacity(len);
192
193        let mut m = base;
194        let mut sum = u64::from(base);
195
196        for (i, d) in data.iter().enumerate() {
197            let offset = self.hashed_key[i % 8] ^ base;
198
199            let v = d ^ offset;
200
201            encrypted.push(v);
202
203            m ^= v;
204            sum = sum.wrapping_add(u64::from(v));
205        }
206
207        let sum: [u8; 8] = sum.to_be_bytes();
208
209        let hashed_array: [u8; 8] = {
210            let mut hasher = CRCu64::crc64we();
211
212            hasher.digest(&[m]);
213            hasher.digest(&sum);
214
215            hasher.get_crc().to_be_bytes()
216        };
217
218        let mut path = Vec::with_capacity(len);
219
220        for i in 0..len {
221            let index = i % 8;
222            path.push((hashed_array[index] ^ self.hashed_key[index]) as usize % len);
223        }
224
225        for (i, p) in path.iter().copied().enumerate() {
226            if i == p {
227                continue;
228            }
229
230            encrypted.swap(i, p);
231        }
232
233        (base, encrypted)
234    }
235
236    pub fn decrypt(&self, data: &Cipher) -> Result<Vec<u8>, &'static str> {
237        let base = data.0;
238        let data = &data.1;
239
240        if base > 31 {
241            return Err("The base is not correct.");
242        }
243
244        let len = data.len();
245
246        let mut decrypted = Vec::with_capacity(len);
247
248        self.decrypt_inner(base, data, &mut decrypted);
249
250        Ok(decrypted)
251    }
252
253    fn decrypt_inner(&self, base: u8, data: &[u8], output: &mut Vec<u8>) {
254        let len = data.len();
255
256        let mut m = base;
257        let mut sum = u64::from(base);
258
259        for v in data.iter().copied() {
260            m ^= v;
261            sum = sum.wrapping_add(u64::from(v));
262        }
263
264        let sum: [u8; 8] = sum.to_be_bytes();
265
266        let hashed_array: [u8; 8] = {
267            let mut hasher = CRCu64::crc64we();
268
269            hasher.digest(&[m]);
270            hasher.digest(&sum);
271
272            hasher.get_crc().to_be_bytes()
273        };
274
275        let mut path = Vec::with_capacity(len);
276
277        for i in 0..len {
278            let index = i % 8;
279            path.push((hashed_array[index] ^ self.hashed_key[index]) as usize % len);
280        }
281
282        let mut data = data.to_vec();
283
284        for (i, p) in path.iter().copied().enumerate().rev() {
285            if i == p {
286                continue;
287            }
288
289            data.swap(i, p);
290        }
291
292        for (i, d) in data.iter().enumerate() {
293            let offset = self.hashed_key[i % 8] ^ base;
294
295            output.push(d ^ offset);
296        }
297    }
298
299    pub fn encrypt_to_url_component<T: ?Sized + AsRef<[u8]>>(&self, data: &T) -> String {
300        let (base, encrypted) = self.encrypt(data);
301
302        let base = u8_to_string_64!(base);
303
304        let base_char = base as char;
305
306        let mut result = String::with_capacity(1 + ((encrypted.len() * 4 + 2) / 3));
307
308        base64_url::encode_to_string(&encrypted, &mut result);
309
310        let mut sum = u64::from(base);
311
312        for n in result.bytes() {
313            sum = sum.wrapping_add(u64::from(n));
314        }
315
316        let base_index = ((self.key_sum_rev ^ sum) % ((result.len() + 1) as u64)) as usize;
317
318        result.insert(base_index, base_char);
319
320        result
321    }
322
323    pub fn encrypt_to_url_component_and_push_to_string<T: ?Sized + AsRef<[u8]>, S: Into<String>>(
324        &self,
325        data: &T,
326        output: S,
327    ) -> String {
328        let (base, encrypted) = self.encrypt(data);
329
330        let base = u8_to_string_64!(base);
331
332        let base_char = base as char;
333
334        let mut output = output.into();
335
336        let original_len = output.len();
337
338        base64_url::encode_to_string(&encrypted, &mut output);
339
340        let mut sum = u64::from(base);
341
342        for n in output.bytes().skip(original_len) {
343            sum = sum.wrapping_add(u64::from(n));
344        }
345
346        let base_index =
347            ((self.key_sum_rev ^ sum) % ((output.len() - original_len + 1) as u64)) as usize;
348
349        output.insert(original_len + base_index, base_char);
350
351        output
352    }
353
354    pub fn decrypt_url_component<S: AsRef<str>>(
355        &self,
356        url_component: S,
357    ) -> Result<Vec<u8>, &'static str> {
358        let bytes = url_component.as_ref().as_bytes();
359        let len = bytes.len();
360
361        if len < 1 {
362            return Err("The URL component is incorrect.");
363        }
364
365        let base_index = {
366            let mut sum = 0u64;
367
368            for n in bytes.iter().copied() {
369                sum = sum.wrapping_add(u64::from(n));
370            }
371
372            ((self.key_sum_rev ^ sum) % (len as u64)) as usize
373        };
374
375        let base = string_64_to_u8!(bytes[base_index]);
376
377        if base > 31 {
378            return Err("The URL component is incorrect.");
379        }
380
381        let encrypted_base64_url = [&bytes[..base_index], &bytes[(base_index + 1)..]].concat();
382
383        let encrypted = base64_url::decode(&encrypted_base64_url)
384            .map_err(|_| "The URL component is incorrect.")?;
385
386        self.decrypt(&(base, encrypted))
387    }
388
389    pub fn decrypt_url_component_and_push_to_vec<S: AsRef<str>>(
390        &self,
391        url_component: S,
392        mut output: Vec<u8>,
393    ) -> Result<Vec<u8>, &'static str> {
394        let bytes = url_component.as_ref().as_bytes();
395        let len = bytes.len();
396
397        if len < 1 {
398            return Err("The URL component is incorrect.");
399        }
400
401        let base_index = {
402            let mut sum = 0u64;
403
404            for n in bytes.iter().copied() {
405                sum = sum.wrapping_add(u64::from(n));
406            }
407
408            ((self.key_sum_rev ^ sum) % (len as u64)) as usize
409        };
410
411        let base = string_64_to_u8!(bytes[base_index]);
412
413        if base > 31 {
414            return Err("The URL component is incorrect.");
415        }
416
417        let encrypted_base64_url = [&bytes[..base_index], &bytes[(base_index + 1)..]].concat();
418
419        let encrypted = base64_url::decode(&encrypted_base64_url)
420            .map_err(|_| "The URL component is incorrect.")?;
421
422        let len = encrypted.len();
423
424        output.reserve(len);
425
426        self.decrypt_inner(base, &encrypted, &mut output);
427
428        Ok(output)
429    }
430
431    pub fn encrypt_to_qr_code_alphanumeric<T: ?Sized + AsRef<[u8]>>(&self, data: &T) -> String {
432        let (base, encrypted) = self.encrypt(data);
433
434        let base = u8_to_string_32!(base);
435
436        let base_char = base as char;
437
438        let mut result = String::with_capacity(1 + ((encrypted.len() * 8 + 4) / 5));
439
440        result.push_str(&base32::encode(
441            base32::Alphabet::RFC4648 {
442                padding: false
443            },
444            &encrypted,
445        ));
446
447        let mut sum = u64::from(base);
448
449        for n in result.bytes() {
450            sum = sum.wrapping_add(u64::from(n));
451        }
452
453        let base_index = ((self.key_sum_rev ^ sum) % ((result.len() + 1) as u64)) as usize;
454
455        result.insert(base_index, base_char);
456
457        result
458    }
459
460    pub fn encrypt_to_qr_code_alphanumeric_and_push_to_string<
461        T: ?Sized + AsRef<[u8]>,
462        S: Into<String>,
463    >(
464        &self,
465        data: &T,
466        output: S,
467    ) -> String {
468        let (base, encrypted) = self.encrypt(data);
469
470        let base = u8_to_string_32!(base);
471
472        let base_char = base as char;
473
474        let mut output = output.into();
475
476        let original_len = output.len();
477
478        output.push_str(&base32::encode(
479            base32::Alphabet::RFC4648 {
480                padding: false
481            },
482            &encrypted,
483        ));
484
485        let mut sum = u64::from(base);
486
487        for n in output.bytes().skip(original_len) {
488            sum = sum.wrapping_add(u64::from(n));
489        }
490
491        let base_index =
492            ((self.key_sum_rev ^ sum) % ((output.len() - original_len + 1) as u64)) as usize;
493
494        output.insert(original_len + base_index, base_char);
495
496        output
497    }
498
499    pub fn decrypt_qr_code_alphanumeric<S: AsRef<str>>(
500        &self,
501        qr_code_alphanumeric: S,
502    ) -> Result<Vec<u8>, &'static str> {
503        let bytes = qr_code_alphanumeric.as_ref().as_bytes();
504        let len = bytes.len();
505
506        if len < 1 {
507            return Err("The QR code alphanumeric text is incorrect.");
508        }
509
510        let base_index = {
511            let mut sum = 0u64;
512
513            for n in bytes.iter().copied() {
514                sum = sum.wrapping_add(u64::from(n));
515            }
516
517            ((self.key_sum_rev ^ sum) % (len as u64)) as usize
518        };
519
520        let base = string_32_to_u8!(bytes[base_index]);
521
522        if base > 31 {
523            return Err("The QR code alphanumeric text is incorrect.");
524        }
525
526        let encrypted_base32 =
527            String::from_utf8([&bytes[..base_index], &bytes[(base_index + 1)..]].concat())
528                .map_err(|_| "The QR code alphanumeric text is incorrect.")?;
529
530        let encrypted = match base32::decode(
531            base32::Alphabet::RFC4648 {
532                padding: false
533            },
534            &encrypted_base32,
535        ) {
536            Some(t) => t,
537            None => return Err("The QR code alphanumeric text is incorrect."),
538        };
539
540        self.decrypt(&(base, encrypted))
541    }
542
543    pub fn decrypt_qr_code_alphanumeric_and_push_to_vec<S: AsRef<str>>(
544        &self,
545        qr_code_alphanumeric: S,
546        mut output: Vec<u8>,
547    ) -> Result<Vec<u8>, &'static str> {
548        let bytes = qr_code_alphanumeric.as_ref().as_bytes();
549        let len = bytes.len();
550
551        if len < 1 {
552            return Err("The QR code alphanumeric text is incorrect.");
553        }
554
555        let base_index = {
556            let mut sum = 0u64;
557
558            for n in bytes.iter().copied() {
559                sum = sum.wrapping_add(u64::from(n));
560            }
561
562            ((self.key_sum_rev ^ sum) % (len as u64)) as usize
563        };
564
565        let base = string_32_to_u8!(bytes[base_index]);
566
567        if base > 31 {
568            return Err("The QR code alphanumeric text is incorrect.");
569        }
570
571        let encrypted_base32 =
572            String::from_utf8([&bytes[..base_index], &bytes[(base_index + 1)..]].concat())
573                .map_err(|_| "The QR code alphanumeric text is incorrect.")?;
574
575        let encrypted = match base32::decode(
576            base32::Alphabet::RFC4648 {
577                padding: false
578            },
579            &encrypted_base32,
580        ) {
581            Some(t) => t,
582            None => return Err("The QR code alphanumeric text is incorrect."),
583        };
584
585        let len = encrypted.len();
586
587        output.reserve(len);
588
589        self.decrypt_inner(base, &encrypted, &mut output);
590
591        Ok(output)
592    }
593}