第二十九 对称密码算法

对称密码算法可以分为序列密码和分组密码。

序列密码(流密码)

流密码(Stream Cipher) 目前,公开的序列密码算法主要有RC4、SEAL等。常见的使用流密码的加密协议有 RC4 ,Salsa20 ,和 ChaCha 等。

序列密码也称为流密码(Stream Cipher),它是对称密码算法的一种。

ChaCha20-Poly1305 

ChaCha20-Poly1305是Google所采用的一种新式加密算法,性能强大.

 #[test]

    fn chacha20poly1305() {

        use chacha20poly1305::aead::{Aead, NewAead};

        use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};

        use data_encoding::HEXLOWER; // Or `XChaCha20Poly1305`

        let key_hex = hex!("98baa9548506c53497bae1b098e85cf26b1359baca7e31ad0c7e93b26e8e79d6");

        let key = Key::from_slice(&key_hex); // 32-bytes

        let cipher = ChaCha20Poly1305::new(key);


        let nonce = Nonce::from_slice(b"unique nonce"); // 12-bytes; unique per message


        let ciphertext = cipher

            .encrypt(nonce, b"plaintext message".as_ref())

            .expect("encryption failure!"); // NOTE: handle this error to avoid panics!


        println!("{}", HEXLOWER.encode(&ciphertext));


        let plaintext = cipher

            .decrypt(nonce, ciphertext.as_ref())

            .expect("decryption failure!"); // NOTE: handle this error to avoid panics!


        assert_eq!(&plaintext, b"plaintext message");

    }

 

XChaCha20Poly1305参考代码

fn encrypt_large_file(

    source_file_path: &str,

    dist_file_path: &str,

    key: &[u8; 32],

    nonce: &[u8; 19],

) -> Result<(), anyhow::Error> {

    let aead = XChaCha20Poly1305::new(key.as_ref().into());

    let mut stream_encryptor = stream::EncryptorBE32::from_aead(aead, nonce.as_ref().into());


    const BUFFER_LEN: usize = 500;

    let mut buffer = [0u8; BUFFER_LEN];


    let mut source_file = File::open(source_file_path)?;

    let mut dist_file = File::create(dist_file_path)?;


    loop {

        let read_count = source_file.read(&mut buffer)?;


        if read_count == BUFFER_LEN {

            let ciphertext = stream_encryptor

                .encrypt_next(buffer.as_slice())

                .map_err(|err| anyhow!("Encrypting large file: {}", err))?;

            dist_file.write(&ciphertext)?;

        } else {

            let ciphertext = stream_encryptor

                .encrypt_last(&buffer[..read_count])

                .map_err(|err| anyhow!("Encrypting large file: {}", err))?;

            dist_file.write(&ciphertext)?;

            break;

        }

    }


    Ok(())

}


fn decrypt_large_file(

    encrypted_file_path: &str,

    dist: &str,

    key: &[u8; 32],

    nonce: &[u8; 19],

) -> Result<(), anyhow::Error> {

    let aead = XChaCha20Poly1305::new(key.as_ref().into());

    let mut stream_decryptor = stream::DecryptorBE32::from_aead(aead, nonce.as_ref().into());


    const BUFFER_LEN: usize = 500 + 16;

    let mut buffer = [0u8; BUFFER_LEN];


    let mut encrypted_file = File::open(encrypted_file_path)?;

    let mut dist_file = File::create(dist)?;


    loop {

        let read_count = encrypted_file.read(&mut buffer)?;


        if read_count == BUFFER_LEN {

            let plaintext = stream_decryptor

                .decrypt_next(buffer.as_slice())

                .map_err(|err| anyhow!("Decrypting large file: {}", err))?;

            dist_file.write(&plaintext)?;

        } else if read_count == 0 {

            break;

        } else {

            let plaintext = stream_decryptor

                .decrypt_last(&buffer[..read_count])

                .map_err(|err| anyhow!("Decrypting large file: {}", err))?;

            dist_file.write(&plaintext)?;

            break;

        }

    }


    Ok(())

}

 

