single.php

VC++で.NETの rijndaelManaged クラスを利用する(AES暗号化の実装)

VC++で.NET Framework 内のクラスを利用する方法を紹介します。今回は、暗号化を行うrijndaelManagedクラスの使い方です。

スポンサーリンク

MFC DLLを使った実装

先回「SHA256Managed クラス」の時と同様に今回も、VC++で作成されたアプリケーションで古くから使われているMFCを使った実装方法を紹介していきます。サンプルコードでは、後で再利用しやすいようにDLLとしてエクスポートを行っています。

rijndaelManagedクラス

“rijndael”(ラインダールと読む)は、共通鍵暗号アルゴリズムの1つでDESに代わる新しいアルゴリズム「Advanced Encryption Standard(AES)」で採用されています。

開発の現場でも、セキュリティ施策として暗号化を施す際には「AES」にすることになればお世話になるクラスの1つです。

プロジェクト作成

Visual Studioを使って実装していきます。最初に[ファイル|新規作成|プロジェクト]メニューを選択して、[新しいプロジェクト]として[MFC DLL]を選択します。

上の図のように[MFC DLL]プロジェクトのテンプレートが無い場合には、[Visual Studio インストーラーを開く]リンクをクリックして、表示された[Visual Studio インストーラー]画面で[C++によるデスクトップ開発]のオプションで[x86用とx64用のVisual C++ MFC]を追加します。

プロジェクトを作成する途中で表示される[MFC DLL]画面では、既定値のまま[OK]ボタンをクリックします。

プロパティ変更

先回同様にマネージドコードが使えるようにプロジェクトのプロパティを変更しておきます。

具体的には、次の手順で変更を行います。

  1. [プロジェクト|<プロジェクト>のプロパティ]メニューを選択します。
  2. 表示された[<プロジェクト> プロパティ ページ]画面で[構成プロパティ|全般|共通言語ランタイム サポート]を[共通言語ランタイム サポート(/clr)]に変更します。
  3. [OK]ボタンをクリックして[<プロジェクト> プロパティ ページ]を閉じます。

コードの編集

共通言語ランタイム サポートの変更を行ったら、いよいよ暗号化処理のコードを追加していきます。

最初に行う手順は、アセンブリの追加です。

作成したプロジェクトのソースファイル(拡張子.cpp ファイル)の先頭部分に以下のコードを追加します。

using namespace System;
using namespace System::Text;
using namespace System::Collections;

次に、インスタンス初期化の直後くらいに実行する部分の本体を追加します。今回は分かりやすく[rijndael]という関数名にしました。

CString rijndael(CString sMessage, CString sKey)
{
  String^ message = gcnew String(sMessage);
  String^ key = gcnew String(sKey);

  String^ encryptedString = "";

  System::Security::Cryptography::RijndaelManaged^ rijndaelManaged = gcnew System::Security::Cryptography::RijndaelManaged();
  rijndaelManaged->BlockSize = 128;
  rijndaelManaged->KeySize = 128;
  rijndaelManaged->Mode = System::Security::Cryptography::CipherMode::ECB;
  rijndaelManaged->Padding = System::Security::Cryptography::PaddingMode::PKCS7;

  int size = key->Length / 2;
  array<byte>^ aKey = gcnew array<byte>(size);
  size = hexString2Byte(key, aKey);

  if (rijndaelManaged->ValidKeySize(size) == false)
  {
    return CString("");
  }

  rijndaelManaged->Key = aKey;
  System::Security::Cryptography::ICryptoTransform^ encrypt = rijndaelManaged->CreateEncryptor();

  UTF8Encoding^ utf = gcnew UTF8Encoding();
  array<byte>^ aMessage = utf->GetBytes(message);
  array<byte>^ aEncByte = encrypt->TransformFinalBlock(aMessage, 0, aMessage->Length);

  StringBuilder^ sb = gcnew StringBuilder(aEncByte->Length);

  for (int i = 0; aEncByte->Length; i++)
  {
    sb->Append(aEncByte[i].ToString("X2"));
  }

  char* pChar = new char[1];

  return sb->ToString();
}

次に関数内で呼び出している「hexString2Byte」関数を追加していきます。

static int hexString2Byte(String^ data, array<byte>^ byte)
{
  int size = data->Length / 2;
  for (int i = 0; i < size; i++)
  {
    String^ target = data->Substring(i * 2, 2);
    byte[i] = System::Convert::ToByte(target, 16);
  }
  return size;
}

これは、16進の文字列をバイト配列に変換する関数ですが、rijndael関数内でも実装できますが、コードが読みにくくなるので外部関数化をしています。

BlockSizeやChiperMode、PaddingModeを設定する部分は実装する暗号化の仕様に応じて調整することになります。

最後に、DLLで利用できるようにエクスポート部分を追加します。

extern "C" __declspec(dllexport) long encryptrijndaelManaged(char* pSourceString, char* pKeyString, char* pEncryptString)
{
  int nSize = 0;
  if (pSourceString == NULL)
  {
    return nSize;
  }

  if (pKeyString == NULL)
  {
    return nSize;
  }

  CString sSourceString = CString(pSourceString);
  CString sKeyString = CString(pKeyString);

  CString sEncryptString = rijndaelManaged(sSourceString, sKeyString);

  nSize = sEncryptString.GetLength();

  if (pEncryptString == NULL)
  {
    return nSize;
  }

  memset(pEncryptString, 0x00, nSize);
  memcpy(pEncryptString, sEncryptString.GetBuffer(), nSize);
  sEncryptString.ReleaseBuffer();

  return nSize;
}

