Windowsで暗号プログラムを書いてみる(RSA編 その1) | reverse-eg-mal-memoのブログ

reverse-eg-mal-memoのブログ

サイバーセキュリティに関して、あれこれとメモするという、チラシの裏的存在。
medium(英語):https://sachiel-archangel.medium.com/

注意:Advapi32のCryptAPIは非推奨になり、次世代暗号化API(CNG)が推奨されているようです。

今回はCryptAPIで作ってしまったので、そのまま掲載します。(そのうち直すかもしれません。)

 

RSA暗号化・復号化のCryptAPIのざっくりな流れ

大雑把なフローは以下の通り。

 

前提:

  • "WinCrypt.h"をincludeすること。
  • "Crypt32.lib"をリンクできるように設定すること。

 

概略フロー:

秘密鍵/公開鍵ペアの作成:

  • Cryptコンテキストの取得(CryptAcquireContext)
  • 鍵の作成(CryptGenKey)
  • 秘密鍵/公開鍵の取得(CryptExportKey)
  • キーオブジェクトの開放(CryptDestroyKey)
  • Cryptコンテキストの開放(CryptReleaseContext)

暗号化・復号化:

  • Cryptコンテキストの取得(CryptAcquireContext)
  • 鍵のインポート(CryptImportKey)
  • 暗号化・復号化の実行(CryptEncrypt、CryptDecrypt)
  • キーオブジェクトの開放(CryptDestroyKey)
  • Cryptコンテキストの開放(CryptReleaseContext)

(おまけ)PEM出力:

  • X509/PKCS#7エンコード処理(CryptEncodeObjectEx)
  • バイナリのBASE64変換(CryptBinaryToString)
  • ヘッダー、フッターの付与

(おまけ)PEM入力:

  • BASE64のバイナリ変換(CryptStringToBinary)
  • X509/PKCS#7デコード処理(CryptDecodeObjectEx)

 

ポイント:

  • 利用するCryptAPIの種類と順番、概ねのパラメータ。
  • 鍵情報は秘密鍵・公開鍵のペアと公開鍵。
  • 暗号化は公開鍵で行い、復号は秘密鍵で行っている。

 

サンプルソースのコード実装上の制限:

  • TCHARを8バイトのASCII文字として取り扱うため、文字コードをマルチバイト文字セットを使用している。

 

 

CryptAPIを使ったRSA暗号化・復号化サンプルソース

これも、VisualStudio2017で動作することが確認されたソースです。

もし流用するのであれば、パラメータ等については、用途に合わせて変えてください。

MicrosoftのMSDNを参考にするとよいでしょう。

ソースコードは次の記事に記載しています。また、このソースコードを用いたサンプルをおまけ記事2に掲載します。

必要な領域を動的に確保するため、データの格納はバッファのポインタではなく、データを格納するオリジナルのデータオブジェクトを用いています。

データオブジェクトのソースはおまけ記事に掲載します。

 

データクラスで文字列操作をTCHARで実装してありますが、文字列をASCIIで使いたいため、マルチバイト文字セットでビルドする必要があります。

設定は右側のソリューションエクスプローラーでプロジェクトを選択したうえで、メニューの「プロジェクト」→「プロパティ」を選択し、プロパティページを表示します。左側ペインの「構成プロパティ」→「全般」を選び、右側ペインの「プロジェクトの規定値」内の文字セットを「マルチバイト文字セットを使用する」を選択します(VisualStudio 2017の場合)。

 

 

 

また、秘密鍵・公開鍵の生成や暗号化・復号化とは直接関係はないですが、PEMでのデータ出力のためのエンコード/BASE64変換およびPEMデータ入力のためのバイナリ変換/デコードの関数も入れています。

こちらを使う場合、「Crypt32.lib」をライブラリとして参照しています。

使用したVisualStudio 2017のデフォルト設定ではこのライブラリが含まれていないため、リンカの設定に追加する必要があります。新しいバージョンなら、デフォルトで参照するようになっているかもしれません。

(ライブラリを追加しないと、名前解決でコケます。)

設定は同様に開いたプロパティページの左側ペインの「構成プロパティ」→「全般」を選び、右側ペインの「構成プロパティ」→「リンカ」→「入力」を選び、右側ペインの「追加の依存ファイル」に「Crypt32.lib」を追加します。

 

 

 

