我尝试使用
aes-gcm
和 sodiumoxide
Rust 板条箱,在这两种情况下,我都遇到了一个问题,即我尝试加密大于块缓冲区大小的文件,我将得到一个不可解密的无效密文。
这是我希望该程序运行的示例条件:
我需要加密 50 GB 的文件,但只有 2 GB 的内存和 4 GB 的存储空间,因此我无法将整个文件放入内存中,也无法将其分块写入新文件。我需要以某种方式用加密的文件替换文件的块。
这是我的代码:
use clap::Parser;
use sodiumoxide::crypto::secretbox;
use sodiumoxide::crypto::secretbox::{Key, Nonce};
use sodiumoxide::hex;
use std::fs::OpenOptions;
use std::io::{self, Read, Seek, SeekFrom, Write};
const CHUNK_SIZE: usize = 1024; // Adjust chunk size as needed
#[derive(Parser)]
struct Options {
#[clap(subcommand)]
command: Command,
}
#[derive(Parser)]
enum Command {
Encrypt,
Decrypt,
}
fn main() -> io::Result<()> {
let args = Options::parse();
match args.command {
Command::Encrypt => encrypt_file("poemfortest.txt"),
Command::Decrypt => decrypt_file("poemfortest.txt"),
}
}
fn encrypt_file(input_file_path: &str) -> io::Result<()> {
let input_file = OpenOptions::new()
.read(true)
.write(true)
.open(input_file_path)?;
// Initialize sodiumoxide library
sodiumoxide::init().expect("Failed to initialize sodiumoxide");
let rawkey =
hex::decode("ba72744932db553b55cb944aa8e5739cf23a7f668c32d164c51ce09d4d631160").unwrap();
let rawnonce = hex::decode("48ccdb552de220538ac1667e7e99054fd39ad417c5d83c6e").unwrap();
// Generate the key and nonce used for encryption
let key = Key::from_slice(&rawkey).expect("Invalid key");
let nonce = Nonce::from_slice(&rawnonce).expect("Invalid nonce");
let mut input_file = input_file;
let mut buffer = [0u8; CHUNK_SIZE];
let mut offset: u64 = 0;
loop {
let bytes_read = input_file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
// Encrypt the chunk
let encrypted_chunk = secretbox::seal(&buffer[..bytes_read], &nonce, &key);
// Seek back to the beginning of the chunk
input_file.seek(SeekFrom::Start(offset))?;
// Write the encrypted chunk to the input file
input_file.write_all(&encrypted_chunk)?;
// Move the offset to the next chunk
offset += bytes_read as u64;
}
println!("Encryption completed successfully.");
Ok(())
}
fn decrypt_file(input_file_path: &str) -> io::Result<()> {
let input_file = OpenOptions::new()
.read(true)
.write(true)
.open(input_file_path)?;
// Initialize sodiumoxide library
sodiumoxide::init().expect("Failed to initialize sodiumoxide");
let rawkey =
hex::decode("ba72744932db553b55cb944aa8e5739cf23a7f668c32d164c51ce09d4d631160").unwrap();
let rawnonce = hex::decode("48ccdb552de220538ac1667e7e99054fd39ad417c5d83c6e").unwrap();
// Generate the key and nonce used for decryption
let key = Key::from_slice(&rawkey).expect("Invalid key");
let nonce = Nonce::from_slice(&rawnonce).expect("Invalid nonce");
let mut input_file = input_file;
let mut buffer = [0u8; CHUNK_SIZE];
let mut offset: u64 = 0;
loop {
let bytes_read = input_file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
// Decrypt the chunk
let decrypted_chunk = match secretbox::open(&buffer[..bytes_read], &nonce, &key) {
Ok(decrypted) => decrypted,
Err(_) => {
eprintln!("Error: Failed to decrypt chunk.");
return Ok(());
}
};
// Seek back to the beginning of the chunk
input_file.seek(SeekFrom::Start(offset))?;
// Write the decrypted chunk to the input file
input_file.write_all(&decrypted_chunk)?;
// Move the offset to the next chunk
offset += bytes_read as u64;
}
println!("Decryption completed successfully.");
Ok(())
}
我希望它适用于大于块缓冲区大小的文件,但事实证明我的分块数据实现很糟糕。
我会使用内存映射文件,然后对其进行加密,并在最后存储随机数/身份验证标签。在我看来,使用 GCM 或 ChaCha20/Poly1305 对于这种目的来说都有点时髦。 ChaCha20 稍好一些,因为它提供了更大的随机数和最大消息大小,但要使实现正确运行可能会很棘手。同时,使用SHA-256没有问题,并且许多处理器都具有SHA-256加速。如果 SHA-512 在 64 位系统上很慢,请尝试 SHA-512,您可能会感到惊讶。
我使用 ChatGPT 生成的示例,因为我不是 Rust 程序员,但我确实检查了代码并对其进行了多次调整,并且代码是根据我的严格规范生成的。我主要保持原样是为了避免语法错误。
use aes::Aes256;
use ctr::cipher::{NewCipher, StreamCipher};
use ctr::Ctr128BE;
use hmac::{Hmac, Mac, NewMac};
use memmap2::MmapOptions;
use rand::{RngCore, rngs::OsRng};
use sha2::Sha256;
use std::fs::{File, OpenOptions};
use std::io::{self, Seek, Write};
use std::path::Path;
const CHUNK_SIZE: usize = 1 * 1024 * 1024; // 1 MiB
/// Encrypts a file in place using AES in CTR mode and appends an HMAC for authentication.
///
/// This function performs in-place encryption of the file specified by `path` using
/// AES encryption in Counter (CTR) mode. After encryption, a nonce generated for the encryption
/// and an HMAC (Hash-based Message Authentication Code) are appended to the file for
/// authentication purposes. The HMAC is calculated over the entire encrypted file content
/// including the nonce, using SHA-256 as the hash function. This approach ensures both
/// confidentiality and integrity of the file data.
///
/// # Parameters
/// - `path`: The path to the file to be encrypted. The file will be modified in place.
/// - `encryption_key`: A 32-byte key used for AES encryption. Must be securely generated and managed.
/// - `hmac_key`: A 32-byte key used for HMAC calculation. Must be different from `encryption_key`
/// and securely generated and managed.
///
/// # Returns
/// - `Ok(())` on success, indicating that the file has been encrypted and the nonce and HMAC
/// have been successfully appended to the file.
/// - `Err(io::Error)` on failure, with an error message indicating the type of error that occurred
/// (e.g., file not found, permission denied, I/O error during processing, etc.).
///
/// # Security Considerations
/// - The `encryption_key` and `hmac_key` must be kept secret and securely managed. Exposure of
/// these keys can compromise the security of the encrypted data.
/// - Ensure that the system's random number generator is secure for nonce generation.
///
/// # Example Usage
/// ```
/// let path = Path::new("path/to/file.txt");
/// let encryption_key = [0u8; 32]; // Use a secure method to generate/store the encryption key
/// let hmac_key = [1u8; 32]; // Use a secure, separate key for HMAC
///
/// if let Err(e) = encrypt_file_aes_ctr_hmac(path, &encryption_key, &hmac_key) {
/// eprintln!("Error encrypting file: {}", e);
/// }
/// ```
///
fn encrypt_file_aes_ctr_hmac(path: &Path, encryption_key: &[u8; 32], hmac_key: &[u8; 32]) -> io::Result<()> {
let file_len = File::open(path)?.metadata()?.len() as usize;
let mut file = OpenOptions::new().read(true).write(true).open(path)?;
// Generate a random nonce
let mut nonce = [0u8; 16];
OsRng.fill_bytes(&mut nonce);
// Initialize HMAC
let mut hmac = Hmac::<Sha256>::new_from_slice(hmac_key).expect("HMAC can take key of any size");
// Initialize the encryption cipher in CTR mode
let mut cipher = Ctr128BE::<Aes256>::new_from_slices(encryption_key, &nonce).unwrap();
for offset in (0..file_len).step_by(CHUNK_SIZE) {
let chunk_size = std::cmp::min(CHUNK_SIZE, file_len - offset);
// Memory-map the chunk
let mmap_opts = MmapOptions::new().offset(offset as u64).len(chunk_size);
let mut mmap = unsafe { mmap_opts.map_mut(&file)? };
// Encrypt the chunk in place
cipher.apply_keystream(&mut mmap);
// Update HMAC with the encrypted chunk
hmac.update(&mmap);
}
// Include the nonce in the HMAC calculation after processing all chunks
hmac.update(&nonce);
// Append the nonce to the file
file.set_len(file_len as u64 + nonce.len() as u64)?;
file.seek(io::SeekFrom::End(0))?; // NOTE: probably not needed, we're already at the end
file.write_all(&nonce)?;
// Finalize HMAC calculation
let hmac_result = hmac.finalize().into_bytes();
// Append HMAC to the file, after the nonce
file.write_all(&hmac_result)?;
Ok(())
}
fn main() {
let path = Path::new("example.txt");
let encryption_key = [0u8; 32]; // Securely generate/store the encryption key
let hmac_key = [1u8; 32]; // Use a secure, separate key for HMAC
if let Err(e) = process_file_in_chunks(path, &encryption_key, &hmac_key) {
eprintln!("Error processing file: {}", e);
}
}
用户请注意: