「Qtの応用 - AES」の版間の差分
| 598行目: | 598行目: | ||
  class SecureAESCrypto {  |   class SecureAESCrypto {  | ||
  public:  |   public:  | ||
     //   |      // 安全な鍵の生成  | ||
     static QByteArray generateKey()  |      static QByteArray generateKey()  | ||
     {  |      {  | ||
| 609行目: | 609行目: | ||
     }  |      }  | ||
     //   |      // 安全な初期化ベクトル (IV) の生成  | ||
     static QByteArray generateIV()  |      static QByteArray generateIV()  | ||
     {  |      {  | ||
| 619行目: | 619行目: | ||
        return iv;  |         return iv;  | ||
     }  |      }  | ||
    // 平文を暗号化  | |||
     static QByteArray encrypt(const QByteArray &plaintext, const QByteArray &key, QByteArray &iv)  |      static QByteArray encrypt(const QByteArray &plaintext, const QByteArray &key, QByteArray &iv)  | ||
     {  |      {  | ||
| 638行目: | 639行目: | ||
     }  |      }  | ||
    // 暗号文を復号  | |||
     static QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv)  |      static QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv)  | ||
     {  |      {  | ||
| 656行目: | 658行目: | ||
     }  |      }  | ||
    // 暗号文と初期化ベクトル (IV) をファイルに保存  | |||
     static bool saveEncryptedData(const QString &filename, const QByteArray &data, const QByteArray &iv)  |      static bool saveEncryptedData(const QString &filename, const QByteArray &data, const QByteArray &iv)  | ||
     {  |      {  | ||
| 672行目: | 675行目: | ||
     }  |      }  | ||
    // ファイルから暗号文と初期化ベクトル (IV) を読み込む  | |||
     static bool loadEncryptedData(const QString &filename, QByteArray &data, QByteArray &iv)  |      static bool loadEncryptedData(const QString &filename, QByteArray &data, QByteArray &iv)  | ||
     {  |      {  | ||
| 767行目: | 771行目: | ||
<u>ファイルの暗号化や大量のデータの暗号化を行う場合は、メモリ使用量を考慮してストリーミング暗号化を行う。</u><br>  | <u>ファイルの暗号化や大量のデータの暗号化を行う場合は、メモリ使用量を考慮してストリーミング暗号化を行う。</u><br>  | ||
<br>  | <br>  | ||
==== サンプルコード : CFBモード ====  | ==== サンプルコード : CFBモード ====  | ||
以下の例では、CFBモードを使用して、暗号化および復号を行っている。<br>  | 以下の例では、CFBモードを使用して、暗号化および復号を行っている。<br>  | ||
2024年9月4日 (水) 06:39時点における版
概要
AESは、DESおよび3DESに代わって規格化された共通鍵暗号方式である。
ブロック長は128ビット、鍵長は128ビット・192ビット・256ビットが選択できる。
AESのソースコードは、Brian Gladman氏のWebサイトで公開されている。
アセンブラで記述されたもの(高速)とC言語で記述されたものが公開されている。
また、GithubにQt AESライブラリが公開されている。
ブロック暗号化モード
AESには、ブロック暗号化モードという機能がある。
最も単純なのがECBモードであり、データを16[byte]ごとに区切って暗号化する。
しかし、Birthday Attack等に弱いという欠点があるため、ECBモードは使用するべきではない。
- CBC
- 暗号文ブロック連鎖モード(Cipher Block Chaining)
 
 - OFB
- 出力フィードバックモード(Oftput Feed Back)
 
 - CFB
- 暗号フィードバックモード(Cipher Feed Back)
 
 - ECB
- 暗号ブックモード(Electric Code Book)
 
 
この他にも、PCBCやCounter Method等の新しい暗号利用モードも考案されている。
ECBモード
ECBモードは、1ブロックずつ単純に処理する。
ただし、ECBモードはブロック単位処理の裏をかいた暗号文一致攻撃をに弱い欠点がある。
暗号ブロックが一致した場合は復号した平文は一致するため、重要な秘密が漏れる可能性が高いと考えられる。
特定の暗号ブロックに対応する平文ブロックが1度でも知られると、同一の値を持つ暗号ブロックは全て解読される。
実際には、暗号文全体のうち部分的な平文は状況的に推測できるということは少なくないため、長い平文を暗号化する場合は非推奨である。
また、ブロック単位の差し替え等の暗号文改竄攻撃にも弱いという問題がある。
改竄攻撃を成功させるために、鍵を入手したり平文を正確に把握する必要が無いため、注意が必要である。
AESを使用した商用ソフトウェアにおいて、ECBモードが使用されることはほぼ無い。
CBCモード
CBCモードは、前の平文ブロックを暗号化した結果を、次の平文にXOR演算によって重ね合わせ、その結果に対して暗号化処理を行う。
最初のブロックを暗号化する場合、前の暗号文の最後のブロックを利用するか、または外部から与えた初期ベクトルを使用する。
前の暗号化結果が次のブロックに連鎖されるので連鎖モードと呼ぶ。
同一の平文から同一の暗号文が生成される可能性は極めて低いため、同一の平文が続く場合も安心して使用できる。
CBCモードに対する暗号文一致攻撃は、以下のように考えられる。
暗号文ブロックに対応する平文がとする時、偶然にも、であった場合、以下の攻撃が成立する。
それぞれの前の暗号文をCk_1、Cj_1とする時、以下が成立する。
また、CBCモードの手順から、以下が成立する。
すると、2つの平文の間に生じる差分は入手できてしまう。
しかし、このような攻撃が成功する確率は非常に稀であるといえる。
CBCモードの初期値は、攻撃者から見えても構わないことになっているが、毎回違う値を利用することが推奨されている。
CBCモードは、暗号化がランダムアクセスに適さないため、暗号化は先頭から順次行う必要がある。
また、復号がランダムアクセスに適しており、復号はランダムに選択した着目ブロックに対して操作できる。(1つ前の暗号文ブロックは必要である)
OFBモード
OFBモードは、初期ベクトルを暗号化して、それをまた暗号化して、次々と乱数を生成する。
その乱数列を、XOR演算によって平文に重ね合わせて、暗号化処理を行う。
したがって、ブロック暗号をストリーム暗号のように使用する。
OFBモードの初期値は、攻撃者から見えても構わないことになっているが、同じ鍵を使用する場合、必ず、初期値は毎回違う値を使用する。
同一の初期値と鍵の組み合わせから生じる乱数列は常に同一であるため、
平文と暗号文の組が1組でも攻撃者の手に渡ってしまうと、そこから乱数列が解ってしまい、
同一の初期値と鍵で暗号化した全ての暗号文が解読されてしまう。
OFBモードは、暗号化も復号化もランダムアクセスに適していないため、どちらも先頭から順次行う必要がある。
最近、人気のある暗号化モードにカウンタモード(またはカウンタメソッド)がある。
カウンタモードは、先頭からのブロック番号を求めてその値を暗号化して、その数値を乱数として使用する。
カウンタモードであれば、安全性が若干損なう可能性があるが、暗号化も復号もランダムに行えて便利である。
初期化ベクトル
カウンタモード (CTR)
CTRモードでは、カウンタ値を初期化ベクトルとして使用する。
各ブロックごとにカウンタをインクリメントすることにより、一意の値を生成する。
GCMモード
GCMモード等では、ナンス (使い捨ての数値) とカウンタを組み合わせて初期化ベクトルを生成する。
初期化ベクトル生成時の注意点
- 予測可能性
- 自動生成されたIV (初期化ベクトル) が予測可能であってはいけない。
 - 例えば、単純なインクリメント方式は避けるべきである。
 
 - 一意性
- 同じキーで同じIV (初期化ベクトル) を再利用しないことが重要である。
 - 特にストリーム暗号的なモード (CTRモード, GCMモード) では致命的な脆弱性につながる。
 
 - ランダム性
- 可能な限り、暗号学的に安全な乱数生成器を使用してIV (初期化ベクトル) を生成すべきである。
 
 - 通信
- IV (初期化ベクトル) は、一般的に、暗号文と一緒に送信する必要がある。
 - 多くの場合、暗号文の先頭に付加される。
 
 - 長さ
- IV (初期化ベクトル) の長さは、使用する暗号化モードに応じて適切に設定する必要がある。
 - 多くの場合、ブロックサイズと同じである。
 
 
自動生成されたIV (初期化ベクトル) を使用することにより、同じ平文でも毎回異なる暗号文が生成されて、セキュリティが向上する。
使用例: CBCモード
サンプルコード
以下の例では、QCryptoクラスを使用して、CBCモードでの暗号化と復号を行っている。
- キー管理
- setKeyメソッドでパスワードからキーを生成する。
 - キーが設定されていない場合は、暗号化・復号操作は失敗する。
 - ただし、以下の例ではパスワードからキーを生成しているが、実務では、より安全なキー管理方法を使用すべきである。
 
 - IV (初期化ベクトル) の自動生成
- 暗号化時において、自動的に新しいランダムなIV (初期化ベクトル) を生成して、IV (初期化ベクトル) を暗号文の先頭に付加して送信する。
 - 復号時において、暗号文からIV (初期化ベクトル) を自動的に取り出して使用する。
 
 - パディング
- PKCS7パディングを使用しており、復号時にパディングの妥当性を確認する。
 
 
 # CMakeLists.txtファイル
 
 # ...略
 
 # Pkg-configの使用
 find_package(PkgConfig REQUIRED)
 
 # Qtライブラリの検索
 find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
 find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)
 
 # OpenSSLライブラリの検索
 find_package(OpenSSL REQUIRED)
 
 # ...略
 
 # Qtライブラリのリンク
 target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core)
 
 # OpenSSLライブラリのリンク
 target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
 
 # OpenSSLのインクルードディレクトリ
 target_include_directories(${PROJECT_NAME} PRIVATE ${OPENSSL_INCLUDE_DIR})
 
 # C++コンパイラフラグの設定
 if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic)
 elseif(MSVC)
    target_compile_options(${PROJECT_NAME} PRIVATE /W4)
 endif()
 // AES.hファイル
 
 #ifndef AES_H
 #define AES_H
 
 #include <QObject>
 #include <QByteArray>
 
 class AES : public QObject
 {
    Q_OBJECT
 
 private:  // Variables
    QByteArray m_key;
    QString    m_lastError;
 
 private:  // Methods
    QByteArray generateKey(const QString &password);
    QByteArray generateIV();
    QByteArray padData(const QByteArray &data);
    QByteArray unpadData(const QByteArray &data);
 
 public:  // Methods
    explicit   AES(QObject *parent = nullptr);
    ~AES();
 
    bool       setKey(const QString &password);
    QByteArray encrypt(const QByteArray &plaintext);
    QByteArray decrypt(const QByteArray &ciphertext);
    QString    lastError() const;
 };
 
 #endif // AES_H
 // AES.cppファイル
 
 #include <QCryptographicHash>
 #include <openssl/aes.h>
 #include <openssl/rand.h>
 #include <openssl/err.h>
 #include "AES.h"
 
 AES::AES(QObject *parent) : QObject(parent)
 {
    OpenSSL_add_all_algorithms();
 }
 
 AES::~AES()
 {
    EVP_cleanup();
 }
 
 bool AES::setKey(const QString &password)
 {
    m_key = generateKey(password);
    return !m_key.isEmpty();
 }
 
 QByteArray AES::encrypt(const QByteArray &plaintext)
 {
    if (m_key.isEmpty()) {
       m_lastError = "キーがセットされていません";
       return QByteArray();
    }
 
    QByteArray iv = generateIV();
    QByteArray paddedText = padData(plaintext);
    QByteArray ciphertext(paddedText.size(), 0);
 
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
       m_lastError = "暗号化コンテキストの生成に失敗";
       return QByteArray();
    }
 
    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, 
                           reinterpret_cast<const unsigned char*>(m_key.constData()), 
                           reinterpret_cast<const unsigned char*>(iv.constData())) != 1) {
       m_lastError = "Failed to initialize encryption";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
 
    int len;
    int ciphertext_len;
 
    if (EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(ciphertext.data()), 
                          &len, reinterpret_cast<const unsigned char*>(paddedText.constData()), 
                          paddedText.size()) != 1) {
       m_lastError = "データの暗号化に失敗";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
    ciphertext_len = len;
 
    if (EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(ciphertext.data()) + len, &len) != 1) {
       m_lastError = "暗号化のファイナライズに失敗";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
    ciphertext_len += len;
 
    EVP_CIPHER_CTX_free(ctx);
 
    return iv + ciphertext.left(ciphertext_len);
 }
 
 QByteArray AES::decrypt(const QByteArray &ciphertext)
 {
    if (m_key.isEmpty()) {
       m_lastError = "キーがセットされていません";
       return QByteArray();
    }
 
    if (ciphertext.size() < AES_BLOCK_SIZE) {
       m_lastError = "暗号文のサイズが無効";
       return QByteArray();
    }
 
    QByteArray iv = ciphertext.left(AES_BLOCK_SIZE);
    QByteArray actualCiphertext = ciphertext.mid(AES_BLOCK_SIZE);
    QByteArray plaintext(actualCiphertext.size(), 0);
 
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    if (!ctx) {
       m_lastError = "暗号化コンテキストの生成に失敗";
       return QByteArray();
    }
 
    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, 
                           reinterpret_cast<const unsigned char*>(m_key.constData()), 
                           reinterpret_cast<const unsigned char*>(iv.constData())) != 1) {
       m_lastError = "Failed to initialize decryption";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
 
    int len;
    int plaintext_len;
 
    if (EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(plaintext.data()), 
                          &len, reinterpret_cast<const unsigned char*>(actualCiphertext.constData()), 
                          actualCiphertext.size()) != 1) {
       m_lastError = "データの復号に失敗";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
    plaintext_len = len;
 
    if (EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(plaintext.data()) + len, &len) != 1) {
       m_lastError = "復号のファイナライズに失敗";
       EVP_CIPHER_CTX_free(ctx);
       return QByteArray();
    }
    plaintext_len += len;
 
    EVP_CIPHER_CTX_free(ctx);
 
    return unpadData(plaintext.left(plaintext_len));
 }
 
 QString AES::lastError() const
 {
    return m_lastError;
 }
 
 QByteArray AES::generateKey(const QString &password)
 {
    return QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256);
 }
 
 QByteArray AES::generateIV()
 {
    QByteArray iv(AES_BLOCK_SIZE, 0);
    if (RAND_bytes(reinterpret_cast<unsigned char*>(iv.data()), AES_BLOCK_SIZE) != 1) {
       m_lastError = "初期化ベクトルの生成に失敗";
       return QByteArray();
    }
    return iv;
 }
 
 QByteArray AES::padData(const QByteArray &data)
 {
    int padding = AES_BLOCK_SIZE - (data.size() % AES_BLOCK_SIZE);
    return data + QByteArray(padding, padding);
 }
 
 QByteArray AES::unpadData(const QByteArray &data)
 {
    if (data.isEmpty()) {
       return data;
    }
    int padding = data[data.size() - 1];
    if (padding > AES_BLOCK_SIZE || padding < 1) {
       m_lastError = "パディングが無効";
       return QByteArray();
    }
    return data.left(data.size() - padding);
 }
 // main.cppファイル
 
 #include <QCoreApplication>
 #include <QDebug>
 #include "AES.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv); 
 
    AES aes;
 
    // 暗号化 (AES CBC)
    if (!aes.setKey("<任意の暗号化パスワード>")) {
       qDebug() << "Failed to set key:" << aes.lastError();
       return;
    }
 
    QByteArray plaintext = "Hello, World! This is a secret message.";
    QByteArray ciphertext = aes.encrypt(plaintext);
    if (ciphertext.isEmpty()) {
       qDebug() << "暗号化に失敗: " << aes.lastError();
       return;
    }
 
    // 復号 (AES CBC)
    QByteArray decrypted = aes.decrypt(ciphertext);
    if (decrypted.isEmpty()) {
       qDebug() << "復号に失敗: " << aes.lastError();
       return;
    }
 
    qDebug() << "復号したデータ: " << QString::fromUtf8(decrypted);
 
    return a.exec();
 }
