对称密码算法可以分为序列密码和分组密码。
序列密码(流密码)
流密码(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");
}
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
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