関数の仕様:

static int CreateRSAKey(DataContainer *pobjPrivateKey, DataContainer *pobjPublicKey);

RSA鍵を生成し、第1引数に秘密鍵、第2引数に公開鍵を格納して返します。

 

引数:

pobjPrivateKey:秘密鍵Blob受け取り用データオブジェクトのポインタ

pobjPublicKey:公開鍵Blob受け取り用データオブジェクトのポインタ

 

返り値:

0:正常終了

負値:異常終了(詳細はヘッダのdefine参照)

 

制限:

領域のサイズを計算して確保する必要があり、それも含めてラッピングしたため、データクラスを使う必要がある。

 

 

static int Encrypt(DataContainer *pobjPublicKey, DataContainer *pobjData);

第1引数で渡した鍵で第2引数のデータを暗号化する。

暗号化結果は第2引数のオブジェクトに格納される。

 

引数:

pobjPublicKey:公開鍵Blobデータオブジェクトのポインタ

pobjData:暗号化するデータが格納されたデータオブジェクトのポインタ

 

返り値:

0:正常終了

負値:異常終了(詳細はヘッダのdefine参照)

 

制限:

大きなサイズのデータには未対応。

領域のサイズを計算して確保する必要があり、それも含めてラッピングしたため、データクラスを使う必要がある。

 

 

static int Decrypt(DataContainer *pobjPrivateKey, DataContainer *pobjData);

第1引数で渡した鍵で第2引数のデータを復号化する。

復号化結果は第2引数のオブジェクトに格納される。

 

引数:

pobjPrivateKey:秘密鍵Blobデータオブジェクトのポインタ

pobjData:復号化するデータが格納されたデータオブジェクトのポインタ

 

返り値:

0:正常終了

負値:異常終了(詳細はヘッダのdefine参照)

 

制限:

大きなサイズのデータには未対応。

領域のサイズを計算して確保する必要があり、それも含めてラッピングしたため、データクラスを使う必要がある。

 

 

static int ConvertBinToString(DataContainer *pobjBin, DataContainer *pobjStr, int iPrivatePublicFlag);

第1引数で渡したBlobの鍵をPEMに変換し、第2引数のデータデータオブジェクトに格納する。

第3引数にBlobの鍵が秘密鍵か公開鍵かを識別するフラグ(CRYPTRSA_FLAG_PRIVATEまたはCRYPTRSA_FLAG_PUBLIC)を指定する。

 

引数:

pobjBin:エンコード/BASE64変換するBlobの鍵を格納するデータオブジェクトのポインタ

pobjStr:エンコード/BASE64変換結果を格納するデータオブジェクトのポインタ

iPrivatePublicFlag:pobjBinが秘密鍵か公開鍵かを示すフラグ

 

返り値:

0:正常終了

負値:異常終了(詳細はヘッダのdefine参照)

 

制限:

エンコードは「X509_ASN_ENCODING | PKCS_7_ASN_ENCODING」で固定。

公開鍵のPEMはopensslのコマンド 「openssl rsa -RSAPublicKey_out」で出力した結果と同等となる。

 

 

static int ConvertStringToBin(DataContainer *pobjStr, DataContainer *pobjBin, int iPrivatePublicFlag);

第1引数で渡したPEMの鍵をBlobに変換し、第2引数のデータデータオブジェクトに格納する。

第3引数にPEMの鍵が秘密鍵か公開鍵かを識別するフラグ(CRYPTRSA_FLAG_PRIVATEまたはCRYPTRSA_FLAG_PUBLIC)を指定する。

 

引数:

pobjStr:Blob変換/デコードするPEMの鍵を格納するデータオブジェクトのポインタ

pobjBin:Blob変換/デコード結果を格納するデータオブジェクトのポインタ

iPrivatePublicFlag:pobjBinが秘密鍵か公開鍵かを示すフラグ

 

返り値:

0:正常終了

負値:異常終了(詳細はヘッダのdefine参照)

 

制限:

デコードは「X509_ASN_ENCODING | PKCS_7_ASN_ENCODING」で固定。

 

 

ソースコードは、ブログの1記事の字数制限を超えてしまったので、次の記事に掲載します。