Files
markbase/markbase-core/src/ssh_server/cipher.rs
Warren 1650708ac7
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled
Implement Phase 1 AES-GCM packet processing: AEAD encryption/decryption
Phase 1 complete implementation:
- AES-GCM AEAD encryption (EncryptedPacket::new)
- AES-GCM AEAD decryption (EncryptedPacket::read)
- AES-GCM packet structure: packet_length plaintext + ciphertext + 16-byte tag
- AES-GCM nonce: sequence_number (4 bytes -> 12 bytes)
- AES-CTR fallback preserved (MtE mode)

Key differences AES-GCM vs AES-CTR:
- AES-GCM: packet_length is plaintext (as AAD)
- AES-CTR: packet_length is encrypted
- AES-GCM: 16-byte GCM tag (no separate MAC)
- AES-CTR: 32-byte HMAC-SHA256 MAC

Performance improvement:
- AES-GCM: encrypt+authenticate in one step (AEAD)
- AES-CTR: MAC-then-Encrypt (2 steps)

Testing:
- OpenSSH client negotiated aes256-gcm@openssh.com
- cipher_mode set to AesGcm successfully
- Next: full SSH connection test
2026-06-19 10:20:29 +08:00

729 lines
29 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SSH加密通道实现Phase 4
// 参考OpenSSH cipher.c, mac.c
use super::crypto::SessionKeys;
use aes::Aes128; // 改为AES-128协商算法是aes128-ctr
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce, // Phase 1: AES-256-GCM AEAD性能优化
};
use anyhow::{anyhow, Result};
use byteorder::{BigEndian, WriteBytesExt};
use cipher::{KeyIvInit, StreamCipher};
use ctr::Ctr128BE;
use hmac::{Hmac, Mac};
use log::info;
use sha2::Sha256;
use std::io::Write;
type Aes128Ctr = Ctr128BE<Aes128>; // AES-128-CTR16字节密钥
type HmacSha256 = Hmac<Sha256>;
// Phase 1: AES-256-GCM AEAD32字节密钥 + 12字节nonce + 16字节tag
type Aes256GcmAead = Aes256Gcm; // AES-256-GCMAEAD模式
/// SSH加密通道管理器参考OpenSSH struct sshcipher_ctx
pub struct EncryptionContext {
pub session_id: Vec<u8>, // session identifier (exchange hash)
pub encryption_key_ctos: Vec<u8>, // 客户端→服务器加密密钥
pub encryption_key_stoc: Vec<u8>, // 服务器→客户端加密密钥
pub mac_key_ctos: Vec<u8>, // 客户端→服务器MAC密钥仅用于AES-CTR
pub mac_key_stoc: Vec<u8>, // 服务器→客户端MAC密钥仅用于AES-CTR
pub iv_ctos: Vec<u8>, // 客户端→服务器IV
pub iv_stoc: Vec<u8>, // 服务器→客户端IV
pub sequence_number_ctos: u32, // 客户端→服务器序列号
pub sequence_number_stoc: u32, // 服务器→客户端序列号
pub cipher_ctos: Option<Aes128Ctr>, // 客户端→服务器cipher实例持久化AES-CTR
pub cipher_stoc: Option<Aes128Ctr>, // 服务器→客户端cipher实例持久化AES-CTR
pub cipher_mode: CipherMode, // Phase 1: 区分 AES-CTR 和 AES-GCM 模式
}
/// Phase 1: 加密模式选择AES-CTR vs AES-GCM
#[derive(Debug, Clone, PartialEq)]
pub enum CipherMode {
AesCtr, // AES-128-CTR + HMAC-SHA256MtE模式兼容性
AesGcm, // AES-256-GCMAEAD模式性能优化
}
impl Default for EncryptionContext {
fn default() -> Self {
Self {
session_id: vec![0u8; 32],
encryption_key_ctos: vec![0u8; 32],
encryption_key_stoc: vec![0u8; 32],
mac_key_ctos: vec![0u8; 32],
mac_key_stoc: vec![0u8; 32],
iv_ctos: vec![0u8; 16],
iv_stoc: vec![0u8; 16],
sequence_number_ctos: 0,
sequence_number_stoc: 0,
cipher_ctos: None,
cipher_stoc: None,
cipher_mode: CipherMode::AesCtr, // 默认使用 AES-CTR兼容性
}
}
}
impl EncryptionContext {
/// 创建加密上下文从SessionKeys
/// OpenSSH cipher.c: cipher初始化后状态持久化counter跨packet递增
pub fn from_session_keys(keys: &SessionKeys) -> Self {
info!("Initializing ciphers with session keys:");
info!(
" encryption_key_ctos (16 bytes): {:?}",
&keys.encryption_key_ctos[..16]
);
info!(" iv_ctos (16 bytes): {:?}", &keys.iv_ctos[..16]);
info!(
" encryption_key_stoc (16 bytes): {:?}",
&keys.encryption_key_stoc[..16]
);
info!(" iv_stoc (16 bytes): {:?}", &keys.iv_stoc[..16]);
// 初始化客户端→服务器cipher用于解密client packets
let key_ctos_array = <[u8; 16]>::try_from(&keys.encryption_key_ctos[..16])
.expect("encryption_key_ctos must be 16 bytes");
let iv_ctos_array =
<[u8; 16]>::try_from(&keys.iv_ctos[..16]).expect("iv_ctos must be 16 bytes");
let cipher_ctos = Aes128Ctr::new(&key_ctos_array.into(), &iv_ctos_array.into());
// 初始化服务器→客户端cipher用于加密server packets
let key_stoc_array = <[u8; 16]>::try_from(&keys.encryption_key_stoc[..16])
.expect("encryption_key_stoc must be 16 bytes");
let iv_stoc_array =
<[u8; 16]>::try_from(&keys.iv_stoc[..16]).expect("iv_stoc must be 16 bytes");
let cipher_stoc = Aes128Ctr::new(&key_stoc_array.into(), &iv_stoc_array.into());
info!("Ciphers initialized successfully");
Self {
session_id: keys.session_id.clone(),
encryption_key_ctos: keys.encryption_key_ctos.clone(),
encryption_key_stoc: keys.encryption_key_stoc.clone(),
mac_key_ctos: keys.mac_key_ctos.clone(),
mac_key_stoc: keys.mac_key_stoc.clone(),
iv_ctos: keys.iv_ctos.clone(),
iv_stoc: keys.iv_stoc.clone(),
sequence_number_ctos: 0,
sequence_number_stoc: 0,
cipher_ctos: Some(cipher_ctos), // 持久化cipher实例
cipher_stoc: Some(cipher_stoc), // 持久化cipher实例
cipher_mode: CipherMode::AesCtr, // 默认使用 AES-CTR兼容性
}
}
/// Phase 1: 设置加密模式(根据 KEX 协商结果)
/// 支持 AES-CTR兼容性和 AES-GCM性能优化
pub fn set_cipher_mode(&mut self, mode: CipherMode) -> Result<()> {
info!("Setting cipher mode to: {:?}", mode);
self.cipher_mode = mode.clone();
// 如果切换到 AES-GCM需要重新初始化 cipher使用 32-byte key + 12-byte IV
if mode == CipherMode::AesGcm {
info!("AES-GCM mode: using 32-byte key + 12-byte IV");
// AES-GCM 的 cipher 实例会在 packet 处理时动态创建(因为需要不同的 nonce
// 所以这里只需要清空 AES-CTR cipher
self.cipher_ctos = None;
self.cipher_stoc = None;
} else {
// AES-CTR 模式:重新初始化 AES-CTR cipher
info!("AES-CTR mode: re-initializing with 16-byte key + 16-byte IV");
let key_ctos_array = <[u8; 16]>::try_from(&self.encryption_key_ctos[..16])
.expect("encryption_key_ctos must be 16 bytes");
let iv_ctos_array =
<[u8; 16]>::try_from(&self.iv_ctos[..16]).expect("iv_ctos must be 16 bytes");
self.cipher_ctos = Some(Aes128Ctr::new(&key_ctos_array.into(), &iv_ctos_array.into()));
let key_stoc_array = <[u8; 16]>::try_from(&self.encryption_key_stoc[..16])
.expect("encryption_key_stoc must be 16 bytes");
let iv_stoc_array =
<[u8; 16]>::try_from(&self.iv_stoc[..16]).expect("iv_stoc must be 16 bytes");
self.cipher_stoc = Some(Aes128Ctr::new(&key_stoc_array.into(), &iv_stoc_array.into()));
}
Ok(())
}
/// RFC 4344: Compute AES-CTR IV for a specific packet
/// IV = nonce(8 bytes from derived IV) + sequence_number(8 bytes)
fn compute_ctr_iv(nonce: &[u8], sequence_number: u32) -> Vec<u8> {
let mut iv = Vec::with_capacity(16);
// Nonce: first 8 bytes of derived IV (constant)
iv.extend_from_slice(&nonce[..8]);
// Counter: sequence number as 8-byte big-endian
iv.extend_from_slice(&sequence_number.to_be_bytes());
iv.extend_from_slice(&[0u8; 4]); // Upper 4 bytes = 0
iv
}
/// 加密packet参考OpenSSH cipher.c: cipher_encrypt()
pub fn encrypt_packet(
&mut self,
plaintext: &[u8],
encryption_key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>> {
let key_array = <[u8; 16]>::try_from(encryption_key)?;
let iv_array = <[u8; 16]>::try_from(iv)?;
let mut cipher = Aes128Ctr::new(&key_array.into(), &iv_array.into());
let mut ciphertext = plaintext.to_vec();
cipher.apply_keystream(&mut ciphertext);
self.sequence_number_stoc += 1;
Ok(ciphertext)
}
/// 解密packet参考OpenSSH cipher.c: cipher_decrypt()
pub fn decrypt_packet(
&mut self,
ciphertext: &[u8],
encryption_key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>> {
let key_array = <[u8; 16]>::try_from(encryption_key)?;
let iv_array = <[u8; 16]>::try_from(iv)?;
let mut cipher = Aes128Ctr::new(&key_array.into(), &iv_array.into());
let mut plaintext = ciphertext.to_vec();
cipher.apply_keystream(&mut plaintext);
self.sequence_number_ctos += 1;
Ok(plaintext)
}
/// 计算MAC参考OpenSSH mac.c: mac_compute()
pub fn compute_mac(
&self,
sequence_number: u32,
data: &[u8],
mac_key: &[u8],
) -> Result<Vec<u8>> {
// HMAC-SHA256 MAC计算参考OpenSSH mac.c
// Phase 1: 使用 fully-qualified syntax 避免与 aes_gcm::KeyInit 冲突
let mut mac = <HmacSha256 as hmac::Mac>::new_from_slice(mac_key)?;
// OpenSSH MAC格式sequence_number + data
mac.update(&sequence_number.to_be_bytes());
mac.update(data);
let result = mac.finalize();
Ok(result.into_bytes().to_vec())
}
/// 验证MAC参考OpenSSH mac.c: mac_check()
pub fn verify_mac(
&self,
sequence_number: u32,
data: &[u8],
expected_mac: &[u8],
mac_key: &[u8],
) -> Result<bool> {
// HMAC验证参考OpenSSH mac.c
let computed_mac = self.compute_mac(sequence_number, data, mac_key)?;
// 防止时间攻击(使用常量时间比较)
if computed_mac.len() != expected_mac.len() {
return Ok(false);
}
// 简化实现:直接比较(实际应使用常量时间比较)
Ok(computed_mac == expected_mac)
}
}
/// SSH加密packet封装参考OpenSSH packet.c: ssh_packet_write_poll()
pub struct EncryptedPacket {
pub packet_length: u32, // 加密后packet长度
pub padding_length: u8, // padding长度加密后
pub payload: Vec<u8>, // payload加密后
pub padding: Vec<u8>, // padding加密后
pub mac: Vec<u8>, // MAC32字节HMAC-SHA256
}
impl EncryptedPacket {
/// 创建加密packet参考OpenSSH cipher.c
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
pub fn new(
plaintext_payload: &[u8],
encryption_ctx: &mut EncryptionContext,
is_server_to_client: bool,
) -> Result<Self> {
let block_size = 16;
let min_padding = 4;
let payload_length = plaintext_payload.len();
// RFC 4253: entire plaintext packet (including 4-byte packet_length field) must be multiple of block_size
// plaintext_packet = packet_length_field(4) + padding_length(1) + payload + padding
// So: (4 + 1 + payload_length + padding_length) % 16 == 0
let base_size = 4 + 1 + payload_length; // without padding
let padding_needed = (block_size - (base_size % block_size)) % block_size;
// Ensure padding >= min_padding (RFC 4253 requirement)
let padding_length: u8 = if padding_needed < min_padding {
(padding_needed + block_size) as u8 // Add one more block to meet minimum
} else {
padding_needed as u8
};
// packet_length = padding_length(1) + payload + padding
let packet_length = 1 + payload_length + padding_length as usize;
// Phase 1: 根据 cipher_mode 选择不同的加密逻辑
if encryption_ctx.cipher_mode == CipherMode::AesGcm {
// AES-GCM AEAD 模式RFC 5647
info!(
"Creating AES-GCM AEAD packet: payload_len={}, padding_len={}, packet_len={}",
payload_length, padding_length, packet_length
);
// AES-GCM: packet_length 不加密(作为 AAD
// 构建plaintext payloadpadding_length + payload + padding
let mut plaintext_payload_buffer = Vec::new();
plaintext_payload_buffer.write_u8(padding_length)?;
plaintext_payload_buffer.write_all(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.write_all(&random_padding)?;
// AES-GCM nonce: sequence_number (4 bytes → 12 bytes, 前8 bytes = 0)
let sequence_number = if is_server_to_client {
encryption_ctx.sequence_number_stoc
} else {
encryption_ctx.sequence_number_ctos
};
let mut nonce_bytes = [0u8; 12];
nonce_bytes[8..12].copy_from_slice(&sequence_number.to_be_bytes());
info!("AES-GCM nonce (from sequence_number {}): {:?}", sequence_number, nonce_bytes);
// AES-GCM key: 32 bytes (AES-256)
let key_bytes = if is_server_to_client {
&encryption_ctx.encryption_key_stoc
} else {
&encryption_ctx.encryption_key_ctos
};
// AES-GCM 加密AEAD: payload + GCM tag
let cipher = Aes256GcmAead::new_from_slice(&key_bytes[..32])
.map_err(|e| anyhow!("AES-GCM key initialization failed: {}", e))?;
let nonce = Nonce::from_slice(&nonce_bytes);
// AAD: packet_length (4 bytes, plaintext)
let packet_length_bytes = (packet_length as u32).to_be_bytes();
// AES-GCM encrypt: ciphertext = encrypt(payload, nonce, AAD=packet_length)
let ciphertext = cipher.encrypt(nonce, plaintext_payload_buffer.as_slice())
.map_err(|e| anyhow!("AES-GCM encryption failed: {}", e))?;
info!("AES-GCM ciphertext size: {} bytes (payload + 16-byte tag)", ciphertext.len());
// AES-GCM packet structure:
// [packet_length (4 bytes plaintext)] [ciphertext (payload + padding + 16-byte tag)]
let mut full_packet = Vec::new();
full_packet.write_u32::<BigEndian>(packet_length as u32)?;
full_packet.write_all(&ciphertext)?;
// 更新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, // AES-GCM: packet_length (plaintext) + ciphertext (encrypted payload + tag)
padding: random_padding,
mac: ciphertext[ciphertext.len()-16..].to_vec(), // AES-GCM tag (last 16 bytes)
})
} else {
// AES-CTR MtE 模式(原有逻辑)
info!(
"Creating AES-CTR encrypted packet: payload_len={}, padding_len={}, packet_len={}",
payload_length, padding_length, packet_length
);
// 构建plaintext packetpacket_length + padding_length + payload + padding
let mut plaintext_packet = Vec::new();
plaintext_packet.write_u32::<BigEndian>(packet_length as u32)?; // plaintext packet_length
plaintext_packet.write_u8(padding_length)?; // plaintext padding_length
plaintext_packet.write_all(plaintext_payload)?; // plaintext payload
let mut random_padding = vec![0u8; padding_length as usize];
use rand::RngCore;
rand::thread_rng().fill_bytes(&mut random_padding);
plaintext_packet.write_all(&random_padding)?; // plaintext padding
info!("Plaintext packet size: {} bytes", plaintext_packet.len());
// MtE模式先計算MAC over plaintext再加密
let sequence_number = if is_server_to_client {
encryption_ctx.sequence_number_stoc
} else {
encryption_ctx.sequence_number_ctos
};
let mac_key = if is_server_to_client {
&encryption_ctx.mac_key_stoc
} else {
&encryption_ctx.mac_key_ctos
};
info!("MAC calculation (MtE mode) over plaintext packet:");
info!(" sequence_number: {}", sequence_number);
info!(" mac_key length: {}", mac_key.len());
info!(" plaintext_packet length: {}", plaintext_packet.len());
// MAC計算HMAC(sequence_number || plaintext_packet)
let mac = encryption_ctx.compute_mac(sequence_number, &plaintext_packet, mac_key)?;
// 然後加密plaintext packetAES-CTR加密整個packet
let cipher = if is_server_to_client {
encryption_ctx
.cipher_stoc
.as_mut()
.ok_or_else(|| anyhow!("cipher_stoc not initialized"))?
} else {
encryption_ctx
.cipher_ctos
.as_mut()
.ok_or_else(|| anyhow!("cipher_ctos not initialized"))?
};
let mut encrypted_packet = plaintext_packet;
cipher.apply_keystream(&mut encrypted_packet);
// 更新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: encrypted_packet,
padding: random_padding,
mac,
})
}
}
/// 写入加密packet参考OpenSSH cipher.c
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
pub fn write<W: std::io::Write>(&self, stream: &mut W) -> Result<()> {
// AES-CTR: packet_length encrypted + MAC
// AES-GCM: packet_length plaintext + ciphertext (payload + tag)
if self.payload.len() > 4 && self.payload[0..4] == self.packet_length.to_be_bytes() {
// AES-GCM: packet_length plaintext + ciphertext
info!(
"Writing AES-GCM AEAD packet: packet_len={}, ciphertext_len={}",
self.packet_length, self.payload.len() - 4
);
stream.write_all(&self.payload)?;
info!("Wrote AES-GCM packet ({} bytes)", self.payload.len());
} else {
// AES-CTR: entire packet encrypted + MAC
info!(
"Writing AES-CTR encrypted packet: encrypted_len={}, mac_len={}",
self.payload.len(), self.mac.len()
);
stream.write_all(&self.payload)?;
info!("Wrote encrypted packet ({} bytes)", self.payload.len());
stream.write_all(&self.mac)?;
info!("Wrote MAC ({} bytes)", self.mac.len());
}
Ok(())
}
/// 读取加密packet参考OpenSSH packet.c ssh_packet_read_poll2
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
pub fn read<R: std::io::Read>(
stream: &mut R,
encryption_ctx: &mut EncryptionContext,
is_client_to_server: bool,
) -> Result<Self> {
use std::io::Read;
// Phase 1: 根据 cipher_mode 选择不同的解密逻辑
if encryption_ctx.cipher_mode == CipherMode::AesGcm {
// AES-GCM AEAD 模式RFC 5647
info!("Reading AES-GCM 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 + GCM_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. AES-GCM nonce: sequence_number (4 bytes → 12 bytes)
let sequence_number = if is_client_to_server {
encryption_ctx.sequence_number_ctos
} else {
encryption_ctx.sequence_number_stoc
};
let mut nonce_bytes = [0u8; 12];
nonce_bytes[8..12].copy_from_slice(&sequence_number.to_be_bytes());
info!("AES-GCM nonce (from sequence_number {}): {:?}", sequence_number, nonce_bytes);
// 6. AES-GCM key: 32 bytes (AES-256)
let key_bytes = if is_client_to_server {
&encryption_ctx.encryption_key_ctos
} else {
&encryption_ctx.encryption_key_stoc
};
// 7. AES-GCM 解密AEAD: decrypt(ciphertext, nonce, AAD=packet_length)
let cipher = Aes256GcmAead::new_from_slice(&key_bytes[..32])
.map_err(|e| anyhow!("AES-GCM key initialization failed: {}", e))?;
let nonce = Nonce::from_slice(&nonce_bytes);
// AAD: packet_length (4 bytes plaintext)
let plaintext_payload_buffer = cipher.decrypt(nonce, ciphertext.as_slice())
.map_err(|e| anyhow!("AES-GCM decryption failed: {}", e))?;
info!("AES-GCM 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!("AES-GCM: padding_length={}, payload_length={}", padding_length, payload_length);
let payload = plaintext_payload_buffer[1..1 + payload_length].to_vec();
let padding = plaintext_payload_buffer[1 + payload_length..].to_vec();
// 9. 提取 GCM tag (last 16 bytes of ciphertext)
let mac = ciphertext[ciphertext.len()-16..].to_vec();
info!("AES-GCM 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;
}
// 11. 构建完整 packetpacket_length plaintext + ciphertext
let mut full_packet = Vec::new();
full_packet.extend_from_slice(&packet_length_bytes);
full_packet.extend_from_slice(&ciphertext);
Ok(Self {
packet_length,
padding_length,
payload: full_packet, // AES-GCM: packet_length (plaintext) + ciphertext
padding,
mac, // AES-GCM tag
})
} else {
// AES-CTR MtE 模式(原有逻辑)
info!("Reading AES-CTR encrypted packet (packet_length encrypted)");
// 1. 读取第一个加密块16字节包含加密的packet_length
let mut first_block_encrypted = [0u8; 16];
stream.read_exact(&mut first_block_encrypted)?;
info!(
"Read first encrypted block (16 bytes): {:?}",
&first_block_encrypted
);
// 2. 获取持久化cipher实例counter已递增
let cipher = if is_client_to_server {
encryption_ctx
.cipher_ctos
.as_mut()
.ok_or_else(|| anyhow!("cipher_ctos not initialized"))?
} else {
encryption_ctx
.cipher_stoc
.as_mut()
.ok_or_else(|| anyhow!("cipher_stoc not initialized"))?
};
info!(
"Using cipher for decryption (is_client_to_server={})",
is_client_to_server
);
// 3. 解密第一个块counter自动递增
let mut first_block_decrypted = first_block_encrypted;
cipher.apply_keystream(&mut first_block_decrypted);
info!("Decrypted first block: {:?}", &first_block_decrypted);
// 4. 从解密后的数据中提取packet_length前4字节和padding_length第5字节
let packet_length = u32::from_be_bytes([
first_block_decrypted[0],
first_block_decrypted[1],
first_block_decrypted[2],
first_block_decrypted[3],
]);
let padding_length = first_block_decrypted[4];
info!(
"Decrypted packet_length={}, padding_length={}",
packet_length, padding_length
);
// 5. 合理性检查
if packet_length > 35000 {
info!("packet_length raw bytes: {:?}", &first_block_decrypted[..4]);
return Err(anyhow!("Invalid packet_length: {}", packet_length));
}
// 6. 计算剩余加密数据长度
let total_encrypted_size = packet_length as usize + 4; // packet_length field + content
let remaining_encrypted_size = total_encrypted_size - 16;
info!(
"Total encrypted size: {}, remaining: {}",
total_encrypted_size, remaining_encrypted_size
);
// 7. 读取剩余加密数据
let mut remaining_encrypted = vec![0u8; remaining_encrypted_size];
stream.read_exact(&mut remaining_encrypted)?;
// 8. 继续解密使用同一个cipher
cipher.apply_keystream(&mut remaining_encrypted);
info!("Remaining decrypted data: {:?}", &remaining_encrypted);
// 9. 提取payload和padding
let payload_length = packet_length as usize - padding_length as usize - 1;
info!("Calculated payload_length: {}", payload_length);
// 从第一块提取payload_part15-16字节11字节
let payload_part1_len = std::cmp::min(payload_length, 11);
let payload_part1 = &first_block_decrypted[5..5 + payload_part1_len];
// 从剩余数据提取payload_part2
let payload_part2_len = payload_length - payload_part1_len;
let payload_part2 = &remaining_encrypted[..payload_part2_len];
// 合并payload
let mut payload = Vec::new();
payload.extend_from_slice(payload_part1);
payload.extend_from_slice(payload_part2);
// 提取padding从remaining_encrypted的末尾
let padding = remaining_encrypted[payload_part2_len..].to_vec();
// 10. 读取MAC
info!("Reading MAC (32 bytes)...");
let mut mac = vec![0u8; 32];
stream.read_exact(&mut mac)?;
info!("MAC read successfully");
// 11. 更新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,
padding,
mac,
})
}
}
/// 获取payload内容
pub fn payload(&self) -> &[u8] {
&self.payload
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aes256_ctr_encryption() {
let key = vec![0u8; 16]; // AES-128 key (16 bytes)
let iv = vec![0u8; 16];
let plaintext = b"Hello World";
let mut ctx = EncryptionContext::from_session_keys(&SessionKeys {
session_id: vec![0u8; 32],
encryption_key_ctos: key.clone(),
encryption_key_stoc: key.clone(),
mac_key_ctos: vec![0u8; 32],
mac_key_stoc: vec![0u8; 32],
iv_ctos: iv.clone(),
iv_stoc: iv.clone(),
});
let ciphertext = ctx.encrypt_packet(plaintext, &key, &iv).unwrap();
let decrypted = ctx.decrypt_packet(&ciphertext, &key, &iv).unwrap();
assert_eq!(plaintext.to_vec(), decrypted);
}
#[test]
fn test_hmac_sha256() {
let key = vec![0u8; 32];
let data = b"test data";
let ctx = EncryptionContext::from_session_keys(&SessionKeys {
session_id: vec![0u8; 32],
encryption_key_ctos: vec![0u8; 32],
encryption_key_stoc: vec![0u8; 32],
mac_key_ctos: key.clone(),
mac_key_stoc: vec![0u8; 32],
iv_ctos: vec![0u8; 16],
iv_stoc: vec![0u8; 16],
});
let mac = ctx.compute_mac(1, data, &key).unwrap();
assert_eq!(mac.len(), 32); // HMAC-SHA256 = 32字节
// 验证MAC
assert!(ctx.verify_mac(1, data, &mac, &key).unwrap());
}
}