1. はじめに
ChaCha20-Poly1305 は、暗号化と認証を同時に行う AEAD (Authenticated Encryption with Associated Data) と呼ばれる暗号方式です。
昔いろいろとあって IETF(Internet Engineering Task Force) で ChaCha20-Poly1305 を標準化が進められたようです。
パッケージも用意されており、Rust で簡単に使うことができます。
2. パッケージの追加
Cargo.toml に必要となるパッケージを追加します。
[dependencies]
chacha20poly1305 = "0.10.1"
rand = "0.8.5"
base64 = "0.22.1"
3. 以下、ソース
use base64::{engine::general_purpose, Engine as _};
use chacha20poly1305::aead::{Aead, KeyInit, OsRng};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use chacha20poly1305::aead::generic_array::typenum::U32;
use chacha20poly1305::aead::generic_array::GenericArray;
use rand::RngCore;
use std::str;
// ChaCha20Poly1305 で暗号化されたデータを復号する
fn decrypt(base_key: &[u8], nonce: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let key = Key::from_slice(base_key);
let cipher = ChaCha20Poly1305::new(key);
let nonce = Nonce::from_slice(nonce);
cipher
.decrypt(nonce, ciphertext)
.expect("decryption failure!")
}
// ChaCha20Poly1305 でデータを暗号化する
fn encrypt(base_key: &[u8], plaintext: &[u8]) -> (Vec<u8>, Vec<u8>) {
let key = Key::from_slice(base_key);
let cipher = ChaCha20Poly1305::new(key);
// 12バイトのNonceを生成
let mut nonce = [0u8; 12];
OsRng.fill_bytes(&mut nonce);
let nonce = Nonce::from_slice(&nonce);
let ciphertext = cipher
.encrypt(nonce, plaintext)
.expect("encryption failure!");
(nonce.to_vec(), ciphertext)
}
// 文字列を GenericArray に変換する
fn string_to_generic_array(s: &str) -> GenericArray<u8, U32> {
let bytes = general_purpose::STANDARD
.decode(s)
.expect("Decoding failed");
GenericArray::clone_from_slice(&bytes)
}
fn main() {
// 暗号化キーを生成
let base_key = ChaCha20Poly1305::generate_key(&mut OsRng);
println!("Base key: {:?}", base_key);
let base_key_str = general_purpose::STANDARD.encode(base_key);
println!("Base key: {:?}", base_key_str);
let base_key = string_to_generic_array(&base_key_str);
println!("Base key: {:?}", base_key);
let plaintext = "Hello, world!".as_bytes();
let (nonce, ciphertext) = encrypt(base_key.as_slice(), plaintext);
println!("Nonce: {:?}", nonce);
println!("Ciphertext: {:?}", ciphertext);
let nonce_str = general_purpose::STANDARD.encode(&nonce);
let ciphertext_str = general_purpose::STANDARD.encode(&ciphertext);
println!("{}", nonce_str);
println!("{}", ciphertext_str);
let decrypted_plaintext = decrypt(base_key.as_slice(), &nonce, &ciphertext);
println!(
"Decrypted: {:?}",
String::from_utf8(decrypted_plaintext).unwrap()
);
}
キーとNonceを使ってワンタイムキーを生成し暗号化を行います。
暗号化されたデータは、復号するために必要なNonceと一緒に扱う必要があります。
キーに関してはアプリ間で共通。Nonceはメッセージごとに異なる値を使うことが必要です。
4. 復号のみ
fn main() {
let base_key = general_purpose::STANDARD
.decode("base_key_str で出力された文字列")
.expect("Failed to decode base key");
let nonce = general_purpose::STANDARD
.decode("nonce_str で出力された文字列")
.expect("Failed to decode nonce");
let ciphertext = general_purpose::STANDARD
.decode("ciphertext_str で出力された文字列")
.expect("Failed to decode ciphertext");
println!("Base key: {:?}", base_key);
println!("Nonce: {:?}", nonce);
println!("Ciphertext: {:?}", ciphertext);
let decrypted_plaintext = decrypt(&base_key, &nonce, &ciphertext);
println!(
"Decrypted: {:?}",
String::from_utf8(decrypted_plaintext).unwrap()
);
}
.env にパスワードなど直書きするのではなく、事前に暗号化しておいて、復号して使うという使い方もできます。
5. まとめ
細かい仕様については、参考サイトを参照してください。
正直解説を読んでいても、よくわからない部分が多かったです。
平文のサイズが 512 bits 以上の場合、Counter がインクリメントして・・・とか、実際どうなるの?といった感じです。おそらくいい感じにしてくれると思いたい。
最後に書いた通り、.env でパスワードを暗号化するためだけに用意したコードなので実用性はあまりないかもしれません。
似たようなコードを書く際の一助になればと思います。
今回の記事も、Github Copilot が7割くらい書いてくれました。
A. 参考サイト
ChaCha20-Poly1305の解説と実装
ChaCha20をRustで実装してみた
新しいTLSの暗号方式ChaCha20-Poly1305
コメント