※注意
- 暗号化時は、IV (初期化ベクトル) を自動生成して、暗号文の先頭に追加している。
 - 復号時は、暗号文の先頭からIV (初期化ベクトル) を取り出して使用する。
 - パスワードからキーを生成しているが、実務ではより安全なキー管理方法を使用すべきである。
 - エラーハンドリングにおいて、入出力の検証を追加することを推奨する。
 
パスワード / キーの管理
パスワード / キーの管理は非常に重要なセキュリティ上の問題である。
ハードコーディングされたパスワードは避けるべきであり、より安全な方法でパスワードを取得・管理する必要がある。
以下に、いくつかの一般的なアプローチと推奨事項を示す。
ユーザ入力
最も簡単な方法は、ユーザに直接パスワードを入力してもらうことである。
 QString getPasswordFromUser()
 {
    QInputDialog dialog;
    dialog.setInputMode(QInputDialog::TextInput);
    dialog.setTextEchoMode(QLineEdit::Password);
    dialog.setWindowTitle("パスワードの入力");
    dialog.setLabelText("暗号化パスワードを入力してください: ");
 
    if (dialog.exec() == QDialog::Accepted) {
       return dialog.textValue();
    }
    return QString();
 }
 // 使用例
 QString password = getPasswordFromUser();
 if (!password.isEmpty()) {
    aes.setKey(password);
 }
