feat(encrypt): Add XOR and ChaCha20 encryption with low-end device optimization and openssl support. (#1186)

Add ChaCha20 XOR algorithm, extend AES-GCM-256 capabilities, and integrate OpenSSL support.

---------

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
This commit is contained in:
CyiceK
2025-08-09 18:53:55 +08:00
committed by GitHub
parent 7de4b33dd1
commit 0087ac3ffc
13 changed files with 720 additions and 31 deletions
+78 -1
View File
@@ -1,11 +1,21 @@
use crate::tunnel::packet_def::ZCPacket;
use std::sync::Arc;
use crate::{common::config::EncryptionAlgorithm, tunnel::packet_def::ZCPacket};
#[cfg(feature = "wireguard")]
pub mod ring_aes_gcm;
#[cfg(feature = "wireguard")]
pub mod ring_chacha20;
#[cfg(feature = "aes-gcm")]
pub mod aes_gcm;
#[cfg(feature = "openssl-crypto")]
pub mod openssl_cipher;
pub mod xor_cipher;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("packet is too short. len: {0}")]
@@ -39,3 +49,70 @@ impl Encryptor for NullCipher {
}
}
}
/// Create an encryptor based on the algorithm name
pub fn create_encryptor(
algorithm: &str,
key_128: [u8; 16],
key_256: [u8; 32],
) -> Arc<dyn Encryptor> {
let algorithm = match EncryptionAlgorithm::try_from(algorithm) {
Ok(algorithm) => algorithm,
Err(_) => {
eprintln!(
"Unknown encryption algorithm: {}, falling back to default AES-GCM",
algorithm
);
EncryptionAlgorithm::AesGcm
}
};
match algorithm {
EncryptionAlgorithm::AesGcm => {
#[cfg(feature = "wireguard")]
{
Arc::new(ring_aes_gcm::AesGcmCipher::new_128(key_128))
}
#[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))]
{
Arc::new(aes_gcm::AesGcmCipher::new_128(key_128))
}
#[cfg(all(not(feature = "wireguard"), not(feature = "aes-gcm")))]
{
compile_error!(
"wireguard or aes-gcm feature must be enabled for default encryption"
);
}
}
EncryptionAlgorithm::Aes256Gcm => {
#[cfg(feature = "wireguard")]
{
Arc::new(ring_aes_gcm::AesGcmCipher::new_256(key_256))
}
#[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))]
{
Arc::new(aes_gcm::AesGcmCipher::new_256(key_256))
}
}
EncryptionAlgorithm::Xor => Arc::new(xor_cipher::XorCipher::new(&key_128)),
#[cfg(feature = "wireguard")]
EncryptionAlgorithm::ChaCha20 => Arc::new(ring_chacha20::RingChaCha20Cipher::new(key_256)),
#[cfg(feature = "openssl-crypto")]
EncryptionAlgorithm::OpensslAesGcm => {
Arc::new(openssl_cipher::OpenSslCipher::new_aes128_gcm(key_128))
}
#[cfg(feature = "openssl-crypto")]
EncryptionAlgorithm::OpensslAes256Gcm => {
Arc::new(openssl_cipher::OpenSslCipher::new_aes256_gcm(key_256))
}
#[cfg(feature = "openssl-crypto")]
EncryptionAlgorithm::OpensslChacha20 => {
Arc::new(openssl_cipher::OpenSslCipher::new_chacha20(key_256))
}
}
}
@@ -0,0 +1,241 @@
use openssl::symm::{Cipher, Crypter, Mode};
use rand::RngCore;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use crate::tunnel::packet_def::ZCPacket;
use crate::peers::encrypt::{Encryptor, Error};
// OpenSSL 加密尾部结构
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct OpenSslTail {
pub nonce: [u8; 16], // 使用 16 字节的 nonce/IV
}
pub const OPENSSL_ENCRYPTION_RESERVED: usize = std::mem::size_of::<OpenSslTail>();
#[derive(Clone)]
pub struct OpenSslCipher {
pub(crate) cipher: OpenSslEnum,
}
pub enum OpenSslEnum {
Aes128Gcm([u8; 16]),
Aes256Gcm([u8; 32]),
Chacha20([u8; 32]),
}
impl Clone for OpenSslEnum {
fn clone(&self) -> Self {
match &self {
OpenSslEnum::Aes128Gcm(key) => OpenSslEnum::Aes128Gcm(*key),
OpenSslEnum::Aes256Gcm(key) => OpenSslEnum::Aes256Gcm(*key),
OpenSslEnum::Chacha20(key) => OpenSslEnum::Chacha20(*key),
}
}
}
impl OpenSslCipher {
pub fn new_aes128_gcm(key: [u8; 16]) -> Self {
Self {
cipher: OpenSslEnum::Aes128Gcm(key),
}
}
pub fn new_aes256_gcm(key: [u8; 32]) -> Self {
Self {
cipher: OpenSslEnum::Aes256Gcm(key),
}
}
pub fn new_chacha20(key: [u8; 32]) -> Self {
Self {
cipher: OpenSslEnum::Chacha20(key),
}
}
fn get_cipher_and_key(&self) -> (Cipher, &[u8]) {
match &self.cipher {
OpenSslEnum::Aes128Gcm(key) => (Cipher::aes_128_gcm(), key.as_slice()),
OpenSslEnum::Aes256Gcm(key) => (Cipher::aes_256_gcm(), key.as_slice()),
OpenSslEnum::Chacha20(key) => (Cipher::chacha20_poly1305(), key.as_slice()),
}
}
fn is_aead_cipher(&self) -> bool {
matches!(
self.cipher,
OpenSslEnum::Aes128Gcm(_) | OpenSslEnum::Aes256Gcm(_) | OpenSslEnum::Chacha20(_)
)
}
fn get_nonce_size(&self) -> usize {
match &self.cipher {
OpenSslEnum::Aes128Gcm(_) | OpenSslEnum::Aes256Gcm(_) | OpenSslEnum::Chacha20(_) => 12, // GCM and ChaCha20-Poly1305 use 12-byte nonce
}
}
}
impl Encryptor for OpenSslCipher {
fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if !pm_header.is_encrypted() {
return Ok(());
}
let payload_len = zc_packet.payload().len();
if payload_len < OPENSSL_ENCRYPTION_RESERVED {
return Err(Error::PacketTooShort(zc_packet.payload().len()));
}
let (cipher, key) = self.get_cipher_and_key();
let is_aead = self.is_aead_cipher();
let nonce_size = self.get_nonce_size();
// 提取 nonce/IV
let openssl_tail = OpenSslTail::ref_from_suffix(zc_packet.payload())
.unwrap()
.clone();
let text_len = if is_aead {
payload_len - OPENSSL_ENCRYPTION_RESERVED - 16 // AEAD 需要减去 tag 长度
} else {
payload_len - OPENSSL_ENCRYPTION_RESERVED
};
let mut decrypter = Crypter::new(
cipher,
Mode::Decrypt,
key,
Some(&openssl_tail.nonce[..nonce_size]),
)
.map_err(|_| Error::DecryptionFailed)?;
if is_aead {
// 对于 AEAD 模式,需要设置 tag
let tag_start = text_len;
let tag = &zc_packet.payload()[tag_start..tag_start + 16];
decrypter
.set_tag(tag)
.map_err(|_| Error::DecryptionFailed)?;
}
let mut output = vec![0u8; text_len + cipher.block_size()];
let mut count = decrypter
.update(&zc_packet.payload()[..text_len], &mut output)
.map_err(|_| Error::DecryptionFailed)?;
count += decrypter
.finalize(&mut output[count..])
.map_err(|_| Error::DecryptionFailed)?;
// 更新数据包
zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]);
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(false);
let old_len = zc_packet.buf_len();
let new_len = old_len - (payload_len - count);
zc_packet.mut_inner().truncate(new_len);
Ok(())
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
return Ok(());
}
let (cipher, key) = self.get_cipher_and_key();
let is_aead = self.is_aead_cipher();
let nonce_size = self.get_nonce_size();
let mut tail = OpenSslTail::default();
rand::thread_rng().fill_bytes(&mut tail.nonce[..nonce_size]);
let mut encrypter =
Crypter::new(cipher, Mode::Encrypt, key, Some(&tail.nonce[..nonce_size]))
.map_err(|_| Error::EncryptionFailed)?;
let payload_len = zc_packet.payload().len();
let mut output = vec![0u8; payload_len + cipher.block_size()];
let mut count = encrypter
.update(zc_packet.payload(), &mut output)
.map_err(|_| Error::EncryptionFailed)?;
count += encrypter
.finalize(&mut output[count..])
.map_err(|_| Error::EncryptionFailed)?;
// 更新数据包内容
zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]);
// 对于 AEAD 模式,添加 tag
if is_aead {
let mut tag = vec![0u8; 16]; // GCM 标签通常是 16 字节
encrypter
.get_tag(&mut tag)
.map_err(|_| Error::EncryptionFailed)?;
zc_packet.mut_inner().extend_from_slice(&tag);
}
// 添加 nonce/IV
zc_packet.mut_inner().extend_from_slice(tail.as_bytes());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(true);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
peers::encrypt::{openssl_cipher::OpenSslCipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
use super::OPENSSL_ENCRYPTION_RESERVED;
#[test]
fn test_openssl_aes128_gcm() {
let key = [0u8; 16];
let cipher = OpenSslCipher::new_aes128_gcm(key);
let text = b"Hello, World! This is a test message for OpenSSL AES-128-GCM.";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
// 加密
cipher.encrypt(&mut packet).unwrap();
assert!(packet.payload().len() > text.len() + OPENSSL_ENCRYPTION_RESERVED);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
#[test]
fn test_openssl_chacha20() {
let key = [0u8; 32];
let cipher = OpenSslCipher::new_chacha20(key);
let text = b"Hello, World! This is a test message for OpenSSL ChaCha20.";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
// 加密
cipher.encrypt(&mut packet).unwrap();
assert!(packet.payload().len() > text.len());
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}
+125
View File
@@ -0,0 +1,125 @@
use rand::RngCore;
use ring::aead::{self, Aad, LessSafeKey, Nonce, UnboundKey};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use super::{Encryptor, Error};
use crate::tunnel::packet_def::ZCPacket;
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct ChaCha20Poly1305Tail {
pub tag: [u8; 16],
pub nonce: [u8; 12],
}
pub const CHACHA20_POLY1305_ENCRYPTION_RESERVED: usize =
std::mem::size_of::<ChaCha20Poly1305Tail>();
#[derive(Clone)]
pub struct RingChaCha20Cipher {
cipher: LessSafeKey,
key: [u8; 32],
}
impl RingChaCha20Cipher {
pub fn new(key: [u8; 32]) -> Self {
let unbound_key = UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap();
let cipher = LessSafeKey::new(unbound_key);
Self { cipher, key }
}
}
impl Encryptor for RingChaCha20Cipher {
fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if !pm_header.is_encrypted() {
return Ok(());
}
let payload_len = zc_packet.payload().len();
if payload_len < CHACHA20_POLY1305_ENCRYPTION_RESERVED {
return Err(Error::PacketTooShort(zc_packet.payload().len()));
}
let text_and_tag_len = payload_len - CHACHA20_POLY1305_ENCRYPTION_RESERVED + 16;
let chacha20_tail = ChaCha20Poly1305Tail::ref_from_suffix(zc_packet.payload()).unwrap();
let nonce = Nonce::assume_unique_for_key(chacha20_tail.nonce.clone());
let rs = self.cipher.open_in_place(
nonce,
Aad::empty(),
&mut zc_packet.mut_payload()[..text_and_tag_len],
);
if rs.is_err() {
return Err(Error::DecryptionFailed);
}
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(false);
let old_len = zc_packet.buf_len();
zc_packet
.mut_inner()
.truncate(old_len - CHACHA20_POLY1305_ENCRYPTION_RESERVED);
Ok(())
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
return Ok(());
}
let mut tail = ChaCha20Poly1305Tail::default();
rand::thread_rng().fill_bytes(&mut tail.nonce);
let nonce = Nonce::assume_unique_for_key(tail.nonce.clone());
let rs =
self.cipher
.seal_in_place_separate_tag(nonce, Aad::empty(), zc_packet.mut_payload());
match rs {
Ok(tag) => {
tail.tag.copy_from_slice(tag.as_ref());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(true);
zc_packet.mut_inner().extend_from_slice(tail.as_bytes());
Ok(())
}
Err(_) => Err(Error::EncryptionFailed),
}
}
}
#[cfg(test)]
mod tests {
use crate::{
peers::encrypt::{ring_chacha20::RingChaCha20Cipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
use super::CHACHA20_POLY1305_ENCRYPTION_RESERVED;
#[test]
fn test_ring_chacha20_cipher() {
let key = [0u8; 32];
let cipher = RingChaCha20Cipher::new(key);
let text = b"Hello, World! This is a test message for Ring ChaCha20-Poly1305.";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
cipher.encrypt(&mut packet).unwrap();
assert_eq!(
packet.payload().len(),
text.len() + CHACHA20_POLY1305_ENCRYPTION_RESERVED
);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}
+86
View File
@@ -0,0 +1,86 @@
use crate::tunnel::packet_def::ZCPacket;
use super::{Encryptor, Error};
// XOR 加密不需要额外的尾部数据,因为它是对称的
pub const XOR_ENCRYPTION_RESERVED: usize = 0;
#[derive(Clone)]
pub struct XorCipher {
pub(crate) key: Vec<u8>,
}
impl XorCipher {
pub fn new(key: &[u8]) -> Self {
if key.is_empty() {
panic!("XOR key cannot be empty");
}
Self { key: key.to_vec() }
}
fn xor_data(&self, data: &mut [u8]) {
for (i, byte) in data.iter_mut().enumerate() {
*byte ^= self.key[i % self.key.len()];
}
}
}
impl Encryptor for XorCipher {
fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if !pm_header.is_encrypted() {
return Ok(());
}
// XOR 解密(XOR是对称的,加密和解密操作相同)
self.xor_data(zc_packet.mut_payload());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(false);
Ok(())
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
return Ok(());
}
// XOR 加密
self.xor_data(zc_packet.mut_payload());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(true);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
peers::encrypt::{xor_cipher::XorCipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
#[test]
fn test_xor_cipher() {
let key = b"test_key_123456";
let cipher = XorCipher::new(key);
let text = b"Hello, World! This is a test message.";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
// 加密
cipher.encrypt(&mut packet).unwrap();
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
assert_ne!(packet.payload(), text); // 加密后数据应该不同
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}
+25 -25
View File
@@ -71,7 +71,7 @@ struct RpcTransport {
packet_recv: Mutex<UnboundedReceiver<ZCPacket>>,
peer_rpc_tspt_sender: UnboundedSender<ZCPacket>,
encryptor: Arc<Box<dyn Encryptor>>,
encryptor: Arc<dyn Encryptor>,
}
#[async_trait::async_trait]
@@ -147,7 +147,7 @@ pub struct PeerManager {
foreign_network_manager: Arc<ForeignNetworkManager>,
foreign_network_client: Arc<ForeignNetworkClient>,
encryptor: Arc<Box<dyn Encryptor>>,
encryptor: Arc<dyn Encryptor + 'static>,
data_compress_algo: CompressorAlgo,
exit_nodes: Vec<IpAddr>,
@@ -184,25 +184,18 @@ impl PeerManager {
my_peer_id,
));
let mut encryptor: Arc<Box<dyn Encryptor>> = Arc::new(Box::new(NullCipher));
if global_ctx.get_flags().enable_encryption {
#[cfg(feature = "wireguard")]
{
use super::encrypt::ring_aes_gcm::AesGcmCipher;
encryptor = Arc::new(Box::new(AesGcmCipher::new_128(global_ctx.get_128_key())));
}
#[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))]
{
use super::encrypt::aes_gcm::AesGcmCipher;
encryptor = Arc::new(Box::new(AesGcmCipher::new_128(global_ctx.get_128_key())));
}
#[cfg(all(not(feature = "wireguard"), not(feature = "aes-gcm")))]
{
compile_error!("wireguard or aes-gcm feature must be enabled for encryption");
}
}
let encryptor = if global_ctx.get_flags().enable_encryption {
// 只有在启用加密时才使用工厂函数选择算法
let algorithm = &global_ctx.get_flags().encryption_algorithm;
super::encrypt::create_encryptor(
algorithm,
global_ctx.get_128_key(),
global_ctx.get_256_key(),
)
} else {
// disable_encryption = true 时使用 NullCipher
Arc::new(NullCipher)
};
if global_ctx
.check_network_in_whitelist(&global_ctx.get_network_name())
@@ -1110,7 +1103,7 @@ impl PeerManager {
pub async fn try_compress_and_encrypt(
compress_algo: CompressorAlgo,
encryptor: &Box<dyn Encryptor>,
encryptor: &Arc<dyn Encryptor + 'static>,
msg: &mut ZCPacket,
) -> Result<(), Error> {
let compressor = DefaultCompressor {};
@@ -1375,9 +1368,12 @@ impl PeerManager {
return false;
}
let next_hop_policy = Self::get_next_hop_policy( self.global_ctx.get_flags().latency_first);
let next_hop_policy = Self::get_next_hop_policy(self.global_ctx.get_flags().latency_first);
// check relay node allow relay kcp.
let Some(next_hop_id) = route.get_next_hop_with_policy(dst_peer_id, next_hop_policy).await else {
let Some(next_hop_id) = route
.get_next_hop_with_policy(dst_peer_id, next_hop_policy)
.await
else {
return false;
};
@@ -1386,7 +1382,11 @@ impl PeerManager {
};
// check next hop allow kcp relay
if next_hop_info.feature_flag.map(|x| x.no_relay_kcp).unwrap_or(false) {
if next_hop_info
.feature_flag
.map(|x| x.no_relay_kcp)
.unwrap_or(false)
{
return false;
}