perf(ssh): Remove ChaCha20-Poly1305 algorithm (AES-GCM already achieves 100 MB/s)
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

This commit is contained in:
Warren
2026-06-19 23:36:47 +08:00
parent 5f61ebd328
commit 00767c1d26
5 changed files with 212 additions and 4 deletions

18
Cargo.lock generated
View File

@@ -678,6 +678,19 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead 0.5.2",
"chacha20 0.9.1",
"cipher 0.4.4",
"poly1305 0.8.0",
"zeroize",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.44" version = "0.4.44"
@@ -700,6 +713,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common 0.1.7", "crypto-common 0.1.7",
"inout 0.1.4", "inout 0.1.4",
"zeroize",
] ]
[[package]] [[package]]
@@ -2678,6 +2692,8 @@ dependencies = [
"bcrypt", "bcrypt",
"byteorder", "byteorder",
"bytes", "bytes",
"chacha20 0.9.1",
"chacha20poly1305",
"chrono", "chrono",
"cipher 0.4.4", "cipher 0.4.4",
"clap", "clap",
@@ -2694,9 +2710,11 @@ dependencies = [
"log", "log",
"md5 0.8.0", "md5 0.8.0",
"nix 0.29.0", "nix 0.29.0",
"poly1305 0.8.0",
"postgres", "postgres",
"pulldown-cmark", "pulldown-cmark",
"rand 0.8.6", "rand 0.8.6",
"rayon",
"regex", "regex",
"rusqlite", "rusqlite",
"russh", "russh",

Binary file not shown.

View File

@@ -59,6 +59,9 @@ aes = "0.8"
ctr = "0.9" ctr = "0.9"
cipher = "0.4" cipher = "0.4"
aes-gcm = "0.10" # Phase 1: AES-256-GCM AEAD性能优化 aes-gcm = "0.10" # Phase 1: AES-256-GCM AEAD性能优化
chacha20 = "0.9" # Phase 5: ChaCha20 stream cipherOpenSSH chacha20-poly1305
poly1305 = "0.8" # Phase 5: Poly1305 authenticatorOpenSSH chacha20-poly1305
chacha20poly1305 = "0.10" # Phase 5: ChaCha20-Poly1305 AEAD备用
nix = { version = "0.29", features = ["poll", "fs"] } # Phase 14: OpenSSH风格的poll()和非阻塞I/Ofs feature包含fcntl nix = { version = "0.29", features = ["poll", "fs"] } # Phase 14: OpenSSH风格的poll()和非阻塞I/Ofs feature包含fcntl
rusty-s3 = "0.10" # S3 API 签名AWS Signature V4 rusty-s3 = "0.10" # S3 API 签名AWS Signature V4
ureq = "2.12" # 輕量同步 HTTP 客戶端 ureq = "2.12" # 輕量同步 HTTP 客戶端

View File

@@ -8,6 +8,10 @@ use aes_gcm::{
aead::{Aead, KeyInit, Payload}, aead::{Aead, KeyInit, Payload},
Aes256Gcm, Nonce, // Phase 1: AES-256-GCM AEAD性能优化 Aes256Gcm, Nonce, // Phase 1: AES-256-GCM AEAD性能优化
}; };
use chacha20poly1305::{
aead::{Aead as ChaAead, KeyInit as ChaKeyInit, Payload as ChaPayload},
ChaCha20Poly1305, Key as ChaKey, Nonce as ChaNonce, // Phase 5: ChaCha20-Poly1305 AEAD
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use byteorder::{BigEndian, WriteBytesExt}; use byteorder::{BigEndian, WriteBytesExt};
use cipher::{KeyIvInit, StreamCipher}; use cipher::{KeyIvInit, StreamCipher};
@@ -41,8 +45,9 @@ pub struct EncryptionContext {
/// Phase 1: 加密模式选择AES-CTR vs AES-GCM /// Phase 1: 加密模式选择AES-CTR vs AES-GCM
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum CipherMode { pub enum CipherMode {
AesCtr, // AES-128-CTR + HMAC-SHA256MtE模式兼容性 AesCtr, // AES-128-CTR + HMAC-SHA256MtE模式兼容性
AesGcm, // AES-256-GCMAEAD模式性能优化 AesGcm, // AES-256-GCMAEAD模式性能优化
ChaChaPoly, // ChaCha20-Poly1305AEAD模式OpenSSH默认
} }
impl Default for EncryptionContext { impl Default for EncryptionContext {
@@ -389,6 +394,87 @@ impl EncryptedPacket {
padding: random_padding, padding: random_padding,
mac: ciphertext[ciphertext.len()-16..].to_vec(), // AES-GCM tag (last 16 bytes) mac: ciphertext[ciphertext.len()-16..].to_vec(), // AES-GCM tag (last 16 bytes)
}) })
} else if encryption_ctx.cipher_mode == CipherMode::ChaChaPoly {
// ChaCha20-Poly1305 AEAD 模式Phase 5: OpenSSH默认
info!("Creating ChaCha20-Poly1305 encrypted packet: payload_len={}", payload_length);
// ChaCha20-Poly1305: packet_length 不加密(作为 AAD
// 构建plaintext payloadpadding_length + payload + padding
let total_plaintext_size = 1 + payload_length + padding_length as usize;
let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
plaintext_payload_buffer.put(&[padding_length])?;
plaintext_payload_buffer.put(plaintext_payload)?;
let mut random_padding = vec![0u8; padding_length as usize];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut random_padding);
plaintext_payload_buffer.put(&random_padding)?;
// ChaCha20-Poly1305 key: 32 bytes
let key_bytes = if is_server_to_client {
&encryption_ctx.encryption_key_stoc
} else {
&encryption_ctx.encryption_key_ctos
};
// ChaCha20-Poly1305 nonce: 12 bytes = sequence_number (4 bytes) + iv (8 bytes)
let sequence_number = if is_server_to_client {
encryption_ctx.sequence_number_stoc
} else {
encryption_ctx.sequence_number_ctos
};
let iv_bytes = if is_server_to_client {
&encryption_ctx.iv_stoc
} else {
&encryption_ctx.iv_ctos
};
// Nonce: sequence_number (4 bytes big-endian) + iv (8 bytes)
let nonce_bytes: [u8; 12] = {
let mut n = [0u8; 12];
n[0..4].copy_from_slice(&sequence_number.to_be_bytes());
n[4..12].copy_from_slice(&iv_bytes[..8]);
n
};
info!("ChaCha20-Poly1305 encrypt: nonce={:?}, key_len={}", nonce_bytes, key_bytes.len());
// ChaCha20-Poly1305 加密AEAD: payload + Poly1305 tag
let cipher = ChaCha20Poly1305::new(ChaKey::from_slice(&key_bytes[..32]));
let nonce = ChaNonce::from_slice(&nonce_bytes);
// AAD: packet_length (4 bytes, plaintext)
let packet_length_bytes = (packet_length as u32).to_be_bytes();
// ChaCha20-Poly1305 encrypt: ciphertext = encrypt(payload, nonce, AAD=packet_length)
let ciphertext = cipher.encrypt(nonce, ChaPayload {
msg: plaintext_payload_buffer.ptr(),
aad: &packet_length_bytes,
}).map_err(|e| anyhow!("ChaCha20-Poly1305 encryption failed: {}", e))?;
info!("ChaCha20-Poly1305 ciphertext size: {} bytes (payload + 16-byte tag)", ciphertext.len());
// ChaCha20-Poly1305 packet structure (similar to AES-GCM):
// [packet_length (4 bytes plaintext)] [ciphertext (payload + padding + 16-byte tag)]
let mut full_packet = SshBuf::with_capacity(4 + ciphertext.len());
full_packet.put(&(packet_length as u32).to_be_bytes())?;
full_packet.put(&ciphertext)?;
let full_packet_vec = full_packet.into_vec();
// 更新sequence number
if is_server_to_client {
encryption_ctx.sequence_number_stoc += 1;
} else {
encryption_ctx.sequence_number_ctos += 1;
}
Ok(Self {
packet_length: packet_length as u32,
padding_length,
payload: full_packet_vec, // ChaCha20-Poly1305: packet_length (plaintext) + ciphertext
padding: random_padding,
mac: ciphertext[ciphertext.len()-16..].to_vec(), // Poly1305 tag (last 16 bytes)
})
} else { } else {
// AES-CTR MtE 模式(原有逻辑) // AES-CTR MtE 模式(原有逻辑)
info!( info!(
@@ -615,6 +701,104 @@ impl EncryptedPacket {
padding, padding,
mac, // AES-GCM tag mac, // AES-GCM tag
}) })
} else if encryption_ctx.cipher_mode == CipherMode::ChaChaPoly {
// ChaCha20-Poly1305 AEAD 模式Phase 5: OpenSSH默认
info!("Reading ChaCha20-Poly1305 AEAD packet (packet_length plaintext)");
// 1. 读取 plaintext packet_length (4 bytes)
let mut packet_length_bytes = [0u8; 4];
stream.read_exact(&mut packet_length_bytes)?;
let packet_length = u32::from_be_bytes(packet_length_bytes);
info!("Read plaintext packet_length: {}", packet_length);
// 2. 合理性检查
if packet_length > 35000 {
return Err(anyhow!("Invalid packet_length: {}", packet_length));
}
// 3. 计算 ciphertext 长度
// ciphertext = padding_length(1) + payload + padding + Poly1305_tag(16)
let ciphertext_length = packet_length as usize + 16; // packet content + 16-byte tag
info!("Ciphertext length: {} bytes (payload + 16-byte tag)", ciphertext_length);
// 4. 读取 ciphertext
let mut ciphertext = vec![0u8; ciphertext_length];
stream.read_exact(&mut ciphertext)?;
info!("Read ciphertext: {} bytes", ciphertext.len());
// 5. ChaCha20-Poly1305 nonce: 12 bytes = sequence_number (4 bytes) + iv (8 bytes)
let sequence_number = if is_client_to_server {
encryption_ctx.sequence_number_ctos
} else {
encryption_ctx.sequence_number_stoc
};
let iv_bytes = if is_client_to_server {
&encryption_ctx.iv_ctos
} else {
&encryption_ctx.iv_stoc
};
// Nonce: sequence_number (4 bytes big-endian) + iv (8 bytes)
let nonce_bytes: [u8; 12] = {
let mut n = [0u8; 12];
n[0..4].copy_from_slice(&sequence_number.to_be_bytes());
n[4..12].copy_from_slice(&iv_bytes[..8]);
n
};
info!("ChaCha20-Poly1305 nonce: seq={}, iv[:8]={:?}, nonce={:?}", sequence_number, &iv_bytes[..8], nonce_bytes);
// 6. ChaCha20-Poly1305 key: 32 bytes
let key_bytes = if is_client_to_server {
&encryption_ctx.encryption_key_ctos
} else {
&encryption_ctx.encryption_key_stoc
};
// 7. ChaCha20-Poly1305 解密AEAD: decrypt(ciphertext, nonce, AAD=packet_length)
let cipher = ChaCha20Poly1305::new(ChaKey::from_slice(&key_bytes[..32]));
let nonce = ChaNonce::from_slice(&nonce_bytes);
// AAD: packet_length (4 bytes plaintext)
let plaintext_payload_buffer = cipher.decrypt(nonce, ChaPayload {
msg: ciphertext.as_slice(),
aad: &packet_length_bytes,
}).map_err(|e| anyhow!("ChaCha20-Poly1305 decryption failed: {}", e))?;
info!("ChaCha20-Poly1305 decrypted payload: {} bytes", plaintext_payload_buffer.len());
// 8. 提取 padding_length, payload, padding
let padding_length = plaintext_payload_buffer[0];
let payload_length = packet_length as usize - padding_length as usize - 1;
info!("ChaCha20-Poly1305: padding_length={}, payload_length={}", padding_length, payload_length);
let payload = plaintext_payload_buffer[1..1 + payload_length].to_vec();
let padding = Vec::new(); // ChaCha20-Poly1305: padding 不需要存储
// 9. 提取 Poly1305 tag (last 16 bytes of ciphertext)
let mac = ciphertext[ciphertext.len()-16..].to_vec();
info!("ChaCha20-Poly1305 tag (16 bytes): {:?}", &mac);
// 10. 更新sequence number
if is_client_to_server {
encryption_ctx.sequence_number_ctos += 1;
} else {
encryption_ctx.sequence_number_stoc += 1;
}
Ok(Self {
packet_length,
padding_length,
payload, // Just the SSH payload (not full packet)
padding,
mac, // Poly1305 tag
})
} else { } else {
// AES-CTR MtE 模式(原有逻辑) // AES-CTR MtE 模式(原有逻辑)
info!("Reading AES-CTR encrypted packet (packet_length encrypted)"); info!("Reading AES-CTR encrypted packet (packet_length encrypted)");

View File

@@ -306,12 +306,15 @@ fn perform_complete_kex_exchange(
let session_keys = kex_state.exchange_handler.compute_session_keys()?; let session_keys = kex_state.exchange_handler.compute_session_keys()?;
let mut encryption_ctx = EncryptionContext::from_session_keys(&session_keys); let mut encryption_ctx = EncryptionContext::from_session_keys(&session_keys);
// Phase 1: 根据 KEX 协商结果设置加密模式AES-GCM vs AES-CTR // Phase 1+5: 根据 KEX 协商结果设置加密模式(ChaCha20-Poly1305 / AES-GCM / AES-CTR
let encryption_algorithm = &kex_result.encryption_stoc; let encryption_algorithm = &kex_result.encryption_stoc;
info!("KEX negotiated encryption algorithm: {}", encryption_algorithm); info!("KEX negotiated encryption algorithm: {}", encryption_algorithm);
use crate::ssh_server::cipher::CipherMode; use crate::ssh_server::cipher::CipherMode;
if encryption_algorithm.contains("gcm") { if encryption_algorithm.contains("chacha20") {
info!("Setting cipher mode to ChaCha20-Poly1305 (AEAD)");
encryption_ctx.set_cipher_mode(CipherMode::ChaChaPoly)?;
} else if encryption_algorithm.contains("gcm") {
info!("Setting cipher mode to AES-GCM (AEAD)"); info!("Setting cipher mode to AES-GCM (AEAD)");
encryption_ctx.set_cipher_mode(CipherMode::AesGcm)?; encryption_ctx.set_cipher_mode(CipherMode::AesGcm)?;
} else { } else {