環境変数の使用
セキュリティ上のリスクはあるが、開発環境やテスト環境では環境変数を使用することがある。
 QString getPasswordFromEnv()
 {
    return qgetenv("MY_APP_ENCRYPTION_KEY");
 }
暗号化された設定ファイル
暗号化された設定ファイルにパスワードを保存する方法もある。
ただし、この方法を使用する場合は、設定ファイル自体を安全に保護する必要がある。
 QString getPasswordFromConfig()
 {
    QSettings settings("MyCompany", "MyApp");
    return settings.value("EncryptionKey").toString();
 }
キーストア / セキュアエンクレーブ
OSのセキュアなキーストアを使用する方法である。
これは最も安全な方法の1つであるが、実装が複雑になる可能性がある。
以下に示すAPIを使用する場合は、プラットフォーム固有のコードが必要になる。
- Windows
- Data Protection API (DPAPI)
 
 - MacOS
- Keychain
 
 - Linux
- Secret Service API
 
 
ハードウェアセキュリティモジュール (HSM)
高度なセキュリティが必要な場合、HSMを使用してキーを管理することができる。
キーの派生
ユーザのパスワードから安全にキーを派生させる方法もある。
これには、PBKDF2やscrypt等のキー派生関数を使用する。
 QByteArray deriveKey(const QString &password, const QByteArray &salt)
 {
    // OpenSSLを使用してPBKDF2でキーを派生させる例
    QByteArray key(32, 0); // 256ビットキー
    PKCS5_PBKDF2_HMAC(password.toUtf8().constData(), password.length(),
                      reinterpret_cast<const unsigned char*>(salt.constData()),
                      salt.length(), 10000, EVP_sha256(),
                      key.size(), reinterpret_cast<unsigned char*>(key.data()));
    return key;
 }
