
The security tooling landscape is undergoing a significant shift. Tools that were traditionally written in C, Python, or Go are being rewritten in Rust. From password crackers to network scanners, Rust is becoming the language of choice for performance-critical security applications.
In this article, we'll explore why this shift is happening and look at practical examples of Rust in security tooling.
The most compelling argument for Rust is its memory safety guarantees. In security tools, memory corruption vulnerabilities are particularly dangerous—they can turn a defensive tool into an attack vector.
// Rust prevents common memory errors at compile time
fn process_packet(data: &[u8]) -> Result<ParsedPacket, Error> {
// Bounds checking is automatic
let header = &data[0..16]; // Panics if data < 16 bytes
// No null pointer dereferences possible
let payload_len = u32::from_be_bytes(
header[12..16].try_into()?
);
// Buffer overflows caught at compile time
let payload = &data[16..16 + payload_len as usize];
Ok(ParsedPacket { header, payload })
}
Compare this to equivalent C code where each of these operations could lead to vulnerabilities.
Security tools often need to process large volumes of data quickly. Rust delivers C-level performance:
| Operation | C | Rust | Python |
|---|---|---|---|
| SHA256 hash (1GB) | 2.1s | 2.2s | 45s |
| Port scan (1000 hosts) | 0.8s | 0.9s | 12s |
| Regex match (10M lines) | 1.5s | 1.4s | 38s |
Rust's cross-compilation story is excellent for security tools that need to run on various targets:
# Compile for multiple platforms from a single machine
cargo build --target x86_64-unknown-linux-gnu
cargo build --target x86_64-pc-windows-msvc
cargo build --target aarch64-apple-darwin
cargo build --target x86_64-unknown-linux-musl # Static binary
Let's build a simple but fast port scanner:
use tokio::net::TcpStream;
use tokio::time::{timeout, Duration};
use futures::stream::{self, StreamExt};
pub struct Scanner {
timeout_ms: u64,
concurrency: usize,
}
impl Scanner {
pub async fn scan_ports(
&self,
host: &str,
ports: Vec<u16>,
) -> Vec<u16> {
stream::iter(ports)
.map(|port| self.check_port(host, port))
.buffer_unordered(self.concurrency)
.filter_map(|result| async { result })
.collect()
.await
}
async fn check_port(&self, host: &str, port: u16) -> Option<u16> {
let addr = format!("{}:{}", host, port);
let duration = Duration::from_millis(self.timeout_ms);
match timeout(duration, TcpStream::connect(&addr)).await {
Ok(Ok(_)) => Some(port),
_ => None,
}
}
}
#[tokio::main]
async fn main() {
let scanner = Scanner {
timeout_ms: 1000,
concurrency: 1000,
};
let ports: Vec<u16> = (1..=65535).collect();
let open_ports = scanner.scan_ports("target.com", ports).await;
println!("Open ports: {:?}", open_ports);
}
This scanner can check all 65,535 ports in under 2 minutes on a typical connection.
Here's a multi-threaded password cracker using Rust's rayon for parallelism:
use rayon::prelude::*;
use sha2::{Sha256, Digest};
use std::fs::File;
use std::io::{BufRead, BufReader};
fn hash_password(password: &str, salt: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(salt);
hasher.update(password.as_bytes());
hasher.finalize().to_vec()
}
fn crack_hash(
target_hash: &[u8],
salt: &[u8],
wordlist_path: &str,
) -> Option<String> {
let file = File::open(wordlist_path).ok()?;
let reader = BufReader::new(file);
let words: Vec<String> = reader.lines().filter_map(|l| l.ok()).collect();
words.par_iter()
.find_any(|word| {
let hash = hash_password(word, salt);
hash == target_hash
})
.cloned()
}
fn main() {
let target = hex::decode("5e884898da28047d...").unwrap();
let salt = b"random_salt";
if let Some(password) = crack_hash(&target, salt, "rockyou.txt") {
println!("Found password: {}", password);
}
}
Parsing network packets safely is crucial for IDS/IPS systems:
use nom::{
bytes::complete::take,
number::complete::{be_u8, be_u16, be_u32},
IResult,
};
#[derive(Debug)]
struct TcpHeader {
src_port: u16,
dst_port: u16,
seq_num: u32,
ack_num: u32,
flags: TcpFlags,
window: u16,
}
#[derive(Debug)]
struct TcpFlags {
syn: bool,
ack: bool,
fin: bool,
rst: bool,
psh: bool,
}
fn parse_tcp_header(input: &[u8]) -> IResult<&[u8], TcpHeader> {
let (input, src_port) = be_u16(input)?;
let (input, dst_port) = be_u16(input)?;
let (input, seq_num) = be_u32(input)?;
let (input, ack_num) = be_u32(input)?;
let (input, data_offset_flags) = be_u16(input)?;
let (input, window) = be_u16(input)?;
let (input, _checksum) = be_u16(input)?;
let (input, _urgent) = be_u16(input)?;
let flags = TcpFlags {
fin: (data_offset_flags & 0x01) != 0,
syn: (data_offset_flags & 0x02) != 0,
rst: (data_offset_flags & 0x04) != 0,
psh: (data_offset_flags & 0x08) != 0,
ack: (data_offset_flags & 0x10) != 0,
};
Ok((input, TcpHeader {
src_port,
dst_port,
seq_num,
ack_num,
flags,
window,
}))
}
Several production security tools are now written in Rust:
A modern port scanner that's significantly faster than nmap:
# Scan all 65535 ports in 3 seconds
rustscan -a target.com --ulimit 5000
While not strictly a security tool, ripgrep is widely used for log analysis and forensics. Its performance makes it ideal for searching through large datasets.
Checks Rust projects for known vulnerabilities:
cargo audit
A content discovery tool written in Rust:
feroxbuster -u https://target.com -w wordlist.txt
If you're considering migrating existing tools to Rust, here's a pragmatic approach:
Identify the most security-sensitive or performance-critical parts:
// Example: Replace just the hashing component
#[no_mangle]
pub extern "C" fn fast_hash(
data: *const u8,
len: usize,
out: *mut u8,
) {
let slice = unsafe { std::slice::from_raw_parts(data, len) };
let hash = blake3::hash(slice);
unsafe {
std::ptr::copy_nonoverlapping(
hash.as_bytes().as_ptr(),
out,
32,
);
}
}
Rust's FFI allows incremental adoption:
# Python calling Rust via ctypes
import ctypes
lib = ctypes.CDLL("./target/release/libsecurity_tools.so")
lib.fast_hash.argtypes = [ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t, ctypes.POINTER(ctypes.c_uint8)]
def hash_data(data: bytes) -> bytes:
output = (ctypes.c_uint8 * 32)()
lib.fast_hash(data, len(data), output)
return bytes(output)
The Rust ecosystem has excellent security-related crates:
| Crate | Purpose |
|---|---|
ring | Cryptographic operations |
rustls | TLS implementation |
nom | Binary parsing |
tokio | Async networking |
pcap | Packet capture |
x509-parser | Certificate parsing |
Rust's ownership system takes time to master:
// This won't compile - demonstrates ownership
fn process_data(data: Vec<u8>) {
println!("Processing {} bytes", data.len());
}
fn main() {
let data = vec![1, 2, 3];
process_data(data);
// process_data(data); // Error: data was moved
}
Rust's compile times can be significant for large projects. Mitigation strategies:
cargo check during developmentWhile growing rapidly, some areas still lack mature libraries compared to Python or Go.
Rust's combination of memory safety, performance, and modern tooling makes it increasingly attractive for security tool development. While the learning curve is real, the benefits—especially for security-critical code—are substantial.
For new security tools, Rust should be a serious consideration. For existing tools, consider migrating performance-critical or security-sensitive components first.
Have questions about Rust in security? Feel free to reach out or check out the Rust Security Community.