Skip to main content

pingap_util/
crypto.rs

1// Copyright 2024-2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::Error;
16use super::{base64_decode, base64_encode};
17use aes_gcm_siv::{
18    Aes256GcmSiv, Nonce,
19    aead::{Aead, KeyInit},
20};
21use std::sync::LazyLock;
22
23type Result<T, E = Error> = std::result::Result<T, E>;
24
25// TODO update aes_gcm_siv to use the new Nonce type
26
27#[allow(deprecated)]
28static PINGAP_NONCE: LazyLock<&Nonce> =
29    LazyLock::new(|| Nonce::from_slice(b"pingap nonce"));
30
31/// Generates a 32-byte key from the input string.
32/// If the input is longer than 32 bytes, it's truncated.
33/// If shorter, it's padded with zeros.
34///
35/// # Arguments
36/// * `key` - The input string to generate the key from
37///
38/// # Returns
39/// A Vec<u8> containing the 32-byte key
40fn generate_key(key: &str) -> Vec<u8> {
41    let key_size = 32;
42    let buf = key.as_bytes();
43    let pos = buf.len();
44    if pos > key_size {
45        return buf[0..key_size].to_vec();
46    }
47    if pos == key_size {
48        return buf.to_vec();
49    }
50    let mut block: Vec<u8> = vec![0; key_size];
51    block[..pos].copy_from_slice(buf);
52    block
53}
54
55/// Encrypts data using AES-256-GCM-SIV with a static nonce.
56///
57/// # Arguments
58/// * `key` - The encryption key
59/// * `data` - The plaintext data to encrypt
60///
61/// # Returns
62/// * `Ok(String)` - Base64 encoded ciphertext
63/// * `Err(Error)` - If encryption fails
64pub fn aes_encrypt(key: &str, data: &str) -> Result<String> {
65    let cipher =
66        Aes256GcmSiv::new_from_slice(&generate_key(key)).map_err(|e| {
67            Error::Invalid {
68                message: e.to_string(),
69            }
70        })?;
71    let cipher_text =
72        cipher
73            .encrypt(&PINGAP_NONCE, data.as_bytes())
74            .map_err(|e| Error::Aes {
75                message: e.to_string(),
76            })?;
77    Ok(base64_encode(&cipher_text))
78}
79
80/// Decrypts AES-256-GCM-SIV encrypted data using a static nonce.
81///
82/// # Arguments
83/// * `key` - The decryption key
84/// * `data` - Base64 encoded ciphertext to decrypt
85///
86/// # Returns
87/// * `Ok(String)` - Decrypted plaintext
88/// * `Err(Error)` - If decryption or base64 decoding fails
89pub fn aes_decrypt(key: &str, data: &str) -> Result<String> {
90    let cipher =
91        Aes256GcmSiv::new_from_slice(&generate_key(key)).map_err(|e| {
92            Error::Invalid {
93                message: e.to_string(),
94            }
95        })?;
96    let cipher_text =
97        base64_decode(data).map_err(|e| Error::Base64Decode { source: e })?;
98    let plaintext = cipher
99        .decrypt(&PINGAP_NONCE, cipher_text.as_ref())
100        .map_err(|e| Error::Aes {
101            message: e.to_string(),
102        })?;
103
104    let data = std::str::from_utf8(&plaintext).map_err(|e| Error::Invalid {
105        message: e.to_string(),
106    })?;
107    Ok(data.to_string())
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use pretty_assertions::assert_eq;
114
115    #[test]
116    fn test_aes_encrypt() {
117        for key in [
118            "12345678901234567890123456789012",
119            "12345678901234567890123456789012ABC",
120            "1234567890123456789012345678901",
121        ] {
122            let data = "hello";
123            let result = aes_encrypt(key, data);
124            assert_eq!(result.is_ok(), true);
125
126            let result = aes_decrypt(key, &result.unwrap());
127            assert_eq!(result.is_ok(), true);
128            assert_eq!(result.unwrap(), data);
129        }
130    }
131}