推奨事項
- パスワードを平文で保存しないこと。
 - 可能な限り、ユーザに直接パスワードを入力してもらう方法を選択すること。
 - パスワードの強度要件を設定して、ユーザに強力なパスワードの使用を促す。
 - パスワード / キーの転送時は、安全な通信チャネル (例: HTTPS) を使用すること。
 - 必要に応じて、多要素認証を実装することを検討する。
 - 定期的なパスワード変更を促すポリシーを実装することを検討する。
 
実務では、これらの方法を組み合わせて使用することが一般的である。
例えば、ユーザが入力したパスワードからキーを派生させて、派生したキーをセキュアなキーストアに保存するという方法がある。
Crypto++
Crypto++とは
AES (またはその他のブロック暗号) を使用する場合は、一般的に、ブロック暗号のインスタンスを持つモードを使用する。
例えば、CFBモードの場合は、以下のように記述する。
 CFB_Mode<AES>::Encryption Encrypt;  // CFB_Modeは、ブロック暗号をテンプレートパラメータとして受け取る
Crypto++のインストール
Crypto++ Libraryの公式Webサイトにアクセスして、暗号化ライブラリのソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
unzip cryptopp<バージョン>.zip cd cryptopp<バージョン>
または、Githubからソースコードをダウンロードすることもできる。
git clone https://github.com/weidai11/cryptopp.git cd cryptopp
Crypto++をビルドおよびインストールする。
make -j $(nproc) static dynamic CXX=/<GCCのインストールディレクトリ>/g++ make test make install DESTDIR=<暗号化ライブラリのインストールディレクトリ>
暗号化ライブラリの使用手順は、以下のWebサイトを参照すること。
https://www.cryptopp.com/wiki/Advanced_Encryption_Standard
ブロック暗号の参照
外部のブロック暗号オブジェクトのインスタンスではなく、そのオブジェクトへの参照を保持するモードオブジェクトを作成することもできる。
以下の例では、外部のAESオブジェクトでCFBモードを使用している。
 AES::Encryption aesEncryption(key, AES::DEFAULT_KEYLENGTH);
 CFB_Mode_ExternalCipher::Encryption cfbEncryption(aesEncryption, iv);
