C ++で機密性の高い文字列を不明瞭にする手法


87

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

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

そのような機密データを不明瞭にするためにどのようなテクニックを使用すべきですか?

編集:

もちろん、皆さんのほとんどが「実行可能ファイルをリバースエンジニアリングできる」と言っています -もちろんです!これは私のうんざりなので、ここで少し怒らせます。

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

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

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

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


2
おそらく、暗号化キーを使用して達成しようとしていることを説明すると、これを回避する方法を回避するためのアドバイスを提供できます。
joshperry


4
@キリル:この質問をあなたが言及した質問とまったく同じものと呼ぶのは難しいです。確かに、考え方は同じです。問題はありません。
xtofl 2009年

1
@xtofl、あなたは投票しない自由です。私にとっては、2つの同一の質問のように見えます。
キリルV.リャドビンスキー

2
(暴言とは関係ありません)「秘密鍵」の定義そのものは、公開していない公開鍵と秘密鍵のペアの半分です。この場合も、秘密鍵はサーバー上に保持するものです。クライアントアプリの1つは公開されています。
MSalters 2009年

回答:


42

基本的に、あなたのプログラムへのアクセスおよびデバッガと誰もができるなり、彼らがしたい場合は、アプリケーションで鍵を見つけます。

ただし、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があります。Themidaは、バイナリを保護するという素晴らしい仕事をします。Spotifyなどのよく知られたプログラムでリバースエンジニアリングから保護するためによく使用されます。OllyDbgやIda Proなどのプログラムでのデバッグを防止する機能があります。

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

パスワードの一致

ここで誰かがパスワードと塩のハッシュについて話し合いました。

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

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


ユーザーが入力する必要があるパスワードの場合は、パスワードのハッシュ+塩を保存します。以下の回答を参照してください。

1
@hapalibashi:アプリケーションにソルトを安全に保存するにはどうすればよいですか?私は、OPが一方向のパスワード照合システムを必要としていないと思います。静的なキーを格納する一般化された方法だけです。
csl

2
逆アセンブルされたプログラムを見ると、通常はXORがそれほど多くないので、XORを使用して何かを不明瞭にすることを希望している場合は、それらが自分自身に注意を向けることに注意してください。
kb。

2
@kb-それは興味深い点です。私はあなたがビットごとのands and orsがxorよりもはるかに多く発生するのを見ていると思います。a ^ b ==(a&〜b)|| (〜a&b)
ジェレミー・パウエル

4
ソルト値を知っていても、通常、敵に利点はありません。ソルトのポイントは、「辞書攻撃」を回避することです。これにより、攻撃者は多くの可能性のある入力のハッシュを事前に計算しました。ソルトを使用すると、ソルトに基づく新しい辞書で辞書を事前に計算する必要があります。各ソルトが1回だけ使用される場合、辞書攻撃は完全に役に立たなくなります。
エドワードディクソン

8

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

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

1)文字列の要素を何らかの方法で非表示にします-別の文字列で文字列をxoring(^演算子)するような明白なものは、文字列を検索不可能にするのに十分です。

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

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

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

何よりも、あなたのキーが発見されたとき(そしてあなたのアプリケーションが十分にポピュラーになったとき)はそれほど重要ではないことを確認してください!


5

私が過去に使用した戦略は、ランダムに見える文字の配列を作成することです。最初に挿入してから、代数的プロセスを使用して特定の文字を見つけます。ここで、0からNまでの各ステップは、数値<配列のサイズ<サイズを生成します。これには、難読化された文字列の次の文字が含まれます。(この答えは今、難読化されています!)

例:

文字の配列が与えられた(数値とダッシュは参照用のみです)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

そして、最初の6つの結果が3、6、7、10、21、47である方程式

「HELLO!」という単語が生成されます 上記の配列から。


良い考え-配列に非印刷文字を使用することでさらに改善できると思います...
Thomi

4

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

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

std::string myKey = part1() + part2() + ... + partN();

確かに、バイナリを検索するときに文字列が明らかになるのを防ぎます。しかし、あなたの文字列はまだメモリに常駐しています。あなたの解決策はおそらく私がやっていることには十分です。
トミ

@Thomi、もちろん、使い終わったらすぐに破棄できます。しかし、それでも、機密性の高い文字列を処理するための最良の方法ではありません。
Nick Dandoulakis

...破壊しても、メモリがすぐに再利用されるとは限りません。
THOMI

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;
    // ...
}

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;

