如何分块加密大文件?

问题描述 投票:0回答:1

我尝试使用

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(())
}

我希望它适用于大于块缓冲区大小的文件,但事实证明我的分块数据实现很糟糕。

rust encryption buffer
1个回答
0
投票

我会使用内存映射文件,然后对其进行加密,并在最后存储随机数/身份验证标签。在我看来,使用 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);
    }
}

用户请注意:

  • 此代码跟踪文件的哪一部分正在被加密 - 任何错误或关闭都可能导致文件部分加密,恢复的希望很小(复制然后加密可能会起作用,或者可能保持文件末尾的状态,当然首先写入随机数)
  • 可以使用单个密钥,如果同时用于 HMAC 和 AES,则几乎没有机会获取有关密钥的信息,但是最佳实践是派生或使用两个单独的密钥
  • 以这种方式加密太多文件可能会在随机数中产生冲突,以这种方式加密的文件越多,发生这种情况的可能性就越高,您可以使用密钥派生函数为每个文件创建一个密钥(对)
© www.soinside.com 2019 - 2024. All rights reserved.