ExternalCipherモードは、WindowsでFIPSをサポートするために追加されたものであり、必要がない限り避けるべきである。
ECBおよびCBCモードの備考
ECBおよびCBCモードでは、データをブロックサイズの倍数で処理する必要がある。
または、StreamTransformationFilterをモードオブジェクトにラッピングして、フィルタオブジェクトとして使用することもできる。
StreamTransformationFilterは、データをブロックにバッファリングして、必要に応じてパディングを行う。
もし、フルブロックサイズで処理する(パディングを行わない)場合は、第4引数にStreamTransformationFilterのNO_PADDINGを指定する。
 StringSource(data, true, new StreamTransformationFilter(encryptor, new StringSink(result), NO_PADDING))
サンプルコード : 使用する鍵長の最小値、最大値、標準値の表示
以下の例では、AESが使用する鍵長の最小値、最大値、標準値をダンプしている。(2番目と3番目はパイプラインのフィルターを使用している)
パイプラインは高レベルの抽象化であり、入力のバッファリング、出力のバッファリング、パディングを処理する。
 std::cout << "鍵長 : " << AES::DEFAULT_KEYLENGTH << std::endl;
 std::cout << "鍵長(最小) : " << AES::MIN_KEYLENGTH << std::endl;
 std::cout << "鍵長(最大) : " << AES::MAX_KEYLENGTH << std::endl;
 std::cout << "ブロックサイズ : " << AES::BLOCKSIZE << std::endl;
 
 // 出力
 // 標準値の鍵長は128ビット(16バイト)
 鍵長 : 16
 鍵長(最小) : 16
 鍵長(最大) : 32
 ブロックサイズ : 16
