AES-GCM クレートを使って、パスワードをハッシュ化する処理を実装します。
AES-GCM でパスワードをハッシュ化する
crypto クレート
crates.io で crypto と検索すると、RustCrypto: crypto crate がヒットします。
これは、Rust で暗号システムを構築するためのリソースで暗号化に関するクレート郡をまとめたものです。
AES-GCM クレート
今回は暗号化をしたいので、RustCrypto のクレート群のうちの1つで認証付き暗号(Authenticated Encryption with Associated Data)をおこなうことができる AES-GCM クレートを使いたいと思います。
AES-GCM は純粋な Rust で実装されているのが特徴の1つです。
AES は2000年のコンペ方式で標準化されたアルゴリズムであり、CTR モードも1979年に論文に提出され十分に研究がされているため、専門家が十分に検証した強いアルゴリズムです。
GCM はそれに MAC を組み合わせて完全性と認証性を加えた方式で、一般的な暗号化では十分に強力だと言えます。
encrypt と decrypt を実装する
Cargo.toml
[dependencies]
anyhow = "1.0.64"
aes-gcm = "0.10.1"
data-encoding = "2.3.2"
rand = "0.8.5"
以下のクレートを使用します。
- anyhow:エラー処理を楽にしてくれる便利なクレート
- aes-gcm:ハッシュ化で使用するクレート
- data-encoding:データエンコードで使用するクレート
- rand:乱数を生成するために使用するクレート
main.rs
実装したサンプルコードです。
use aes_gcm::aead::generic_array::GenericArray;
use aes_gcm::aead::Aead;
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use anyhow::anyhow;
use data_encoding::HEXLOWER;
use rand::seq::SliceRandom;
use std::str;
#[derive(Debug)]
struct EncryptionKey(String);
#[derive(Debug)]
struct EncryptionNonce(String);
impl From<String> for EncryptionKey {
fn from(key: String) -> Self {
Self(key)
}
}
impl From<String> for EncryptionNonce {
fn from(nonce: String) -> Self {
Self(nonce)
}
}
const RAND_BASE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const KEY_SIZE: usize = 32;
const NONCE_SIZE: usize = 12;
fn main() -> anyhow::Result<()> {
let (encryption_key, encryption_nonce) = init()?;
let key = encryption_key.0.as_bytes();
let nonce = encryption_nonce.0.as_bytes();
// contents to be encrypted
let contents = "plain text".to_string();
// encryption
let encrypted_contents =
aes_encrypt(contents.as_bytes(), key, nonce).map_err(|e| anyhow!(e))?;
println!("{:?}", encrypted_contents);
// encode
let encoded_contents = HEXLOWER.encode(&encrypted_contents);
println!("{}", encoded_contents);
// decode
let decoded_contents = HEXLOWER
.decode(encoded_contents.as_ref())
.map_err(|e| anyhow!(e))?;
println!("{:?}", decoded_contents);
// decryption
let plain_text = aes_decrypt(&encrypted_contents, key, nonce).map_err(|e| anyhow!(e))?;
let decrypted_contents: &str = str::from_utf8(&plain_text)?;
println!("{}", decrypted_contents);
assert_eq!(&contents, decrypted_contents);
Ok(())
}
fn init() -> anyhow::Result<(EncryptionKey, EncryptionNonce)> {
let key = gen_rand_string(KEY_SIZE)?.into();
let nonce = gen_rand_string(NONCE_SIZE)?.into();
println!("{:?}, {:?}", key, nonce);
Ok((key, nonce))
}
fn gen_rand_string(size: usize) -> anyhow::Result<String> {
let mut rng = &mut rand::thread_rng();
String::from_utf8(
RAND_BASE
.as_bytes()
.choose_multiple(&mut rng, size)
.cloned()
.collect(),
)
.map_err(|e| anyhow!(e))
}
fn aes_encrypt(contents: &[u8], key: &[u8], nonce: &[u8]) -> anyhow::Result<Vec<u8>> {
let key = GenericArray::from_slice(key);
let nonce = Nonce::from_slice(nonce);
// encryption
let cipher = Aes256Gcm::new(key);
cipher
.encrypt(nonce, contents.as_ref())
.map_err(|e| anyhow!(e))
}
fn aes_decrypt(cipher_text: &[u8], key: &[u8], nonce: &[u8]) -> anyhow::Result<Vec<u8>> {
let key = GenericArray::from_slice(key);
let nonce = Nonce::from_slice(nonce);
// decryption
let cipher = Aes256Gcm::new(key);
cipher.decrypt(nonce, cipher_text).map_err(|e| anyhow!(e))
}
以下が、実装時のポイントです。
KEY
は32バイトである必要があるので、KEY_SIZE
で32を指定NONCE
は12バイトである必要があるので、NONCE_SIZE
で12を指定- 暗号化で使用するキーとノンスは、
gen_rand_stinrg
で生成したランダムな文字列 - 暗号化されたデータを確認するため、
HEXLOWER
でエンコードとデコードした結果を出力
また、値などを確認するために、println
では以下の項目を出力しています。
- キー
EncryptionKey
とノンスEncryptionNonce
- 暗号化後のデータ
- エンコード結果
- デコード結果
- 復号後の平文
Nonce (IV) を使用する際の注意点
「Nonce-Disrespecting Adversaries: Practical Forgery Attacks on GCM in TLS」という脆弱性情報が公開されています。
TLSのAES-GCMに対して初期ベクトル (IV) が再利用されると実用的な攻撃が可能であることが示されました。
そのことから、以下の要件のうち少なくとも 1 つは満たすようにしたほうが良さそうです。
- 暗号化毎に異なる鍵を使用する
- 生成回数が 248 回以下の要件でのみ利用する
- IVに乱数でなく、カウンター値を利用する
まとめ
AES-GCM での暗号化の実装をしてみました。
暗号化についての研究にも触れることができて、面白かったです。(難しいけど)
ハッシュ化については、別の記事にまとめてあるのでチェックしてみてください!
参考
また、GitHub でもサンプルコードを公開してます。
コメント