(C++)CryptProtectData/CryptUnprotectDataについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
Ketty
記事: 103
登録日時: 11年前

(C++)CryptProtectData/CryptUnprotectDataについて

#1

投稿記事 by Ketty » 11年前

こんにちは。
いつもお世話になっております。Kettyです(^▽^)

Windows標準の暗号化関数CryptProtectDataおよびCryptUnprotectDataを使って、
文字列の暗号化/複合したいのですが、複合に失敗します。

なぜ失敗するのか理解できないので、どこに誤りがあるか教えてください。

【環境】
Windows7 Home Edition 64bit
Visual Studio 2010 Express Edition
C++(stl)とDXライブラリを使用しています。

【参考にした記事】
http://eternalwindows.jp/crypto/dpapi/dpapi02.html
http://msdn.microsoft.com/ja-jp/library ... s.85).aspx
http://msdn.microsoft.com/ja-jp/library ... s.85).aspx
http://owlsperspective.blogspot.jp/2010 ... art-1.html
https://msmania.wordpress.com/tag/cryptprotectdata/

【ソース】

コード:

#include <Windows.h>
#include <string>
#include <DxLib.h>
// 暗号化用
#pragma comment(lib, "crypt32.lib")

using namespace std;

// 文字列を暗号化する(または複合する) 第1引数trueで暗号化, falseで複合
bool CryptText( const bool & isEncryptMode, const string & in_text, string & out_text )
{
	// 戻り値クリア
	out_text.clear() ;

	// 入力が空なら処理しない
	if( in_text.empty() ){ return false ; }

	DATA_BLOB	blobIn ;		// 入力データ
	DATA_BLOB	blobOut ;		// 出力データ

	// 入力データ生成
	blobIn.cbData = in_text.size() ;
	blobIn.pbData = (LPBYTE)in_text.c_str() ;

	// 暗号化する
	if( isEncryptMode )
	{
		// パスワード入力のプロンプトを表示させずローカルコンピュータの全ユーザーが複合可能とする
		if( CryptProtectData( &blobIn, NULL, NULL, NULL, NULL, ( CRYPTPROTECT_UI_FORBIDDEN | CRYPTPROTECT_LOCAL_MACHINE ), &blobOut ) == FALSE )
		{
			return false ; // 失敗
		}
	}
	// 複合する
	else
	{
		// パスワード入力のプロンプトを表示させない
		if( CryptUnprotectData( &blobIn, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &blobOut ) == FALSE )
		{
			return false ; // 失敗 ※ここで必ず失敗する( GetLastError()によると、"パラメーターが間違っています。" )
		}
	}

	// 戻り値に暗号化後(または複合後)の文字列をセット
	out_text = reinterpret_cast<const char *>( &blobOut.pbData ) ;

	// 解放
	SecureZeroMemory( blobOut.pbData, blobOut.cbData ) ;
	LocalFree( blobOut.pbData ) ;

	return true ;
}


// Main関数
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	// DXライブラリのログ出力をしない
	DxLib::SetOutApplicationLogValidFlag( FALSE ) ;
	// ウィンドウモードで起動
	DxLib::ChangeWindowMode( TRUE ) ;
	// 画面モード設定
	DxLib::SetGraphMode( 640, 480, 32 ) ;

	// DXライブラリ初期化処理
	if( DxLib::DxLib_Init() == -1 ){ return -1 ; }

	// フォント設定
	const int fontHandle = DxLib::CreateFontToHandle( "メイリオ", 20, DX_FONTTYPE_ANTIALIASING ) ;
	const int fontColor = DxLib::GetColor( 255, 255, 255 ) ;

	// プレーン文字列
	const string planeText = "This is a pen." ;

	// 暗号化する
	string encryptText ;
	bool isEncryptSuccess = CryptText( true, planeText, encryptText ) ;

	// 複合化する
	string decryptText ;
	bool isDencryptSuccess = CryptText( false, encryptText, decryptText ) ;

	// メインループ(ESCで終了)
	while( DxLib::ProcessMessage() == 0 && DxLib::CheckHitKey( KEY_INPUT_ESCAPE ) == 0 )
	{
		// 画面クリア
		DxLib::ClsDrawScreen();

		// プレーン文字列を描画
		DxLib::DrawStringToHandle( 100, 100, ( "planeText=" + planeText ).c_str(), fontColor, fontHandle ) ;
		// 暗号化した文字列を描画
		DxLib::DrawStringToHandle( 100, 150, ( "encryptText=" + encryptText ).c_str(), fontColor, fontHandle ) ;
		// 複合後の文字列を描画
		DxLib::DrawStringToHandle( 100, 200, ( "decryptText=" + decryptText ).c_str(), fontColor, fontHandle ) ;

		// 暗号化失敗したとき
		if( isEncryptSuccess == false )
		{
			DxLib::DrawStringToHandle( 20, 250, "Encrypt error!", fontColor, fontHandle ) ;
		}

		// 複合に失敗したとき
		if( isDencryptSuccess == false )
		{
			DxLib::DrawStringToHandle( 20, 300, "Decrypt error!", fontColor, fontHandle ) ;
		}

		// しばらく待つ
		Sleep( 10 ) ;
	}

	// DXライブラリ終了処理
	DxLib::DxLib_End() ;

	// ソフトの終了 
	return 0 ;
}
上記を実行すると"Decrypt error!"と表示されます。
また、デバッガでステップ実行すると、CryptUnprotectDataがFALSEを返し、失敗していることを確認ています。
GetLastError()でエラーメッセージを取得したところ、"パラメーターが間違っています。"となることを確認しています。
追記※上記エラーメッセージが誤っていたので修正しました。