サンプルコード : CBCモード
以下の例では、CBCモードを使用して、暗号化および復号を行っている。
使用する鍵はINIファイルでを保存して、アプリケーション再起動時にも使用できるようにしている。
16バイト (128ビット) の鍵を使用しているが、AESは192ビットや256ビットのキーもサポートしている。
暗号化されたデータを保存または送信する場合は、初期化ベクトル (IV) も一緒に保存または送信する必要がある。
 // SecureAESCrypto.hファイル
 
 #include <QCoreApplication>
 #include <QString>
 #include <QByteArray>
 #include <QSettings>
 #include <QFile>
 #include <QDebug>
 
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
 #include <cryptopp/filters.h>
 #include <cryptopp/osrng.h>
 #include <cryptopp/hex.h>
 
 class SecureAESCrypto {
 public:
    // 安全な鍵の生成
    static QByteArray generateKey()
    {
       QByteArray key;
       key.resize(CryptoPP::AES::DEFAULT_KEYLENGTH);
       CryptoPP::AutoSeededRandomPool prng;
       prng.GenerateBlock(reinterpret_cast<byte*>(key.data()), key.size());
 
       return key;
    }
 
    // 安全な初期化ベクトル (IV) の生成
    static QByteArray generateIV()
    {
       QByteArray iv;
       iv.resize(CryptoPP::AES::BLOCKSIZE);
       CryptoPP::AutoSeededRandomPool prng;
       prng.GenerateBlock(reinterpret_cast<byte*>(iv.data()), iv.size());
 
       return iv;
    }
 
    // 平文を暗号化
    static QByteArray encrypt(const QByteArray &plaintext, const QByteArray &key, QByteArray &iv)
    {
       try {
          CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption encryptor;
          encryptor.SetKeyWithIV(reinterpret_cast<const byte*>(key.constData()), key.size(), reinterpret_cast<const byte*>(iv.constData()));
 
          QByteArray ciphertext;
          CryptoPP::StringSource(reinterpret_cast<const byte*>(plaintext.constData()), plaintext.size(), true,
                                 new CryptoPP::StreamTransformationFilter(encryptor, new CryptoPP::StringSink(ciphertext)));
 
          return ciphertext;
       }
       catch (const CryptoPP::Exception& e) {
          qCritical() << "Encryption error:" << e.what();
          return QByteArray();
       }
    }
 
    // 暗号文を復号
    static QByteArray decrypt(const QByteArray &ciphertext, const QByteArray &key, const QByteArray &iv)
    {
       try {
          CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryptor;
          decryptor.SetKeyWithIV(reinterpret_cast<const byte*>(key.constData()), key.size(), reinterpret_cast<const byte*>(iv.constData()));
 
          QByteArray plaintext;
          CryptoPP::StringSource(reinterpret_cast<const byte*>(ciphertext.constData()), ciphertext.size(), true,
                                 new CryptoPP::StreamTransformationFilter(decryptor, new CryptoPP::StringSink(plaintext)));
 
          return plaintext;
       }
       catch (const CryptoPP::Exception& e) {
          qCritical() << "Decryption error:" << e.what();
          return QByteArray();
       }
    }
 
    // 暗号文と初期化ベクトル (IV) をファイルに保存
    static bool saveEncryptedData(const QString &filename, const QByteArray &data, const QByteArray &iv)
    {
       QFile file(filename);
       if (!file.open(QIODevice::WriteOnly)) {
          qCritical() << "Unable to open file for writing:" << filename;
          return false;
       }
 
       QDataStream out(&file);
       out << iv << data;
 
       file.close();
 
       return true;
    }
 
    // ファイルから暗号文と初期化ベクトル (IV) を読み込む
    static bool loadEncryptedData(const QString &filename, QByteArray &data, QByteArray &iv)
    {
       QFile file(filename);
       if (!file.open(QIODevice::ReadOnly)) {
          qCritical() << "Unable to open file for reading:" << filename;
          return false;
       }
 
       QDataStream in(&file);
       in >> iv >> data;
 
       file.close();
 
       return true;
    }
 };
 // main.cppファイル
 
 #include "SecureAESCrypto.h"
 
 QByteArray getOrCreateKey()
 {
    QSettings settings("MyCompany", "SecureAESApp");
    if (settings.contains("encryption/key")) {
       return QByteArray::fromHex(settings.value("encryption/key").toString().toLatin1());
    }
    else {
       QByteArray key = SecureAESCrypto::generateKey();
       settings.setValue("encryption/key", QString(key.toHex()));
 
       return key;
    }
 }
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    // キーの取得または生成
    QByteArray key = getOrCreateKey();
    qDebug() << "Encryption key:" << key.toHex();
 
    // テスト用のデータ
    QByteArray plaintext = "Hello, World! This is a secret message.";
    qDebug() << "Original text:" << plaintext;
 
    // 暗号化
    QByteArray iv = SecureAESCrypto::generateIV();
    QByteArray ciphertext = SecureAESCrypto::encrypt(plaintext, key, iv);
    if (!ciphertext.isEmpty()) {
       qDebug() << "Encrypted text:" << ciphertext.toHex();
 
       // 暗号化されたデータとIVの保存
       if (SecureAESCrypto::saveEncryptedData("encrypted_data.bin", ciphertext, iv)) {
          qDebug() << "Encrypted data and IV saved successfully.";
 
          // 暗号化されたデータとIVの読み込み
          QByteArray loadedCiphertext, loadedIV;
          if (SecureAESCrypto::loadEncryptedData("encrypted_data.bin", loadedCiphertext, loadedIV)) {
             qDebug() << "Encrypted data and IV loaded successfully.";
 
             // 復号
             QByteArray decryptedText = SecureAESCrypto::decrypt(loadedCiphertext, key, loadedIV);
             if (!decryptedText.isEmpty()) {
                qDebug() << "Decrypted text:" << decryptedText;
             }
             else {
                qDebug() << "Decryption failed.";
             }
          }
          else {
             qDebug() << "Failed to load encrypted data and IV.";
          }
       }
       else {
          qDebug() << "Failed to save encrypted data and IV.";
       }
    }
    else {
        qDebug() << "Encryption failed.";
    }
 
    return a.exec();
 }