先压缩,后加密;先解密,后解压;

   fn compress(source: &str, destination: &str) -> Result<(), anyhow::Error> {

        let source = File::open(source).unwrap();

        let destination = File::create(destination).unwrap();

        match zstd_stream::copy_encode(&source, &destination, 7) {

            Ok(_) => {

                let metadata1 = source.metadata().unwrap();

                if let Ok(metadata2) = destination.metadata() {

                    let size = metadata2.file_size();

                    println!("compress success: {} => {}", metadata1.file_size(), size);

                }

            }

            Err(e) => {

                println!("copy_encode : {}", e)

            }

        }


        Ok(())

    }


    fn decompress(source: &str, destination: &str) -> Result<(), anyhow::Error> {

        // 解压zst文件

        if let Ok(source) = File::open(source) {

            if let Ok(destination) = File::create(destination) {

                zstd_stream::copy_decode(source, destination);

            }

        }


        Ok(())

    }


    #[test]

    fn test_xchacha20_poly1305() {

        use random::{rngs::OsRng, RngCore};

        let mut large_file_key = [0u8; 32];

        let mut large_file_nonce = [0u8; 19];

        OsRng.fill_bytes(&mut large_file_key);

        OsRng.fill_bytes(&mut large_file_nonce);


        //先压缩

        compress("large_file.txt", "large_file.zst");


        //后加密

        encrypt_large_file(

            "large_file.zst",

            "large_file.crypto",

            &large_file_key,

            &large_file_nonce,

        );


        //先解密

        decrypt_large_file(

            "large_file.crypto",

            "large_file.tmp.zst",

            &large_file_key,

            &large_file_nonce,

        );


        //后解压

        decompress("large_file.tmp.zst", "large_file_temp.txt");

    }

 

分组密码

最著名的分组密码是DES密码,而目前最为流行的分组密码算法为AES。

对称加密和分组加密的四种模式:详情

- ECB模式, 简单,有利于并行计算,误差不会被传递。需要考虑补齐(padding)

- CBC模式, 密码分组链接模式, 需要引入IV    1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

- CFB模式, 密码反馈模式

- OFB模式, 输出反馈模式

- CTR模式 计数模式  最大的优势是可以并行执行


 

填充方式

- NoPadding  不填充,在此填充下原始数据必须是分组大小的整数倍,非整数倍时无法使用该模式

- ZeroPadding  数据长度不对齐时使用0填充,否则不填充。

- PKCS1Padding  该填充模式是 RSA 加密中使用的,详见 RFC 2313。RSA 加密时,需要将原文填充至密钥大小,填充的格式为:

00 + BT + PS + 00 + D

- PKCS5Padding

- Pkcs7 (the default)  假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。

- Iso97971

- AnsiX923  填充至符合块大小的整数倍,填充值最后一个字节为填充的数量数,其他字节填 0

- Iso10126  填充至符合块大小的整数倍,填充值最后一个字节为填充的数量数,其他字节随机处理

PKCS5Padding 的块大小应为 8 个字节,而 PKCS7Padding 的块大小可以在 1~255 的范围内。但 SunJCE 的 Provider 实现中 PKCS5Padding 也按 PKCS7Padding 来进行处理了。

CryptoJS默认模式为CBC模式,采用Pkcs7填充方式。

AES

