Skip to main content

j_agent/
crypto.rs

1//! ECDH P-256 密钥协商 + AES-256-GCM 加密工具
2
3use aes_gcm::aead::Aead;
4use aes_gcm::aead::generic_array::typenum::U12;
5use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
6use base64::Engine;
7use base64::engine::general_purpose::URL_SAFE_NO_PAD;
8use hkdf::Hkdf;
9use p256::PublicKey;
10use p256::ecdh::EphemeralSecret;
11use rand::rngs::OsRng;
12use sha2::Sha256;
13
14/// AES-256-GCM nonce 长度(12 字节)
15const NONCE_LEN: usize = 12;
16
17/// 生成 P-256 密钥对
18pub fn generate_keypair() -> (EphemeralSecret, PublicKey) {
19    let secret = EphemeralSecret::random(&mut OsRng);
20    let public = secret.public_key();
21    (secret, public)
22}
23
24/// 从 ECDH shared_secret 经 HKDF-SHA256 派生 32 字节 AES-256 密钥
25pub fn derive_aes_key(shared_secret: &p256::ecdh::SharedSecret) -> [u8; 32] {
26    let hk = Hkdf::<Sha256>::new(None, shared_secret.raw_secret_bytes());
27    let mut key = [0u8; 32];
28    hk.expand(b"j-remote-aes256gcm", &mut key)
29        .expect("HKDF expand should not fail for 32 bytes");
30    key
31}
32
33/// AES-256-GCM 加密
34///
35/// 返回 `[nonce(12) | ciphertext | tag(16)]`
36pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Vec<u8> {
37    use rand::RngCore;
38    let cipher = Aes256Gcm::new(key.into());
39    let mut nonce_bytes = [0u8; NONCE_LEN];
40    OsRng.fill_bytes(&mut nonce_bytes);
41    let nonce: &Nonce<U12> = (&nonce_bytes).into();
42
43    let ciphertext = cipher
44        .encrypt(nonce, plaintext)
45        .expect("AES-GCM encrypt should not fail");
46
47    let mut out = Vec::with_capacity(NONCE_LEN + ciphertext.len());
48    out.extend_from_slice(&nonce_bytes);
49    out.extend_from_slice(&ciphertext);
50    out
51}
52
53/// AES-256-GCM 解密
54///
55/// 输入 `[nonce(12) | ciphertext | tag(16)]`
56pub fn decrypt(key: &[u8; 32], data: &[u8]) -> Result<Vec<u8>, &'static str> {
57    if data.len() < NONCE_LEN + 16 {
58        return Err("密文太短");
59    }
60    let (nonce_bytes, ciphertext) = data.split_at(NONCE_LEN);
61    let cipher = Aes256Gcm::new(key.into());
62    let nonce: &Nonce<U12> = nonce_bytes.into();
63
64    cipher.decrypt(nonce, ciphertext).map_err(|_| "解密失败")
65}
66
67/// 导出公钥为 base64url(未压缩 65 字节)
68pub fn export_public_key(pk: &PublicKey) -> String {
69    use p256::elliptic_curve::sec1::ToEncodedPoint;
70    let point = pk.to_encoded_point(false); // uncompressed
71    URL_SAFE_NO_PAD.encode(point.as_bytes())
72}
73
74/// 从 base64url 编码的未压缩公钥导入 P-256 PublicKey
75pub fn import_public_key(b64: &str) -> Result<PublicKey, &'static str> {
76    let bytes = URL_SAFE_NO_PAD.decode(b64).map_err(|_| "base64 解码失败")?;
77    PublicKey::from_sec1_bytes(&bytes).map_err(|_| "无效的 P-256 公钥")
78}