※注意
鍵の保存において、実務ではより安全な方法 (例: OSのキーチェーンやセキュアな暗号化ストレージ) を使用することを推奨する。
また、ユーザ認証やアクセス制御等の追加のセキュリティ層を実装することを推奨する。
ファイルの暗号化や大量のデータの暗号化を行う場合は、メモリ使用量を考慮してストリーミング暗号化を行う。
サンプルコード : CFBモード
以下の例では、CFBモードを使用して、暗号化および復号を行っている。
ECBやCBCではないため、データ長をAESのブロックサイズの倍数にする必要は無い。
 AutoSeededRandomPool rnd;
 
 // Generate a random key
 SecByteBlock key(0x00, AES::DEFAULT_KEYLENGTH);
 rnd.GenerateBlock(key, key.size());
 
 // Generate a random IV
 SecByteBlock iv(AES::BLOCKSIZE);
 rnd.GenerateBlock(iv, iv.size());
 
 byte plainText[] = "Hello! How are you.";
 size_tmessageLen = std::strlen((char*)plainText) + 1;
 
 // Encrypt
 CFB_Mode<AES>::Encryption cfbEncryption(key, key.size(), iv);
 cfbEncryption.ProcessData(plainText, plainText, messageLen);
 
 // Decrypt
 CFB_Mode<AES>::Decryption cfbDecryption(key, key.size(), iv);
 cfbDecryption.ProcessData(plainText, plainText, messageLen);