AES的明文分组长度为128位(16字节),密钥长度可以为128位(16字节)、192位(24字节)、256位(32字节),根据密钥长度的不同,AES分为AES-128、AES-192、AES-256三种。

  #[test]

    fn aes_gcm() {

        use aes_gcm::aead::{Aead, NewAead};

        use aes_gcm::{Aes256Gcm, Key, Nonce};

        use data_encoding::HEXLOWER;

        // Or `Aes128Gcm`

        // 256 bits(32 bytes) key

        // openssl rand -hex 32

        // hex!() : converting hexadecimal string literals to a byte array

        let key = Key::from_slice(&hex!(

            "c2c567b1151904db13374ea7aef181a4b8509e331a7d6e952a11781d29ebfe52"

        ));

        let cipher = Aes256Gcm::new(key);


        let nonce = Nonce::from_slice(b"unique nonce"); // 96-bits; unique per message


        let ciphertext = cipher

            .encrypt(nonce, b"plaintext message".as_ref())

            .expect("encryption failure!"); // NOTE: handle this error to avoid panics!


        println!("{}", HEXLOWER.encode(&ciphertext));

        println!("{}", encode(&ciphertext));

        let plaintext = cipher

            .decrypt(nonce, ciphertext.as_ref())

            .expect("decryption failure!"); // NOTE: handle this error to avoid panics!


        assert_eq!(&plaintext, b"plaintext message");


        //

        match File::open("C:\\data\\寒窑赋.txt") {

            Ok(f) => {

                let mut reader = BufReader::new(f);

                let ciphertext = cipher

                    .encrypt(nonce, reader.fill_buf().unwrap())

                    .expect("encryption failure!");

                println!("{:?}", encode(&ciphertext));


                let plaintext = cipher

                    .decrypt(nonce, ciphertext.as_ref())

                    .expect("decryption failure!");

                println!("{}", String::from_utf8(plaintext).unwrap());

            }

            Err(e) => println!("{}", e),

        }

    }

 

SM4

SM4分组密码算法正式成为ISO/IEC国际标准

extern crate rand as random;

fn rand_block() -> [u8; 16] {

    use random::prelude::*;

    // let mut rng = OsRng::new().unwrap();

    let mut rng = random::thread_rng();

    let mut block: [u8; 16] = [0; 16];

    rng.fill_bytes(&mut block[..]);


    println!("block:{}", HEXLOWER.encode(&block));

    block

}


    let key: [u8; 16] = [

        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,

        0x10,

    ];


    //SM4

    let cipher = Cipher::new(&key, Mode::Cbc).unwrap();


    let iv = rand_block();

    let poem = String::from("断头今日意如何?创业艰难百战多。此去泉台招旧部 ,旌旗十万斩阎罗。");

    let encrypt_bytes = cipher.encrypt(&poem.as_bytes(), &iv).unwrap();

    println!("{}", base64::encode(&encrypt_bytes));


    let mut all_bytes = Vec::<u8>::with_capacity(128);

    all_bytes.extend_from_slice(&key);

    all_bytes.extend_from_slice(&iv);

    all_bytes.extend_from_slice(&encrypt_bytes);


    std::fs::write("poem.crypto", &all_bytes);


    if let Ok(cipher_data) = std::fs::read("poem.crypto") {

        let cipher = Cipher::new(&cipher_data[0..16], Mode::Cbc).unwrap();

        let poem_bytes = cipher

            .decrypt(&cipher_data[32..], &cipher_data[16..32])

            .unwrap();

        let poem = String::from_utf8(poem_bytes).unwrap();

        println!("poem.crypto => {}", poem);

    }


 

use std::fs;


#[test]

fn sm4(){

    let key = rand_block();

    let cipher = Cipher::new(&key, Mode::Cbc).unwrap();


    let iv = rand_block();

   

    if let Ok(poem) = fs::read("why-rust.txt"){

        let encrypt_bytes = cipher.encrypt(&poem, &iv).unwrap();

        println!("{}", base64::encode(&encrypt_bytes));

    }

}

 


 

OpenSSL

openssl enc -sm4-ctr -pbkdf2 -e -in 银行卡.txt -out bank.txt -a

openssl enc -sm4-ctr -pbkdf2 -d -in bank.txt -out decode.txt -a

openssl enc -e -sm4 -in /tmp/1.txt -out /tmp/2.txt

国密算法介绍及OpenSSL实现

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值