keybox_core/
lib.rs

1use bs58;
2use itertools::Itertools;
3use sha3::{digest::*, Shake256};
4
5// Bitcoin style
6// 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
7
8pub struct KeyGenerator {
9    password_digest: String,
10    hasher: Shake256,
11}
12
13impl KeyGenerator {
14    pub fn new(password: String) -> Self {
15        let mut hasher = Shake256::default();
16        hasher.update(password);
17        let password_digest = hasher.finalize_boxed_reset(64);
18        Self {
19            password_digest: bs58::encode(password_digest.into_vec()).into_string(),
20            hasher,
21        }
22    }
23
24    pub fn gen(
25        &mut self,
26        nickname: String,
27        key_length: usize,
28        additional_characters: Vec<char>,
29    ) -> String {
30        let password_digest = &self.password_digest;
31        let source = key_length.to_string() + password_digest + &nickname;
32        self.hasher.update(source);
33        let result = self.hasher.finalize_boxed_reset(64);
34        let mut encoded = bs58::encode(result.into_vec()).into_string();
35        encoded.truncate(key_length);
36        let char_counts = encoded.chars().counts();
37        for (from, to) in char_counts
38            .into_iter()
39            .sorted()
40            .zip(additional_characters.into_iter())
41        {
42            encoded = encoded.replace(&String::from(from.0), &String::from(to));
43        }
44        encoded
45    }
46}
47
48#[cfg(test)]
49mod test {
50    use crate::*;
51    #[test]
52    fn test_hasher() {
53        let mut key_gen =
54            KeyGenerator::new(String::from("My very very long rememberable password!!!!!"));
55        let key_length = 16;
56        let key = key_gen.gen(
57            String::from("nickname of a service or something that I can remember"),
58            key_length,
59            vec!['!', '#'],
60        );
61
62        // source : `My very very long rememberable password!!!!!`
63        // shake256 (512 bits) : `521322bc4f5f53a5b37265d7c0df3c043ddc47e885f260c2903645043e9b8d6d8efaed4bd13b2cb86569e99cb1068c0daea40ea0a77bed6d1caa984a1455a22f`
64        // base58 : `2eB7XEwM9pFnRfSfy9NJXfzNn1HVmpUYVz61z8Cwd4paKw5cQVt35cihEMQ5heW5i1bn4cDy9hh6Yy1QZUwHCJog`
65
66        // source is key_length + password_digest + nickname
67        // source : `162eB7XEwM9pFnRfSfy9NJXfzNn1HVmpUYVz61z8Cwd4paKw5cQVt35cihEMQ5heW5i1bn4cDy9hh6Yy1QZUwHCJognickname of a service or something that I can remember`
68        // shake256 (512 bits) : `b00050118d5b49f85808d09ce9c5953fa3905b4964539bfe5c2e4de045f37b5723a39fb9dfd7c0355abe22a321867e13dbc370c4f9ca1b0f38b7fc15812f5f4e`
69        // base58 : `4X6Lbepf8fUz9Sa63aNHpGar35ptodWaAEPPxA7mFtHXzMxPcPNE6PdZ3rfmRuhb5w3h9aZbfag4dPcjiFHmRGay`
70
71        // key is first {key_length} characters of base58
72        // key : `4X6Lbepf8fUz9Sa6`
73        // if additional characters are specified, characters in key is replaced from small(in ascii code) character.
74        // numbers -> capital letters -> small letters
75        // key : `!X#Lbepf8fUz9Sa#`
76
77        assert_eq!(key, "!X#Lbepf8fUz9Sa#");
78    }
79}