ここでは、外部で生成された鍵と初期化ベクトル(IV)を使用して、暗号化器と復号器を取得する例を記述する。
その後の処理は、上記の例と同様に記述する。
 SecByteBlock aes_key(16);
 SecByteBlock iv(16);
 
 // stub for how you really get it, e.g. reading it from a file, off of a
 // network socket encrypted with an asymmetric cipher, or whatever
 read_key(aes_key, aes_key.size());
 
 // stub for how you really get it, e.g. filling it with random bytes or
 //   reading it from the other side of the socket since both sides have
 //   to use the same IV as well as the same key
 read_initialization_vector(iv);
 
 // the final argument is specific to CFB mode, and specifies the refeeding size in bytes.
 // This invocation corresponds to Java's Cipher.getInstance("AES/CFB8/NoPadding")
 auto enc = new CFB_Mode<AES>::Encryption(aes_key, sizeof(aes_key), iv, 1);
 
 // the final argument is specific to CFB mode, and specifies the refeeding size in bytes.
 // This invocation corresponds to Java's Cipher.getInstance("AES/CFB8/NoPadding")
 auto dec = new CFB_Mode<AES>::Decryption(aes_key, sizeof(aes_key), iv, 1);
Qt AESライブラリ
Qt AESライブラリとは
Qt向けのポータブルなAES暗号化ライブラリである。
128 / 192 / 256ビットの鍵長をサポートしており、ECB、CBC、CFB、OFBモードおよび部分的なAES-NIをサポートしている。
Qt AESライブラリはフリーライセンスであり、商用 / 非商用を問わず、自由に使用、改変、販売してもよい。
また、ソースコードの公開義務も無い。
そのため、Qtの実行バイナリまたはQtプロジェクトにおいて、Qt AESライブラリを同梱して配布してもよい。
Qt AESライブラリのダウンロード
Qt AESライブラリのビルドに必要なライブラリをインストールする。
# SUSE
sudo zypper install make cmake gcc gcc-c++ \
                    qt6-core-devel    # Qt 6を使用する場合
                    qt6-test-devel    # Qt AESライブラリのテストを行う場合 (Qt 6を使用する場合)
                    libQt5Core-devel  # Qt 5を使用する場合
                    libQt5Test-devel  # Qt AESライブラリのテストを行う場合 (Qt 5を使用する場合)
Qt AESクラスのGuthubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf Qt-AES-<バージョン>.tar.gz cd Qt-AES-<バージョン>
Qt AESライブラリをビルドおよびインストールする。
mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=<Qt AESライブラリのインストールディレクトリ> .. make -j $(nproc) make install
Qt AESライブラリのサンプルコード
Qt AESライブラリを使用するには、Qt Coreライブラリが必要となる。
また、テストを行う場合は、Qt Testライブラリも必要となる。
以下の例では、Qt AESライブラリを使用して、鍵長128[bit]のECBモードで暗号化している。
 #include <QCryptographicHash>
 #include "qaesencryption.h"
 
 // AESによるデータの暗号化
 QString Encrypt(QString data, QString key)
 {
    // 鍵長128ビット, ECBモード, ゼロパディング
    QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB, QAESEncryption::ZERO);
 
    // QCryptographicHashによる鍵の暗号化
    QByteArray hashKey = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1);
 
    // データの暗号化
    QByteArray encodedText = encryption.encode(data.toUtf8(), hashKey);
 
    // QByteArrayからQStringへ変換
    // toBase64関数は削除できない
    QString encodeTextStr = QString::fromLatin1(encodedText.toBase64());
 
    return encodeTextStr;
 }
以下の例では、Qt AESライブラリを使用して、鍵長128[bit]のECBモードで復号している。
 #include <QCryptographicHash>
 #include "qaesencryption.h"
 
 // Qt AESライブラリを使用したデータの復号
 QString decodedText(QString data, QString key)
 {
    // 鍵長128ビット, ECBモード, ゼロパディング
    QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB, QAESEncryption::ZERO);
 
    // QCryptographicHashによる鍵の暗号化
    QByteArray hashKey = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1);
 
    // データの復号
    QByteArray decodedText = encryption.decode(QByteArray::fromBase64(data.toLatin1()), hashKey);
 
    // QByteArrayからQStringへ変換
    QString decodedTextStr = QString::fromLatin1(decodedText);
 
    return decodedTextStr;
 }