すべての追加したコードは、こんな感じになります。

// MFCLibrary1.cpp : DLL の初期化ルーチンを定義します。
//

#include "stdafx.h"
#include "MFCLibrary1.h"

using namespace System;
using namespace System::Text;
using namespace System::Collections;

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//
//TODO: この DLL が MFC DLL に対して動的にリンクされる場合、
//    MFC 内で呼び出されるこの DLL からエクスポートされたどの関数も
//    関数の最初に追加される AFX_MANAGE_STATE マクロを
//    持たなければなりません。
//
//    例:
//
//    extern "C" BOOL PASCAL EXPORT ExportedFunction()
//    {
//      AFX_MANAGE_STATE(AfxGetStaticModuleState());
//      // 通常関数の本体はこの位置にあります
//    }
//
//    このマクロが各関数に含まれていること、MFC 内の
//    どの呼び出しより優先することは非常に重要です。
//    it は、次の範囲内で最初のステートメントとして表示されるべきです
//    らないことを意味します、コンストラクターが MFC
//    DLL 内への呼び出しを行う可能性があるので、オブ
//    ジェクト変数の宣言よりも前でなければなりません。
//
//    詳細については MFC テクニカル ノート 33 および
//    58 を参照してください。
//

// CMFCLibrary1App

BEGIN_MESSAGE_MAP(CMFCLibrary1App, CWinApp)
END_MESSAGE_MAP()


// CMFCLibrary1App の構築

CMFCLibrary1App::CMFCLibrary1App()
{
  // TODO: この位置に構築用コードを追加してください。
  // ここに InitInstance 中の重要な初期化処理をすべて記述してください。
}


// 唯一の CMFCLibrary1App オブジェクト

CMFCLibrary1App theApp;


// CMFCLibrary1App の初期化

BOOL CMFCLibrary1App::InitInstance()
{
  CWinApp::InitInstance();

  return TRUE;
}

static int hexString2Byte(String^ data, array<byte>^ byte)
{
  int size = data->Length / 2;
  for (int i = 0; i < size; i++)
  {
    String^ target = data->Substring(i * 2, 2);
    byte[i] = System::Convert::ToByte(target, 16);
  }
  return size;
}

CString rijndael(CString sMessage, CString sKey)
{
  String^ message = gcnew String(sMessage);
  String^ key = gcnew String(sKey);

  String^ encryptedString = "";

  System::Security::Cryptography::RijndaelManaged^ rijndaelManaged = gcnew System::Security::Cryptography::RijndaelManaged();
  rijndaelManaged->BlockSize = 128;
  rijndaelManaged->KeySize = 128;
  rijndaelManaged->Mode = System::Security::Cryptography::CipherMode::ECB;
  rijndaelManaged->Padding = System::Security::Cryptography::PaddingMode::PKCS7;

  int size = key->Length / 2;
  array<byte>^ aKey = gcnew array<byte>(size);
  size = hexString2Byte(key, aKey);

  if (rijndaelManaged->ValidKeySize(size) == false)
  {
    return CString("");
  }

  rijndaelManaged->Key = aKey;
  System::Security::Cryptography::ICryptoTransform^ encrypt = rijndaelManaged->CreateEncryptor();

  UTF8Encoding^ utf = gcnew UTF8Encoding();
  array<byte>^ aMessage = utf->GetBytes(message);
  array<byte>^ aEncByte = encrypt->TransformFinalBlock(aMessage, 0, aMessage->Length);

  StringBuilder^ sb = gcnew StringBuilder(aEncByte->Length);

  for (int i = 0; aEncByte->Length; i++)
  {
    sb->Append(aEncByte[i].ToString("X2"));
  }

  char* pChar = new char[1];

  return sb->ToString();
}

extern "C" __declspec(dllexport) long encryptrijndaelManaged(char* pSourceString, char* pKeyString, char* pEncryptString)
{
  int nSize = 0;
  if (pSourceString == NULL)
  {
    return nSize;
  }

  if (pKeyString == NULL)
  {
    return nSize;
  }

  CString sSourceString = CString(pSourceString);
  CString sKeyString = CString(pKeyString);

  CString sEncryptString = rijndaelManaged(sSourceString, sKeyString);

  nSize = sEncryptString.GetLength();

  if (pEncryptString == NULL)
  {
    return nSize;
  }

  memset(pEncryptString, 0x00, nSize);
  memcpy(pEncryptString, sEncryptString.GetBuffer(), nSize);
  sEncryptString.ReleaseBuffer();

  return nSize;
}


まとめ

rijndaelManagedクラスは、暗号化の手段としてAESを利用するケースは考えられるので覚えておいて損しないクラスの1つです。

VC++で暗号化を実装しようとすると、自作するか外部ライブラリを利用する必要がありますが、.NET Frameworkには便利なクラスが沢山用意されているので、マネージドコードを利用しない手はありません。

大抵の場合、VC++ではアンマネージドな形で開発するのが一般的なので、マネージドを使う部分をDLL化して、必要に応じて呼び出すような形が、本体プロジェクトをすべてアンマネージドに設定することなく、セキュリティなどのデメリットを最小限にとどめることができます。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です