Skip to main content

credential_crypto/
lib.rs

1use anyhow::anyhow;
2use ring::aead::{AES_256_GCM, Aad, LessSafeKey, Nonce, UnboundKey};
3use ring::rand::{SecureRandom, SystemRandom};
4use sha2::{Digest, Sha256};
5
6pub struct CredentialCrypto {
7    pub key: String,
8}
9
10pub trait CredentialCryptoProvider {
11    fn new(key: String) -> CredentialCrypto;
12    fn generate_salt(&self) -> Result<String, anyhow::Error>;
13    fn encrypt(&self, salt: &str, plaintext: &str) -> Result<String, anyhow::Error>;
14    fn decrypt(&self, salt: &str, password: &str) -> Result<String, anyhow::Error>;
15}
16
17impl CredentialCryptoProvider for CredentialCrypto {
18    fn new(key: String) -> CredentialCrypto {
19        CredentialCrypto { key } // machine_no  card_no
20    }
21
22    fn generate_salt(&self) -> Result<String, anyhow::Error> {
23        let mut nonce_bytes = [0u8; 12];
24
25        SystemRandom::new()
26            .fill(&mut nonce_bytes)
27            .map_err(|err| anyhow!("Cannot generate random nonce {err}"))?;
28
29        Ok(hex::encode(nonce_bytes))
30    }
31
32    fn encrypt(&self, salt: &str, plaintext: &str) -> Result<String, anyhow::Error> {
33        if plaintext.is_empty() {
34            return Err(anyhow!("password is empty"));
35        }
36
37        let nonce_bytes: [u8; 12] = hex::decode(salt)?
38            .try_into()
39            .map_err(|err| anyhow!("nonce must be exactly 12 bytes {:#?}", err))?;
40
41        let nonce = Nonce::assume_unique_for_key(nonce_bytes);
42
43        let mut hasher = Sha256::new();
44        hasher.update(self.key.as_bytes());
45        let key_bytes = hasher.finalize();
46
47        let unbound_key = match UnboundKey::new(&AES_256_GCM, &key_bytes) {
48            Ok(key) => key,
49            Err(err) => {
50                return Err(anyhow!("Key derivation failed {err}"));
51            }
52        };
53        let key = LessSafeKey::new(unbound_key);
54
55        // 加密
56        let mut in_out = plaintext.as_bytes().to_vec();
57        if let Err(err) = key.seal_in_place_append_tag(nonce, Aad::empty(), &mut in_out) {
58            return Err(anyhow!("encryption failed {err}"));
59        }
60
61        Ok(hex::encode(in_out))
62    }
63
64    fn decrypt(&self, salt: &str, password: &str) -> Result<String, anyhow::Error> {
65        if password.is_empty() {
66            return Err(anyhow!("password is empty"));
67        }
68
69        let nonce_bytes: [u8; 12] = hex::decode(salt)?
70            .try_into()
71            .map_err(|err| anyhow!("nonce must be exactly 12 bytes {:#?}", err))?;
72
73        let nonce = Nonce::assume_unique_for_key(nonce_bytes);
74
75        let mut hasher = Sha256::new();
76        hasher.update(self.key.as_bytes());
77        let key_bytes = hasher.finalize();
78
79        let unbound_key = match UnboundKey::new(&AES_256_GCM, &key_bytes) {
80            Ok(v) => v,
81            Err(err) => {
82                return Err(anyhow!("Key derivation failed {err}"));
83            }
84        };
85        let key = LessSafeKey::new(unbound_key);
86
87        let mut in_out = hex::decode(&password)?;
88
89        // 執行解密
90        let plaintext_slice = match key.open_in_place(nonce, Aad::empty(), &mut in_out) {
91            Ok(v) => v,
92            Err(err) => {
93                return Err(anyhow!("decryption failed {err}"));
94            }
95        };
96
97        let plaintext = String::from_utf8_lossy(plaintext_slice).into_owned();
98
99        Ok(plaintext)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use crate::{CredentialCrypto, CredentialCryptoProvider};
106
107    #[test]
108    fn generate_payload() {
109        let client = CredentialCrypto::new("1000000002".to_string());
110
111        let salt = "b3d6f762773d4ef7d1cbdfda";
112        let password = "21c95597d3829efd728eb1084e2e22b2350a199b8c14";
113        let p = client.decrypt(salt, password).unwrap();
114
115        let w = client.encrypt(salt, "888888").unwrap();
116
117        println!("{}, {}, {}", p, w, password == w);
118    }
119}