96

C++ アプリケーションに機密情報 (非公開にしたい対称暗号化キー) を保存する必要があります。簡単なアプローチはこれを行うことです:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

ただし、stringsプロセス (またはバイナリ アプリから文字列を抽出するその他のプロセス) を通じてアプリケーションを実行すると、上記の文字列が明らかになります。

このような機密データを隠すには、どのような手法を使用する必要がありますか?

編集:

OK、ほとんどすべての人が「実行可能ファイルはリバース エンジニアリングできる」と言っていましたが、もちろんです! これは私のペットのおしっこなので、ここで少し怒鳴ります。

このサイトのすべてのセキュリティ関連の質問の 99% (わかりました、少し誇張しているかもしれません) が、「完全に安全なプログラムを作成する方法はありません」という急流で答えられているのはなぜですか - これは役に立ちません。答え!セキュリティは、完全なユーザビリティとセキュリティなしの一方の端と、完全なセキュリティだが他方の端のユーザビリティとの間のスライディング スケールです。

要点は、何をしようとしているか、およびソフトウェアが実行される環境に応じて、そのスライド スケールで自分の位置を選択することです。私は軍事施設用のアプリを書いているのではなく、家庭用 PC 用のアプリを書いています。既知の暗号化キーを使用して、信頼されていないネットワークでデータを暗号化する必要があります。このような場合は、「隠蔽によるセキュリティ」で十分でしょう。確かに、十分な時間、エネルギー、スキルがあれば、バイナリをリバース エンジニアリングしてパスワードを見つけることができます。私は気にしない:

一流の安全なシステムを実装するのにかかる時間は、クラックされたバージョンによる売上の損失よりも高価です (実際にこれを販売しているわけではありませんが、私の主張は理解できます)。新人プログラマーの間のプログラミングにおけるこの青空の「可能な限り最善の方法でやろう」という傾向は、控えめに言ってもばかげています。

この質問に回答していただき、ありがとうございます。とても役に立ちました。残念ながら、私は1つの回答しか受け入れることができませんが、すべての有用な回答に賛成票を投じました.

4

14 に答える 14

44

基本的に、プログラムとデバッガーにアクセスできる人なら誰でも、必要に応じてアプリケーション内のキーを見つけることができます。

ただし、バイナリで実行しているときにキーが表示されないようにするだけ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 つの方法は、ツールを使用してバイナリを保護することです。たとえば、バイナリが難読化されていることを確認し、バイナリが実行されている仮想マシンを起動できるセキュリティ ツールがいくつかあります。これにより、デバッグが(より)難しくなり、多くの商用グレードの安全なアプリケーション(悲しいかな、マルウェア)が保護される従来の方法でもあります.

主要なツールの 1 つはThemidaで、バイナリを保護する素晴らしい仕事をします。リバースエンジニアリングから保護するために、Spotify などのよく知られたプログラムでよく使用されます。OllyDbg や Ida Pro などのプログラムでのデバッグを防止する機能があります。

バイナリを保護するためのツールのより大きなリストもありますが、多少古いかもしれません。
それらのいくつかは無料です。

パスワード照合

ここの誰かが、パスワードとソルトのハッシュについて議論しました。

ユーザーが提出したある種のパスワードと照合するためにキーを保存する必要がある場合は、できればユーザー名、パスワード、およびソルトを組み合わせて、一方向ハッシュ関数を使用する必要があります。ただし、これの問題は、一方向を実行して結果のハッシュを比較できるようにするには、アプリケーションがソルトを認識している必要があることです。したがって、アプリケーションのどこかにソルトを保存する必要があります。ただし、@Edward が以下のコメントで指摘しているように、これにより、レインボー テーブルなどを使用した辞書攻撃から効果的に保護されます。

最後に、上記のすべての手法を組み合わせて使用​​できます。

于 2009-10-30T08:37:04.087 に答える
9

まず第一に、十分に決心したハッカーを阻止するためにできることは何もないことを認識してください。すべてのゲームとコンソールの保護は最終的にクラックされるため、これは一時的な修正にすぎません.

しばらく隠れていられる可能性を高めるためにできることは 4 つあります。

1) 何らかの方法で文字列の要素を非表示にします。文字列を別の文字列で xor 処理する (^ 演算子) などの明白な方法で、文字列を検索できなくするのに十分です。

2) 文字列を分割します -- 文字列を分割し、その一部を奇妙なモジュール内の奇妙な名前のメソッドにポップします。文字列を含むメソッドを簡単に検索して見つけられるようにしないでください。もちろん、一部のメソッドはこれらすべてのビットを呼び出す必要がありますが、それでも少し難しくなります。

3) 文字列をメモリ内に構築しないでください。ほとんどのハッカーは、文字列をエンコードした後でメモリ内の文字列を参照できるようにするツールを使用します。可能であれば、これを避けてください。たとえば、キーをサーバーに送信する場合は、1 文字ずつ送信して、文字列全体が存在しないようにします。もちろん、RSA エンコーディングなどから使用している場合、これはよりトリッキーです。