復号化ループを持つユニコード文字列として:

// 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;

いや、私が使ってきたstringencrypt.com仕事をするためにウェブサイトを。これには、C / C ++ stringencrypt.com/c-cpp-encryptionの例があり、これを使用して単純な文字列暗号化を自動化することを検討できます。
BartoszWójcik2013年

2

joshperryが指摘するように、保護しようとしているものにある程度依存しています。経験上、ソフトウェアを保護するためのライセンススキームの一部である場合は、気にしないでください。最終的にはリバースエンジニアリングを行います。ROT-13のような単純な暗号を使用して、単純な攻撃から保護します(その上で文字列を実行します)。ユーザーの機密データを保護する場合は、ローカルに格納された秘密キーでそのデータを保護することが賢明な方法であるかどうか疑問に思うでしょう。繰り返しますが、それはあなたが保護しようとしていることに帰着します。

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


2

前述のとおり、弦を完全に保護する方法はありません。しかし、それを合理的な安全で保護する方法があります。

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

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


良い考え-別の形式のあいまいさは、まだかなり適度に強い(長い、句読点などすべてのジャズ)文字列を使用することですが、明らかにパスワードのようには見えません。
トミ

1

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

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

基本的に、秘密鍵をバイナリ内に保存する方法に関するこれらのかわいいアイデアはすべて、セキュリティの観点から見て、実行すべきではないほど不十分です。誰かがあなたの秘密鍵を手に入れるのは大変なことです。あなたのプログラムの中にそれを保管しないでください。アプリのインポート方法に応じて、秘密キーをスマートカードに保存したり、リモートコンピューターでコードが通信したり、ほとんどの人が行うことを実行してローカルコンピューターの非常に安全な場所(「キーストア」は、奇妙で安全なレジストリのようなものです)、権限とOSのすべての強度によって保護されています。

これは解決された問題であり、答えはプログラム内にキーを保持することではありません:)


1

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


1

私が最近試した1つの方法は次のとおりです。

  1. プライベートデータのハッシュ(SHA256)を取得し、コードに次のように入力します。 part1
  2. プライベートデータとそのハッシュのXORを取り、次のようにコードに入力します。 part2
  3. データを入力します。charstr []として保存せず、実行時に割り当て手順を使用して入力します(以下のマクロに示されています)。
  4. 次に、part1とのXORを取ることにより、実行時にプライベートデータを生成します。part2
  5. 追加の手順:生成されたデータのハッシュを計算し、それと比較し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);

0

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


はい、いいえ..通常、私はあなたに同意しますが、この場合は、ソフトウェアのユーザーからこれを隠そうとしているため、外部システムに保存することはお勧めできません(多くのキーチェーンシステムでは、適切な権限を与えられたユーザーにパスワードをプレーンテキストとして公開します)。キーチェーンソフトウェアはユーザーパスワードには最適ですが、アプリケーションの暗号化キーにはあまり適していません。
トミ

それが安全でないと感じた場合(説明が適切であれば多分それで十分です)、キーチェーンとハードコードされたものを組み合わせることができます:)

0

コンテキストに依存しますが、キーのハッシュソルト(定数文字列、不明瞭になりやすい)を格納するだけで済みます。

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

塩は、おそらくこのような場合には不要であるハッシュを単離することができるならば、それは(Google検索でも仕事を知っていた)ブルートフォース辞書攻撃を停止します。

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


これは暗号化キーであり、パスワードハッシュではありません。データをエンコードおよびデコードするには、実際のキーが必要です。ユーザーはキーを見ることはなく、バイナリの外部に保存されることはありません。
THOMI

0

完全に機能するadamyaxleyによって作成された(非常に軽い)ヘッダーのみのプロジェクト難読化があります。これはラムダ関数とマクロに基づいており、コンパイル時にXOR暗号を使用して文字列をリテラルに暗号化します。必要に応じて、各文字列のシードを変更できます。

次のコードは、コンパイルされたバイナリに文字列「hello world」を格納しません。

#include "obfuscate.h"

int main()
{
  std::cout << AY_OBFUSCATE("Hello World") << std::endl;
  return 0;
}

私はc ++ 17とビジュアルスタジオ2019でテストし、IDAを介して確認しました。文字列が非表示になっていることを確認しました。ADVobfuscatorと比較した重要な利点の1つは、std :: stringに変換できることです(コンパイルされたバイナリではまだ非表示です)。

std::string var = AY_OBFUSCATE("string");
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.