C++アプリケーションに機密情報(秘密にしたい対称暗号化キー)を保存する必要があります。簡単なアプローチはこれを行うことです:
std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";
ただし、strings
プロセス(またはバイナリアプリから文字列を抽出する他のプロセス)を介してアプリケーションを実行すると、上記の文字列が表示されます。
このような機密データを不明瞭にするために、どの手法を使用する必要がありますか?
編集:
OK、だからあなたのほとんどすべてが「あなたの実行可能ファイルはリバースエンジニアリングできる」-もちろん!これは私の大嫌いなので、ここで少し暴言するつもりです。
このサイトのすべてのセキュリティ関連の質問の99%(OK、たぶん少し誇張している)が「完全に安全なプログラムを作成する方法はありません」という急流で回答されているのはなぜですか?回答!セキュリティは、完全なユーザビリティと一方のセキュリティなし、および完全なセキュリティと他方のユーザビリティなしの間のスライディングスケールです。
重要なのは、あなたがしようとしていることとソフトウェアが実行される環境に応じて、そのスライド式スケールで自分の位置を選ぶことです。 軍事施設用のアプリを書いているのではなく、自宅のPC用のアプリを書いています。信頼できないネットワーク上のデータを、既知の暗号化キーで暗号化する必要があります。これらの場合、「隠蔽によるセキュリティ」はおそらく十分です!確かに、十分な時間、エネルギー、スキルを持っている人は、バイナリをリバースエンジニアリングしてパスワードを見つけることができますが、何を推測できますか?私は気にしません:
一流の安全なシステムを実装するのにかかる時間は、ひびの入ったバージョンによる販売の損失よりも高価です(実際にこれを販売しているわけではありませんが、あなたは私の主張を理解しています)。新しいプログラマーの間でのプログラミングのこの青い空の「可能な限り絶対的な最善の方法」の傾向は、控えめに言っても愚かです。
この質問に答えてくれてありがとう-彼らは最も役に立ちました。残念ながら、私は1つの答えしか受け入れませんが、すべての有用な答えをアップ投票しました。
基本的に、プログラムとデバッガーにアクセスできる人は誰でもcanおよびwill必要に応じてアプリケーション内のキーを見つけます。
ただし、バイナリでstrings
を実行するときにキーが表示されないようにするだけの場合、たとえば、キーが印刷可能な範囲内にないことを確認できます。
XORでキーを隠す
たとえば、XORを使用してキーを2つのバイト配列に分割できます。
key = key1 XOR key2
key
と同じバイト長でkey1を作成する場合、(完全に)ランダムなバイト値を使用してからkey2
を計算できます:
key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]
ビルド環境でこれを実行し、key1
とkey2
のみをアプリケーションに保存できます。
バイナリの保護
別のアプローチは、ツールを使用してバイナリを保護することです。たとえば、バイナリが難読化され、それが実行されている仮想マシンを起動することを確認できるセキュリティツールがいくつかあります。これにより、デバッグが困難になります。また、多くの商用グレードのセキュリティで保護されたアプリケーション(また、悲しいかな、マルウェア)が保護されます。
主要なツールの1つは Themida です。これは、バイナリを保護するという素晴らしい仕事をします。リバースエンジニアリングから保護するために、Spotifyなどの有名なプログラムでよく使用されます。 OllyDbgやIda Proなどのプログラムでのデバッグを防止する機能があります。
バイナリを保護するツール のより大きなリストもあります。
一部は無料です。
パスワードの一致
ここで誰かがパスワード+塩のハッシュについて議論しました。
何らかの種類のユーザーが送信したパスワードと照合するためにキーを保存する必要がある場合は、ユーザー名、パスワード、ソルトを組み合わせて、一方向ハッシュ関数を使用することをお勧めします。ただし、これに関する問題は、アプリケーションが一方向を実行し、結果のハッシュを比較するためにソルトを知る必要があることです。したがって、アプリケーションのどこかに塩を保存する必要があります。しかし、@ Edwardが以下のコメントで指摘しているように、これは、例えばRainbowテーブルを使用した辞書攻撃から効果的に保護します。
最後に、上記のすべての手法を組み合わせて使用できます。
過去に使用した戦略は、一見ランダムな文字の配列を作成することです。最初に挿入してから、代数プロセスで特定の文字を見つけます。このプロセスでは、0〜Nの各ステップが、難読化された文字列の次の文字を含む配列の数<サイズを生成します。 (この答えは今、難読化されていると感じています!)
例:
文字の配列を指定します(数字とダッシュは参照専用です)
0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL
そして、最初の6つの結果が3、6、7、10、21、47である方程式
「HELLO!」という言葉が得られます上記の配列から。
文字列用の簡単な暗号化ツールを作成しました。暗号化された文字列を自動的に生成でき、それを行うためのいくつかの追加オプション、いくつかの例があります。
グローバル変数としての文字列:
// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };
myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;
復号化ループを含むUnicode文字列として:
// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];
myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;
for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;
グローバル変数としての文字列:
// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };
for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;
@Checkersに同意します。実行可能ファイルをリバースエンジニアリングできます。
もう少し良い方法は、動的に作成することです。例えば:
std::string myKey = part1() + part2() + ... + partN();
もちろん、ユーザーに出荷されるソフトウェアに個人データを保存することは常にリスクです。十分な教育を受けた(そして専任の)エンジニアなら誰でもデータをリバースエンジニアリングできます。
そうは言っても、個人データを明らかにするために人々が克服しなければならない障壁を高めることで、物事を十分に安全にすることができます。それは通常、良い妥協です。
あなたの場合、文字列を印刷不可能なデータで乱雑にし、次のような単純なヘルパー関数を使用して実行時にそれをデコードできます:
void unscramble( char *s )
{
for ( char *str = s + 1; *str != 0; str += 2 ) {
*s++ = *str;
}
*s = '\0';
}
void f()
{
char privateStr[] = "\001H\002e\003l\004l\005o";
unscramble( privateStr ); // privateStr is 'Hello' now.
string s = privateStr;
// ...
}
前述したように、弦を完全に保護する方法はありません。しかし、それを保護する方法は合理的な安全性があります。
私がこれをしなければならなかったとき、私はコードにいくつかの無邪気な文字列(たとえば、著作権表示、または偽のユーザープロンプトまたは無関係なコードを修正する誰かによって変更されない他のもの)を入れ、それを使用してそれを暗号化しましたキーとして、それをハッシュし(ソルトを追加)、結果をキーとして使用して、実際に暗号化するものを暗号化しました。
もちろんこれはハッキングされる可能性がありますが、それを行うには熱心なハッカーが必要です。
Joshperryが指摘するように、保護しようとしているものにある程度依存しています。経験から、ソフトウェアを保護するためのライセンススキームの一部である場合、気にしないでください。最終的にリバースエンジニアリングを行います。 ROT-13のような単純な暗号を使用するだけで、単純な攻撃(文字列を実行するライン)から保護します。ユーザーの機密データを保護する場合、ローカルに保存された秘密キーでそのデータを保護することは賢明なことかと疑問に思うでしょう。繰り返しますが、それはあなたが保護しようとしているものに帰着します。
編集:あなたがそれをやろうとしているなら、クリスが指摘する技術の組み合わせは、rot13よりもはるかに優れているでしょう。
WindowsユーザーDPAPIを使用している場合、 http://msdn.Microsoft.com/en-us/library/ms995355.aspx
以前の投稿で述べたように、Macを使用している場合はキーチェーンを使用してください。
基本的に、バイナリ内に秘密鍵を保存する方法に関するこれらのかわいいアイデアはすべて、セキュリティの観点からは不十分であり、実行しないでください。秘密鍵を取得することは誰にとっても大きな問題です。プログラム内に保管しないでください。アプリのインポート方法に応じて、秘密キーをスマートカードに保存したり、コードが通信するリモートコンピューターに保存したり、ほとんどの人が行うことを実行してローカルコンピューターの非常に安全な場所に保存したりできます(「キーストア」は、アクセス許可とOSのすべての強度によって保護されている、奇妙な安全なレジストリのようなものです。
これは解決された問題であり、答えはプログラム内にキーを保持することではありません:)
最近試した方法の1つは次のとおりです。
part1
としてコードに入力しますpart2
として入力しますpart1
とpart2
のXORを取得して実行時にプライベートデータを生成しますpart1
と比較します。プライベートデータの整合性を検証します。データを取り込むMACRO:
プライベートデータが4バイトであるとします。割り当て命令を含むデータをランダムな順序で保存するマクロを定義します。
#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
char *p = str;\
p[3] = i3;\
p[2] = i2;\
p[0] = i0;\
p[1] = i1;\
}
次のように、part1
およびpart2
を保存する必要があるコードでこのマクロを使用します。
char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4);
POPULATE_DATA(part2, 5, 6, 7, 8);
this を試してください。ソースコードは、指定されたVisual Studio c ++プロジェクトのすべての文字列をオンザフライで暗号化および復号化する方法を説明しています。
実行可能ファイルに秘密キーを保存する代わりに、ユーザーに要求して、外部 パスワードマネージャー を使用して保存します。これは、Mac OS Xキーチェーンアクセスに似ています。
コンテキストに依存しますが、キーのhashとsalt(定数文字列、わかりにくい)を格納するだけで済みます。
次に、ユーザーがキーを入力した場合、saltを追加し、hashを計算して比較します。
saltはおそらくこの場合は不要です。ハッシュを分離できる場合、ブルートフォース辞書攻撃を阻止します(Google検索も機能することがわかっています)。
ハッカーは、jmp命令を挿入するだけでロット全体をバイパスできますが、単純なテキスト検索よりもかなり複雑です。