数据的加解密
这不是一篇介绍加密算法的文章。主要记录下一些常见加密算法的
Rust
实现。
缘起
系统的某些数据资产需要下载到客户端使用,但是需要对这些数据资产进行加密处理。在客户端存储的是加密后的数据。运行时,通过客户端的私钥进行解密后加载到内存中使用。
思路就是:采用混合加密。用对称加密算法来加密数据,用非对称加密算法来加密对称算法的密钥。这也是网络通讯通常采用的加密方式。
服务器持有公钥,客户端持有私钥。每次客户端向服务端请求数据时,服务端都会动态生成一个对称算法的密钥,用该密钥对数据加密,然后用非对称加密算法来加密该密钥。服务端将加密数据和加密密钥一起传送给客户端,客户端就可以结合自己手里的私钥对密钥进行解密,再用对称密钥解密数据。
同时,为了便于传输,还需要使用Base64
对密钥和数据进行编解码。
对称加密算法的选择
在现代密码学中,有几种常用的对称加密算法:
- AES(Advanced Encryption Standard)
- 最常用的对称加密算法,性能高、安全性强。
- ChaCha20
- 现代流密码算法,广泛用于移动和网络应用。在TLS、HTTPS等协议中使用。
实践中,AES的Rust实现太难了,一些示例所使用的依赖过期,没调试通过。所以,直接使用了Chacha20
,拿来即用。
首先引入依赖项:
[dependencies]
rand = "0.8.5"
chacha20 = "0.9.1"
如下,提供了两个封装函数:随机生成对称密钥和利用对称算法对数据进行加解密:
use chacha20::ChaCha20;
use chacha20::cipher::{
KeyIvInit,
StreamCipher,
KeyInit
};
// 定义加密常量
const CHACHA_KEY_SIZE: usize = 32; // ChaCha20密钥长度
const NONCE_SIZE: usize = 12; // Nonce大小
// 生成ChaCha20密钥和Nonce
fn generate_chacha20_key() -> Vec<u8> {
let mut rng = rand::rngs::OsRng;
let mut full_key = [0u8; CHACHA_KEY_SIZE + NONCE_SIZE];
rng.fill_bytes(&mut full_key);
full_key.to_vec()
}
// 对称算法,加解密都是同样的方法
fn chacha20_code(mut data: Vec<u8>, full_key: &[u8]) -> anyhow::Result<Vec<u8>> {
// 分割密钥和Nonce
let key = &full_key[..CHACHA_KEY_SIZE];
let nonce = &full_key[CHACHA_KEY_SIZE..CHACHA_KEY_SIZE+NONCE_SIZE];
// 使用ChaCha20加密文件内容
let mut cipher = ChaCha20::new(key.into(), nonce.into());
cipher.apply_keystream(&mut data);
Ok(data)
}
非对称算法的选择
非对称加密算法(也称为公钥加密算法)是现代密码学中非常重要的一类算法。以下是几种主要的非对称加密算法:
- RSA(Rivest-Shamir-Adleman)
- 最广泛使用的非对称加密算法之一。基于大数因数分解的困难性,既可以用于加密,也可以用于数字签名。安全性依赖于大素数的乘积很难被因数分解。
- ECC(椭圆曲线密码学)
- 相对RSA,密钥更短,计算效率更高,在移动和物联网设备上应用广泛。安全性基于椭圆曲线上离散对数问题的困难性。
在这里选择了RSA加密算法,首先引入依赖项:
[dependencies]
rsa = "0.9.7"
非对称加密算法主要用来对对称加密密钥进行加解密:
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};
// 加密ChaCha20密钥
fn encrypt_chacha20_key(public_key: &RsaPublicKey, encrypted_key: &[u8]) -> anyhow::Result<Vec<u8>> {
let mut rng = rand::rngs::OsRng;
let encrypted_aes_key = public_key.encrypt( &mut rng, Pkcs1v15Encrypt, encrypted_key)?;
Ok(encrypted_aes_key)
}
// 解密ChaCha20密钥
fn decrypt_chacha20_key(private_key: &RsaPrivateKey, encrypted_key: &[u8]) -> anyhow::Result<Vec<u8>> {
let decrypted_aes_key = private_key.decrypt(Pkcs1v15Encrypt, encrypted_key)?;
Ok(decrypted_aes_key)
}
编码方式的选择
字节流还是不便于传输的,需要用Base64
将字节流编码为字符串以便于传输:
[dependencies]
base64 = "0.22.1"
密钥在生成时,是一个字节数组,也是需要进行Base64
编码的。不过,在传输前,需要用非对称算法对密钥进行加密:
use base64::prelude::*;
fn key_encode(public_key: &RsaPublicKey, key: &[u8]) -> anyhow::Result<String> {
// rsa encrypt
let temp = encrypt_chacha20_key(&public_key, key)?;
// base64 encode
let temp = BASE64_STANDARD.encode(&temp);
Ok(temp)
}
fn key_decode(private_key: RsaPrivateKey, encode_key: String) -> anyhow::Result<Vec<u8>> {
// base64 decode
let temp = BASE64_STANDARD.decode(encode_key.as_bytes())?;
// rsa decrypt
let temp = decrypt_chacha20_key(&private_key, &temp)?;
Ok(temp)
}
对数据的Base64
编解码:
n resource_encode(resource: Vec<u8>, key: &[u8]) -> anyhow::Result<String> {
// chacha20 encrypt
let temp = chacha20_code(resource, key)?;
// base64 encode
let temp = BASE64_STANDARD.encode(&temp);
Ok(temp)
}
fn resource_decode(resource: Vec<u8>, key: &[u8]) -> anyhow::Result<String> {
// base64 decode
let temp = BASE64_STANDARD.decode(&resource)?;
// rsa decrypt
let temp = chacha20_code(temp, key)?;
// bytes to string
let temp = String::from_utf8(temp)?;
Ok(temp)
}
私钥的存储
非对称加密的私钥将以符合PKCS#8
的标准格式进行存储和读取:
fn generate_pkcs8(private_key: &RsaPrivateKey, path: &PathBuf) -> anyhow::Result<()> {
private_key.write_pkcs8_pem_file(path, LineEnding::LF)
.map_err(|e| anyhow::anyhow!(e.to_string()))
}
fn retrieve_pkcs8(path: &PathBuf) -> anyhow::Result<RsaPrivateKey> {
let content = fs::read_to_string(path)?;
RsaPrivateKey::from_pkcs8_pem(&content)
.map_err(|e| anyhow::anyhow!(e.to_string()))
}
私钥是极其敏感的信息,必须得到妥善保管和保护。
最后给出完整示例
示例中的公私钥是临时生成的。实践中公私密钥对
可以通过多种途径生成并应妥善保管。
fn main() -> anyhow::Result<()> {
let mut rng = rand::rngs::OsRng;
let bits = 2048;
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let public_key = RsaPublicKey::from(&private_key);
// 服务端加密
let content = fs::read("test.xml")?;
let chacha20_key = generate_chacha20_key();
let encode_data = resource_encode(content, &chacha20_key)?;
let encode_key = key_encode(&public_key, &chacha20_key)?;
println!("Data base64: {}", &encode_data);
println!("Key base64: {}", &encode_key);
// 客户端解密
let chacha20_key2 = key_decode(private_key, encode_key)?;
let decode_data = resource_decode(encode_data.into_bytes(), &chacha20_key2)?;
println!("Raw Data: {}", &decode_data);
Ok(())
}