Last active
April 22, 2025 12:58
-
-
Save YukiCoco/2946ef1bf93616c7ca96462bd743a6ea to your computer and use it in GitHub Desktop.
安全在服务器储存敏感内容的算法,使用 Argon2 + AES 加密
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::fs; | |
use std::path::Path; | |
use argon2::{ | |
password_hash::{ | |
rand_core::OsRng, | |
PasswordHasher, SaltString | |
}, | |
Argon2 | |
}; | |
use aes_gcm::{ | |
aead::{Aead, KeyInit}, | |
Aes256Gcm, Nonce | |
}; | |
use rand::RngCore; | |
use anyhow::{Result, anyhow}; | |
use solana_sdk::signature::Keypair; | |
pub struct KeypairVault { | |
encrypted_data: Vec<u8>, | |
salt: [u8; 16], | |
nonce: [u8; 12], | |
} | |
impl KeypairVault { | |
pub fn create(password: &str, keypair: &Keypair) -> Result<Self> { | |
// Generate random salt for Argon2 | |
let mut salt_bytes = [0u8; 16]; | |
OsRng.fill_bytes(&mut salt_bytes); | |
// Configure Argon2 with strong parameters | |
// Configure Argon2 with high security parameters | |
let argon2 = Argon2::new( | |
argon2::Algorithm::Argon2id, // Argon2id variant - best for password hashing | |
argon2::Version::V0x13, // Latest version | |
argon2::Params::new( | |
128 * 1024, // m_cost: 128 MiB (significantly increases memory hardness) | |
3, // t_cost: 3 iterations (increased time cost) | |
4, // p_cost: 4 parallel threads (better for modern CPUs) | |
Some(32) // 32 bytes output for AES-256 | |
).unwrap() | |
); | |
// Generate encryption key using Argon2 | |
let mut encryption_key = [0u8; 32]; | |
argon2.hash_password_into( | |
password.as_bytes(), | |
&salt_bytes, | |
&mut encryption_key, | |
).unwrap(); | |
// Generate random nonce for AES-GCM | |
let mut nonce = [0u8; 12]; | |
OsRng.fill_bytes(&mut nonce); | |
// Create AES-GCM cipher | |
let cipher = Aes256Gcm::new_from_slice(&encryption_key)?; | |
// Serialize the keypair to bytes | |
let keypair_bytes = keypair.to_bytes(); | |
// Encrypt the keypair | |
let encrypted_data = cipher | |
.encrypt(Nonce::from_slice(&nonce), keypair_bytes.as_ref()) | |
.map_err(|e| anyhow!("Encryption failed: {}", e))?; | |
Ok(Self { | |
encrypted_data, | |
salt: salt_bytes[0..16].try_into().unwrap(), | |
nonce, | |
}) | |
} | |
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> { | |
let mut data = Vec::new(); | |
data.extend_from_slice(&self.salt); | |
data.extend_from_slice(&self.nonce); | |
data.extend_from_slice(&self.encrypted_data); | |
fs::write(path, data)?; | |
Ok(()) | |
} | |
pub fn load(path: impl AsRef<Path>) -> Result<Self> { | |
let data = fs::read(path)?; | |
if data.len() < 28 { // 16 (salt) + 12 (nonce) | |
return Err(anyhow!("Invalid vault file format")); | |
} | |
let mut salt = [0u8; 16]; | |
let mut nonce = [0u8; 12]; | |
salt.copy_from_slice(&data[0..16]); | |
nonce.copy_from_slice(&data[16..28]); | |
let encrypted_data = data[28..].to_vec(); | |
Ok(Self { | |
encrypted_data, | |
salt, | |
nonce, | |
}) | |
} | |
pub fn decrypt(&self, password: &str) -> Result<Keypair> { | |
// Configure Argon2 | |
let argon2 = Argon2::new( | |
argon2::Algorithm::Argon2id, // Argon2id variant - best for password hashing | |
argon2::Version::V0x13, // Latest version | |
argon2::Params::new( | |
128 * 1024, // m_cost: 128 MiB (significantly increases memory hardness) | |
3, // t_cost: 3 iterations (increased time cost) | |
4, // p_cost: 4 parallel threads (better for modern CPUs) | |
Some(32) // 32 bytes output for AES-256 | |
).unwrap() | |
); | |
// Recreate encryption key | |
let mut encryption_key = [0u8; 32]; | |
argon2.hash_password_into( | |
password.as_bytes(), | |
&self.salt, | |
&mut encryption_key, | |
).unwrap(); | |
// Create cipher | |
let cipher = Aes256Gcm::new_from_slice(&encryption_key)?; | |
// Decrypt the keypair | |
let keypair_bytes = cipher | |
.decrypt(Nonce::from_slice(&self.nonce), self.encrypted_data.as_ref()) | |
.map_err(|e| anyhow!("Decryption failed: {}", e))?; | |
// Convert back to Keypair | |
let keypair = Keypair::from_bytes(&keypair_bytes) | |
.map_err(|e| anyhow!("Invalid keypair data: {}", e))?; | |
Ok(keypair) | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use tempfile::tempdir; | |
#[test] | |
fn test_keypair_vault() { | |
let temp_dir = tempdir().unwrap(); | |
let vault_path = temp_dir.path().join("keypair_vault.bin"); | |
// Create a test keypair | |
let original_keypair = Keypair::new(); | |
let password = "test_password123"; | |
// Create and save vault | |
let vault = KeypairVault::create(password, &original_keypair).unwrap(); | |
vault.save(&vault_path).unwrap(); | |
// Load and decrypt vault | |
let loaded_vault = KeypairVault::load(&vault_path).unwrap(); | |
let decrypted_keypair = loaded_vault.decrypt(password).unwrap(); | |
// Verify keypairs match | |
assert_eq!(original_keypair.to_bytes(), decrypted_keypair.to_bytes()); | |
} | |
#[test] | |
fn test_wrong_password() { | |
let keypair = Keypair::new(); | |
let vault = KeypairVault::create("correct_password", &keypair).unwrap(); | |
assert!(vault.decrypt("wrong_password").is_err()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment