日本の山岳一覧・百名山 API を開発しましたCLICK !

Rust | AES-GCM で文字列を暗号化する

Rust AES-GCM 暗号化
  • URLをコピーしました!

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 でもサンプルコードを公開してます。

Rust AES-GCM 暗号化

この記事が気に入ったら
フォローしてね!

  • URLをコピーしました!

コメント

コメントする

目次