よろしくお願いします。
最後に編集したユーザー Ketty on 2014年8月03日(日) 17:08 [ 編集 1 回目 ]

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: (C++)CryptProtectData/CryptUnprotectDataについて

#2

投稿記事 by YuO » 11年前

根本的な問題点として,バイナリデータをstd::stringで扱っていることが間違いで,
std::stringを使いたいなら,base64符号化するなど,バイナリを文字列に変換したものをstd::stringに代入して取り扱う必要があります。

厳密には,ちゃんと取り扱えばstd::stringは\0を文字列に含むことができるから間違いと言い切るのは語弊がありますが,
バイナリデータを取り扱うのだから,std::vector<unsgined char>などを使うべき。

アバター
Ketty
記事: 103
登録日時: 11年前

Re: (C++)CryptProtectData/CryptUnprotectDataについて

#3

投稿記事 by Ketty » 11年前

Yuoさん
ご回答くださりありがとうございますm(__)m

恐れ入りますが、私の理解が正しいかどうか、念のため確認させてください。
お伝えくださった内容について、以下の認識であってますでしょうか?

①暗号化後のデータが、バイナリデータなので、
reinterpret_cast<const char *>( &blobOut.pbData )などと、キャストしたとて、それがstringになるわけではない。
⇒つまり、今回のソースでは、そもそも暗号化文字列として誤った文字列を複合しようとするから失敗している

②解決するためには、
上記のようにキャストするのではなく、
バイナリを文字列に変換したものをstringに代入する必要があり、
バイナリを文字列に変換する方法としては、base64符号化(など)がある。

YuO
記事: 947
登録日時: 14年前
住所: 東京都世田谷区

Re: (C++)CryptProtectData/CryptUnprotectDataについて

#4

投稿記事 by YuO » 11年前

オフトピック
複合ではなく復号ですよ。
Ketty さんが書きました:①暗号化後のデータが、バイナリデータなので、
reinterpret_cast<const char *>( &blobOut.pbData )などと、キャストしたとて、それがstringになるわけではない。
⇒つまり、今回のソースでは、そもそも暗号化文字列として誤った文字列を複合しようとするから失敗している
キャストしてstd::stringにならないのは当然として,暗号文というバイナリデータはそもそもCスタイル文字列として扱えないものです。
オフトピック
Cスタイル文字列では終端という意味を持つ特別な値である\0が,通常の値として暗号文の一部に出てくる可能性があるため
暗号文は,[blobOut.pbData + 0, blobOut.pbData + blobOut.cbData)に含まれています。
std::stringの代入演算子は引数がCスタイル文字列であることを想定しているため,
  • 上記の範囲内に\0がある場合は,\0より前のデータのみが暗号文として扱われる
    \0およびそれ以降にある,本来暗号文であるものが暗号文として扱われなくなる
  • 上記の範囲内に\0がない場合は,\0が見つかるまでバッファオーバーランして暗号文として扱われる
    本来暗号文でない部分まで暗号文として扱われる
という結果になります。

当然,ブロック長の倍数から外れることが多いでしょうし,CryptProtectData function (Windows)によると,このAPIはMAC (メッセージ認証コード) を追加するようなので,MACの検証で弾かれると思います。
オフトピック
一応,ブロック長の倍数であれば形式上復号は可能です。それが本来の平文にはならないでしょうが。
Ketty さんが書きました:②解決するためには、
上記のようにキャストするのではなく、
バイナリを文字列に変換したものをstringに代入する必要があり、
バイナリを文字列に変換する方法としては、base64符号化(など)がある。
どこかでCスタイル文字列として取り扱いたいのであれば,何らかの符号化は必須です。
std::stringを使うことだけを考えての解決策であれば,std::basic_string::assignの文字列長指定版や反復子指定版を使うことも解決策に含まれます。
ただし,バイナリを直接std::stringで取り扱うことはあまり推奨しませんが。

アバター
Ketty
記事: 103
登録日時: 11年前

Re: (C++)CryptProtectData/CryptUnprotectDataについて

#5

投稿記事 by Ketty » 11年前

Yuoさん
ご回答くださり、ありがとうございます。

>複合ではなく復号ですよ。
タイプ変換ミスしてましたm(__)mご指摘ありがとうございます。

>std::stringの代入演算子は引数がCスタイル文字列であることを想定しているため,
なるほど・・・おっしゃるとおりですね。無理がありますね。
ナル文字のことを意識できておりませんでした。

理解できました。

>std::stringを使うことだけを考えての解決策であれば,
>std::basic_string::assignの文字列長指定版や反復子指定版を使うことも解決策に含まれます。
一度、base64エンコードするやり方をトライしてみます。
実装に時間がかかりそうなので、
ひとまずこのトピックとしては解決とさせていただきますm(__)m
原因が分かって助かりました。ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る