4) アドホック アルゴリズムを実行します。これに加えて、独自のひねりを加えます。おそらく、生成するすべてのものに1を追加するか、暗号化を2回行うか、砂糖を追加するだけです。これは、誰かがバニラの md5 ハッシュや RSA 暗号化などを使用しているときに、何を探すべきかを既に知っているハッカーにとっては、少し難しくなります。

何よりも、鍵がいつ発見されるかが重要ではないことを確認してください。

于 2009-10-30T08:55:23.947 に答える
4

もちろん、ユーザーに出荷されるソフトウェアに個人データを保存することには常にリスクがあります。十分な教育を受けた (そして熱心な) エンジニアであれば、データをリバース エンジニアリングすることができます。

そうは言っても、多くの場合、人々があなたの個人データを明らかにするために乗り越えなければならない障壁を高くすることで、物事を十分に安全にすることができます. それは通常、良い妥協です。

あなたの場合、文字列を印刷できないデータで乱雑にし、次のように単純なヘルパー関数を使用して実行時にデコードできます。

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;
    // ...
}
于 2009-10-30T08:47:44.337 に答える
4

文字列用の単純な暗号化ツールを作成しました。暗号化された文字列を自動的に生成でき、それを行うためのいくつかの追加オプションがあります。いくつかの例:

グローバル変数としての文字列:

// 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;
于 2009-11-06T17:01:21.933 に答える
4

@Checkersに同意します。実行可能ファイルはリバースエンジニアリングできます。

少し良い方法は、動的に作成することです。次に例を示します。

std::string myKey = part1() + part2() + ... + partN();
于 2009-10-30T08:37:53.080 に答える
2

ジョシュペリーが指摘しているように、保護しようとしているものに多少依存します。経験から言えば、ソフトウェアを保護するためのライセンス スキームの一部であれば、気にする必要はありません。彼らは最終的にそれをリバースエンジニアリングします。ROT-13 のような単純な暗号を使用して、単純な攻撃 (文字列を実行する行) から保護するだけです。ユーザーの機密データを保護する場合、ローカルに保存された秘密鍵でそのデータを保護することが賢明な方法であるかどうか疑問に思います. 繰り返しますが、それはあなたが守ろうとしているものに帰着します。

編集:あなたがそれをするつもりなら、クリスが指摘するテクニックの組み合わせはrot13よりもはるかに優れています.

于 2009-10-30T09:01:35.637 に答える
2

前に述べたように、ストリングを完全に保護する方法はありません。しかし、合理的な安全性でそれを保護する方法があります。

私がこれをしなければならなかったとき、私は無害に見える文字列をコードに入れました(たとえば、著作権通知、偽のユーザープロンプト、または関係のないコードを修正しても変更されないその他のもの)、それ自体を使用して暗号化しましたキーとして、それをハッシュし(ソルトを追加)、その結果をキーとして使用して、実際に暗号化したいものを暗号化しました。

もちろん、これはハッキングされる可能性がありますが、そうするには断固たるハッカーが必要です。

于 2009-10-30T10:05:33.430 に答える
1

Windows ユーザー DPAPI を使用している場合は、http://msdn.microsoft.com/en-us/library/ms995355.aspx

以前の投稿で述べたように、Mac を使用している場合はキーチェーンを使用してください。

基本的に、バイナリ内に秘密鍵を保存する方法に関するこれらのかわいいアイデアはすべて、セキュリティの観点から見て十分に貧弱であるため、行うべきではありません。誰でもあなたの秘密鍵を取得することは重要です。プログラム内に保管しないでください。アプリのインポート方法に応じて、秘密鍵をスマート カードに保存したり、コードが通信するリモート コンピューターに保存したり、ほとんどの人が行うようにローカル コンピューターの非常に安全な場所に保存したりできます (「キー」 store" は、奇妙な安全なレジストリのようなものです) は、アクセス許可と OS のすべての機能によって保護されています。

これは解決済みの問題であり、答えはキーをプログラム内に保持しないことです:)

于 2009-11-02T20:00:36.193 に答える
1

これを試してください。ソース コードは、特定の Visual Studio c++ プロジェクト内のすべての文字列をその場で暗号化および復号化する方法を説明しています。

于 2015-11-19T19:56:44.450 に答える
0

コンテキストに依存しますが、キーのハッシュとソルト(定数文字列、わかりにくい) を格納するだけでかまいません。

次に、(if) ユーザーがキーを入力すると、 saltを追加し、ハッシュを計算して比較します。

この場合、 saltはおそらく不要です。ハッシュを分離できる場合は、ブルート フォース辞書攻撃を阻止します (Google 検索も機能することがわかっています)。

ハッカーは、全体をバイパスするためにどこかに jmp 命令を挿入するだけで済みますが、それは単純なテキスト検索よりもかなり複雑です。

于 2009-10-30T09:22:10.953 に答える
0

秘密鍵を実行可能ファイルに保存する代わりに、ユーザーに秘密鍵を要求し、Mac OS X キーチェーン アクセスに似た外部パスワード マネージャーを使用して保存することができます。

于 2009-10-30T08:40:10.187 に答える