Implement Phase 1 AES-GCM packet processing: AEAD encryption/decryption
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

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
This commit is contained in:
Warren
2026-06-19 10:20:29 +08:00
parent 3575ab7e66
commit 1650708ac7

View File

@@ -250,7 +250,7 @@ pub struct EncryptedPacket {
impl EncryptedPacket { impl EncryptedPacket {
/// 创建加密packet参考OpenSSH cipher.c /// 创建加密packet参考OpenSSH cipher.c
/// AES-CTR模式所有数据加密包括packet_length /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
pub fn new( pub fn new(
plaintext_payload: &[u8], plaintext_payload: &[u8],
encryption_ctx: &mut EncryptionContext, encryption_ctx: &mut EncryptionContext,
@@ -278,6 +278,80 @@ impl EncryptedPacket {
// packet_length = padding_length(1) + payload + padding // packet_length = padding_length(1) + payload + padding
let packet_length = 1 + payload_length + padding_length as usize; 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!( info!(
"Creating AES-CTR encrypted packet: payload_len={}, padding_len={}, packet_len={}", "Creating AES-CTR encrypted packet: payload_len={}, padding_len={}, packet_len={}",
payload_length, padding_length, packet_length payload_length, padding_length, packet_length
@@ -348,30 +422,39 @@ impl EncryptedPacket {
mac, mac,
}) })
} }
}
/// 写入加密packet参考OpenSSH cipher.c /// 写入加密packet参考OpenSSH cipher.c
/// AES-CTR模式写入完整加密packet + MAC /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
pub fn write<W: std::io::Write>(&self, stream: &mut W) -> Result<()> { pub fn write<W: std::io::Write>(&self, stream: &mut W) -> Result<()> {
info!( // AES-CTR: packet_length encrypted + MAC
"Writing AES-CTR encrypted packet: total_encrypted_len={}, mac_len={}", // AES-GCM: packet_length plaintext + ciphertext (payload + tag)
self.payload.len(),
self.mac.len()
);
// AES-CTR: 整个packet已加密包括packet_length直接写入 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)?; stream.write_all(&self.payload)?;
info!("Wrote encrypted packet ({} bytes)", self.payload.len()); info!("Wrote encrypted packet ({} bytes)", self.payload.len());
// 写入MAC
stream.write_all(&self.mac)?; stream.write_all(&self.mac)?;
info!("Wrote MAC ({} bytes)", self.mac.len()); info!("Wrote MAC ({} bytes)", self.mac.len());
}
Ok(()) Ok(())
} }
/// 读取加密packet参考OpenSSH packet.c ssh_packet_read_poll2 /// 读取加密packet参考OpenSSH packet.c ssh_packet_read_poll2
/// OpenSSH packet.c: AES-CTR先解密第一个块再提取packet_length /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
/// aadlen = 0 (没有EtM或authenticated encryption), packet_length被加密
pub fn read<R: std::io::Read>( pub fn read<R: std::io::Read>(
stream: &mut R, stream: &mut R,
encryption_ctx: &mut EncryptionContext, encryption_ctx: &mut EncryptionContext,
@@ -379,6 +462,100 @@ impl EncryptedPacket {
) -> Result<Self> { ) -> Result<Self> {
use std::io::Read; 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)"); info!("Reading AES-CTR encrypted packet (packet_length encrypted)");
// 1. 读取第一个加密块16字节包含加密的packet_length // 1. 读取第一个加密块16字节包含加密的packet_length
@@ -414,7 +591,7 @@ impl EncryptedPacket {
info!("Decrypted first block: {:?}", &first_block_decrypted); info!("Decrypted first block: {:?}", &first_block_decrypted);
// 3. 从解密后的数据中提取packet_length前4字节和padding_length第5字节 // 4. 从解密后的数据中提取packet_length前4字节和padding_length第5字节
let packet_length = u32::from_be_bytes([ let packet_length = u32::from_be_bytes([
first_block_decrypted[0], first_block_decrypted[0],
first_block_decrypted[1], first_block_decrypted[1],
@@ -428,16 +605,13 @@ impl EncryptedPacket {
packet_length, padding_length packet_length, padding_length
); );
// 4. 合理性检查 // 5. 合理性检查
if packet_length > 35000 { if packet_length > 35000 {
info!("packet_length raw bytes: {:?}", &first_block_decrypted[..4]); info!("packet_length raw bytes: {:?}", &first_block_decrypted[..4]);
return Err(anyhow!("Invalid packet_length: {}", packet_length)); return Err(anyhow!("Invalid packet_length: {}", packet_length));
} }
// 3. 计算剩余加密数据长度 // 6. 计算剩余加密数据长度
// packet_length = padding_length(1) + payload + padding
// 总加密数据 = packet_length(4) + packet_length = packet_length + 4
// 已读取16字节剩余 = packet_length + 4 - 16
let total_encrypted_size = packet_length as usize + 4; // packet_length field + content let total_encrypted_size = packet_length as usize + 4; // packet_length field + content
let remaining_encrypted_size = total_encrypted_size - 16; let remaining_encrypted_size = total_encrypted_size - 16;
@@ -446,17 +620,16 @@ impl EncryptedPacket {
total_encrypted_size, remaining_encrypted_size total_encrypted_size, remaining_encrypted_size
); );
// 4. 读取剩余加密数据 // 7. 读取剩余加密数据
let mut remaining_encrypted = vec![0u8; remaining_encrypted_size]; let mut remaining_encrypted = vec![0u8; remaining_encrypted_size];
stream.read_exact(&mut remaining_encrypted)?; stream.read_exact(&mut remaining_encrypted)?;
// 5. 继续解密使用同一个cipher // 8. 继续解密使用同一个cipher
cipher.apply_keystream(&mut remaining_encrypted); cipher.apply_keystream(&mut remaining_encrypted);
info!("Remaining decrypted data: {:?}", &remaining_encrypted); info!("Remaining decrypted data: {:?}", &remaining_encrypted);
// 6. 提取payload和padding // 9. 提取payload和padding
// payload长度 = packet_length - padding_length - 1
let payload_length = packet_length as usize - padding_length as usize - 1; let payload_length = packet_length as usize - padding_length as usize - 1;
info!("Calculated payload_length: {}", payload_length); info!("Calculated payload_length: {}", payload_length);
@@ -476,13 +649,13 @@ impl EncryptedPacket {
// 提取padding从remaining_encrypted的末尾 // 提取padding从remaining_encrypted的末尾
let padding = remaining_encrypted[payload_part2_len..].to_vec(); let padding = remaining_encrypted[payload_part2_len..].to_vec();
// 9. 读取MAC // 10. 读取MAC
info!("Reading MAC (32 bytes)..."); info!("Reading MAC (32 bytes)...");
let mut mac = vec![0u8; 32]; let mut mac = vec![0u8; 32];
stream.read_exact(&mut mac)?; stream.read_exact(&mut mac)?;
info!("MAC read successfully"); info!("MAC read successfully");
// 10. 更新sequence number // 11. 更新sequence number
if is_client_to_server { if is_client_to_server {
encryption_ctx.sequence_number_ctos += 1; encryption_ctx.sequence_number_ctos += 1;
} else { } else {
@@ -497,6 +670,7 @@ impl EncryptedPacket {
mac, mac,
}) })
} }
}
/// 获取payload内容 /// 获取payload内容
pub fn payload(&self) -> &[u8] { pub fn payload(&self) -> &[u8] {