12

サーバーがそれに応じて情報にアクセスするために、ID、パスワード、および他のサードパーティサイトへの API キーを利用するサイトを作成しています。この会話の目的のために、それが支払いゲートウェイ用であると仮定しましょう。つまり、DB に保存されているこの情報が公開されると、悪意のあるユーザーが資格情報が漏洩したアカウントから現金を引き出すことができることを意味します。

残念ながら、これはパスワード/ハッシュの状況とは異なります。ユーザーは資格情報を毎回入力するわけではないためです。一度入力すると、アプリケーションで将来使用するためにサーバーに保存されます。

私が思いつく唯一の合理的な方法 (これは MySQL/PHP アプリケーションになります) は、PHP アプリケーションでハードコードされた「パスワード」を使用して資格情報を暗号化することです。ここでの唯一の利点は、悪意のあるユーザー/ハッカーがデータベースにアクセスできても、PHP コードにはアクセスできない場合でも、何も得られないことです。とはいえ、これは私には無意味に思えます。なぜなら、ハッカーがどちらかを手に入れれば、すべてを手に入れると合理的に想定できると思うからです。

コミュニティがいくつかの優れた解決策を決定した場合は、他のソースから例/チュートリアル/より詳細な情報を収集して、将来すべての人に実装できるようにすることをお勧めします.

この質問に、スタックに関する適切な回答が既に表示されていないことに驚きました。私はこれを見つけましたが、私の場合、これは実際には当てはまりません:後でプレーンテキストを取得するためにユーザーパスワードの保存に倫理的にアプローチするにはどうすればよいですか?

皆さんありがとう。

4

5 に答える 5

8

質問、回答、コメントで私が見ることができるものに基づいています。OpenSSLを利用することをお勧めします。これは、サイトがこの情報に定期的にアクセスする必要があることを前提としています(つまり、スケジュールを設定できます)。あなたが述べたように:

サーバーは、あらゆる種類の状況で支払いを送信するためにこの情報を必要とします。ログインするのに上記のキーの「所有者」は必要ありません。実際、所有者は、最初にキーを提供した後は、キーを二度と見る必要がない場合があります。

これはこのコメントからのものであり、保存したいデータへのアクセスはcronジョブ内に置くことができるという仮定があります。さらに、機密ユーザー情報を処理するため、サーバーにSSL(https)があり、OpenSSLおよびmcryptモジュールが使用可能であると想定されます。また、以下は、「どのように」それを達成できるかについて、かなり一般的です。しかし、実際にはあなたの状況ごとにそれを行うことの詳細ではありません。この「ハウツー」は一般的なものであり、実装する前にさらに調査を行う必要があることにも注意してください。そうは言っても、始めましょう。

まず、OpenSSLが提供するものについて話しましょう。OpenSSLは、公開鍵暗号化を提供します。公開鍵を使用してデータを暗号化する機能(これは、暗号化されたデータのセキュリティを危険にさらすことはありません)。次に、公開鍵を使用してその情報にアクセスする方法を提供します。 '秘密鍵。証明書の作成は気にしないので(暗号化キーのみが必要です)、単純な関数(1回だけ使用します)で取得できます。

function makeKeyPair()
{
    //Define variables that will be used, set to ''
    $private = '';
    $public = '';
    //Generate the resource for the keys
    $resource = openssl_pkey_new();

    //get the private key
    openssl_pkey_export($resource, $private);

    //get the public key
    $public = openssl_pkey_get_details($resource);
    $public = $public["key"];
    $ret = array('privateKey' => $private, 'publicKey' => $public);
    return $ret;
}

これで、公開鍵と秘密鍵ができました。秘密鍵を保護し、サーバーから遠ざけ、データベースから遠ざけます。別のサーバー、cronジョブを実行できるコンピューターなどに保存します。支払いの処理とAES暗号化などで秘密鍵を暗号化する必要があるたびに管理者の立ち会いを要求できない限り、世間の注目を集めることはできません。似ている。ただし、公開鍵はアプリケーションにハードコードされており、ユーザーが保存する情報を入力するたびに使用されます。

次に、復号化されたデータを検証する方法を決定する必要があります(したがって、無効なリクエストで支払いAPIへの投稿を開始しません)。保存する必要のあるフィールドが複数あると想定します。一度暗号化すると、シリアル化できるPHP配列になります'd。保存する必要のあるデータの量に応じて、データを直接暗号化するか、公開鍵で暗号化するパスワードを生成し、そのランダムなパスワードを使用してデータ自体を暗号化することができます。説明ではこのルートに行きます。このルートを使用するには、AES暗号化を使用し、暗号化および復号化機能を手元に用意する必要があります。また、データ用の適切なワンタイムパッドをランダムに生成する方法も必要です。私が使用するパスワードジェネレータを提供しますが、しばらく前に書いたコードから移植しましたが、それは目的を果たすか、より良いものを書くことができます。^^

public function generatePassword() {
    //create a random password here
    $chars = array( 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F', 'g', 'G', 'h', 'H', 'i', 'I', 'j', 'J',  'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N', 'o', 'O', 'p', 'P', 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T',  'u', 'U', 'v', 'V', 'w', 'W', 'x', 'X', 'y', 'Y', 'z', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '?', '<', '>', '.', ',', ';', '-', '@', '!', '#', '$', '%', '^', '&', '*', '(', ')');

    $max_chars = count($chars) - 1;
    srand( (double) microtime()*1000000);

    $rand_str = '';
    for($i = 0; $i < 30; $i++)
    {
            $rand_str .= $chars[rand(0, $max_chars)];
    }
    return $rand_str;

}

この特定の関数は30桁を生成し、適切なエントロピーを提供しますが、必要に応じて変更できます。次に、AES暗号化を行う関数:

/**
 * Encrypt AES
 *
 * Will Encrypt data with a password in AES compliant encryption.  It
 * adds built in verification of the data so that the {@link this::decryptAES}
 * can verify that the decrypted data is correct.
 *
 * @param String $data This can either be string or binary input from a file
 * @param String $pass The Password to use while encrypting the data
 * @return String The encrypted data in concatenated base64 form.
 */
public function encryptAES($data, $pass) {
    //First, let's change the pass into a 256bit key value so we get 256bit encryption
    $pass = hash('SHA256', $pass, true);
    //Randomness is good since the Initialization Vector(IV) will need it
    srand();
    //Create the IV (CBC mode is the most secure we get)
    $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
    //Create a base64 version of the IV and remove the padding
    $base64IV = rtrim(base64_encode($iv), '=');
    //Create our integrity check hash
    $dataHash = md5($data);
    //Encrypt the data with AES 128 bit (include the hash at the end of the data for the integrity check later)
    $rawEnc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $pass, $data . $dataHash, MCRYPT_MODE_CBC, $iv);
    //Transfer the encrypted data from binary form using base64
    $baseEnc = base64_encode($rawEnc);
    //attach the IV to the front of the encrypted data (concatenated IV)
    $ret = $base64IV . $baseEnc;
    return $ret;
}

(これらの関数は元々クラスの一部として作成したものであり、独自のクラスに実装することをお勧めします。)また、この関数の使用は、作成されたワンタイムパッドで問題ありませんが、別のアプリケーションのユーザー固有のパスワードの場合、パスワードに追加するには、そこにソルトが必要です。次に、復号化して復号化されたデータが正しいことを確認するには、次の手順に従います。

/**
 * Decrypt AES
 *
 * Decrypts data previously encrypted WITH THIS CLASS, and checks the
 * integrity of that data before returning it to the programmer.
 *
 * @param String $data The encrypted data we will work with
 * @param String $pass The password used for decryption
 * @return String|Boolean False if the integrity check doesn't pass, or the raw decrypted data.
 */
public function decryptAES($data, $pass){
    //We used a 256bit key to encrypt, recreate the key now
    $pass = hash('SHA256', $this->salt . $pass, true);
    //We should have a concatenated data, IV in the front - get it now
    //NOTE the IV base64 should ALWAYS be 22 characters in length.
    $base64IV = substr($data, 0, 22) .'=='; //add padding in case PHP changes at some point to require it
    //change the IV back to binary form
    $iv = base64_decode($base64IV);
    //Remove the IV from the data
    $data = substr($data, 22);
    //now convert the data back to binary form
    $data = base64_decode($data);
    //Now we can decrypt the data
    $decData = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $pass, $data, MCRYPT_MODE_CBC, $iv);
    //Now we trim off the padding at the end that php added
    $decData = rtrim($decData, "\0");
    //Get the md5 hash we stored at the end
    $dataHash = substr($decData, -32);
    //Remove the hash from the data
    $decData = substr($decData, 0, -32);
    //Integrity check, return false if it doesn't pass
    if($dataHash != md5($decData)) {
        return false;
    } else {
        //Passed the integrity check, give use their data
        return $decData;
    }
}

両方の関数を見て、コメントなどを読んでください。それらがどのように機能し、どのように機能するかを理解して、誤って実装しないようにしてください。次に、ユーザーデータを暗号化します。公開鍵で暗号化します。次の関数は、これまでの(および今後の)すべての関数が同じクラスにあることを前提としています。後で必要になるので、両方のOpenSSL暗号化/復号化機能を一度に提供します。

/**
 * Public Encryption
 *
 * Will encrypt data based on the public key
 *
 * @param String $data The data to encrypt
 * @param String $publicKey The public key to use
 * @return String The Encrypted data in base64 coding
 */
public function publicEncrypt($data, $publicKey) {
    //Set up the variable to get the encrypted data
    $encData = '';
    openssl_public_encrypt($data, $encData, $publicKey);
    //base64 code the encrypted data
    $encData = base64_encode($encData);
    //return it
    return $encData;
}

/**
 * Private Decryption
 *
 * Decrypt data that was encrypted with the assigned private
 * key's public key match. (You can't decrypt something with
 * a private key if it doesn't match the public key used.)
 *
 * @param String $data The data to decrypt (in base64 format)
 * @param String $privateKey The private key to decrypt with.
 * @return String The raw decoded data
 */
public function privateDecrypt($data, $privateKey) {
    //Set up the variable to catch the decoded date
    $decData = '';
    //Remove the base64 encoding on the inputted data
    $data = base64_decode($data);
    //decrypt it
    openssl_private_decrypt($data, $decData, $privateKey);
    //return the decrypted data
    return $decData;
}

これらの$data中には、ユーザー情報ではなく、常にワンタイムパッドになります。次に、暗号化と復号化のためにワンタイムパッドの公開鍵暗号化とAESの両方を組み合わせる機能。

/**
 * Secure Send
 *
 * OpenSSL and 'public-key' schemes are good for sending
 * encrypted messages to someone that can then use their
 * private key to decrypt it.  However, for large amounts
 * of data, this method is incredibly slow (and limited).
 * This function will take the public key to encrypt the data
 * to, and using that key will encrypt a one-time-use randomly
 * generated password.  That one-time password will be
 * used to encrypt the data that is provided.  So the data
 * will be encrypted with a one-time password that only
 * the owner of the private key will be able to uncover.
 * This method will return a base64encoded serialized array
 * so that it can easily be stored, and all parts are there
 * without modification for the receive function
 *
 * @param String $data The data to encrypt
 * @param String $publicKey The public key to use
 * @return String serialized array of 'password' and 'data'
 */
public function secureSend($data, $publicKey)
{
    //First, we'll create a 30digit random password
    $pass = $this->generatePassword();
    //Now, we will encrypt in AES the data
    $encData = $this->encryptAES($data, $pass);
    //Now we will encrypt the password with the public key
    $pass = $this->publicEncrypt($pass, $publicKey);
    //set up the return array
    $ret = array('password' => $pass, 'data' => $encData);
    //serialize the array and then base64 encode it
    $ret = serialize($ret);
    $ret = base64_encode($ret);
    //send it on its way
    return $ret;
}

/**
 * Secure Receive
 *
 * This is the complement of {@link this::secureSend}.
 * Pass the data that was returned from secureSend, and it
 * will dismantle it, and then decrypt it based on the
 * private key provided.
 *
 * @param String $data the base64 serialized array
 * @param String $privateKey The private key to use
 * @return String the decoded data.
 */
public function secureReceive($data, $privateKey) {
    //Let's decode the base64 data
    $data = base64_decode($data);
    //Now let's put it into array format
    $data = unserialize($data);
    //assign variables for the different parts
    $pass = $data['password'];
    $data = $data['data'];
    //Now we'll get the AES password by decrypting via OpenSSL
    $pass = $this->privateDecrypt($pass, $privateKey);
    //and now decrypt the data with the password we found
    $data = $this->decryptAES($data, $pass);
    //return the data
    return $data;
}

これらの機能を理解しやすくするために、コメントはそのまま残しました。ここで、実際にユーザーデータを操作して、楽しい部分に取り掛かります。メソッドの$datainsendは、シリアル化された配列のユーザーデータです。がハードコーディングされているsendメソッドを覚えておいて$publicKeyください。クラスに変数として格納し、その方法でアクセスして、渡す変数を減らしたり、他の場所から入力して毎回メソッドに送信したりできます。データを暗号化するための使用例:

$myCrypt = new encryptClass();
$userData = array(
    'id' => $_POST['id'],
    'password' => $_POST['pass'],
    'api' => $_POST['api_key']
);
$publicKey = "the public key from earlier";
$encData = $myCrypt->secureSend(serialize($userData), $publicKey));
//Now store the $encData in the DB with a way to associate with the user
//it is base64 encoded, so it is safe for DB input.

これは簡単な部分です。次の部分は、そのデータを使用できるようにすることです。そのためには、サーバー上に、サイトに必要な方法でユーザーなどを受け入れ$_POST['privKey']てループするページが必要です$encData。これから復号化するための使用例:

$myCrypt = new encryptClass();
$encData = "pulled from DB";
$privKey = $_POST['privKey'];
$data = unserialize($myCrypt->secureReceive($encData, $privKey));
//$data will now contain the original array of data, or false if
//it failed to decrypt it.  Now do what you need with it.

次に、秘密鍵を使用してその安全なページにアクセスするための特定の使用理論。public_html別のサーバーでは、秘密鍵を含まないphpスクリプトを実行するcronジョブがあり、それcurlを探しているページに秘密鍵を投稿するために使用します。( httpsで始まるアドレスを呼び出していることを確認してください)

これが、コードまたはデータベースにアクセスすることで危険にさらされることなく、アプリケーション内にユーザー情報を安全に保存する方法に役立つことを願っています。

于 2013-03-11T11:37:27.867 に答える
3

問題を要約できるかどうか見てみましょう - そして、私が問題を理解していることに対する私の答え。

ユーザーがアプリケーションにログインしてから、サードパーティの資格情報を保存したいと考えています。(それらの資格情報が何であるかは問題ではありません...) セキュリティのために、ハッカーがデータベースへのアクセスを取得した場合に、これらの資格情報を簡単に復号化できないようにする必要があります。

これが私が提案するものです。

  1. ユーザーがアプリケーションにログインするための認証システムを作成します。ユーザーは、サイトにアクセスするたびにログインする必要があります。これらの他のすべての資格情報へのアクセスを保存する場合、「remember me」は恐ろしい考えです。認証は、ユーザー名、パスワード、およびソルトを組み合わせてハッシュすることによって作成されます。このようにして、その情報はどれもデータベースに保存されません。

  2. ユーザー名とパスワードの組み合わせのハッシュ バージョンがセッションに保存されます。これがMASTER KEYになります。

  3. サードパーティ情報が入力されます。この情報は、MASTER KEY ハッシュを使用して暗号化されます。

つまり、これは...

ユーザーが自分のパスワードを知らない場合、彼らは運が悪いです。ただし、ハッカーが情報を入手するのは非常に困難な状況です。ユーザー名、パスワード、salt のハッシュを理解して認証を破る必要があります。次に、マスター キーのユーザー名/パスワードのハッシュ バージョンを取得し、それを使用してデータを復号化します。

ハッキングされる可能性はありますが、非常に困難です。また、この方法によれば、サーバー上の情報は暗号化されてから保存されるため、その情報を知ることはできないため、相対的な否定可能性が得られるとも言えます。この方法は、OnePassword のようなサービスが機能すると私が想定している方法と似ています。

于 2013-03-09T00:16:20.060 に答える
2

箱から出して考えて、箱から出して行動できる場合、いくつかの可能な解決策があります。

  1. サード パーティのサイトから、権限が制限されたアカウントを取得します。支払いゲートウェイの例では、支払いを承認して決済することはできますが、TransferToBankAccount($accountNumber) API を呼び出すことはできないアカウントです。

    • 同様に、プロバイダーに制限を設けるよう依頼してください。$accountNumber は、あなたが提供したいくつかのいずれかである必要があります。または、あなたがいる国と同じ国にある必要があります*。
  2. 一部の安全なシステムは、認証を提供するためにハードウェア トークンに依存しています。(私は主にキーの作成とドングルの署名について考えています。)これがあなたの状況でどのように機能するか正確にはわかりません。認証を要求するドングルがあり、特定の状況でのみ応答するとします。ユーザー名/パスワードを提供する (または提供しない) ことしかできない場合、これは困難です。(対して、リクエストパラメーターを調べることができるリクエストに署名します)。サードパーティへのリクエストにユーザー名/パス/およびクライアント署名が必要な場合、SSLクライアント証明書を使用してこれを行うことができます-サードパーティがそのようなことを受け入れる場合。

  3. #1と#2の組み合わせ。仲介役として機能する別のサーバーをセットアップします。このサーバーは、#1 でサードパーティが実行できると提案した基本的なビジネス ロジックを実装します。認証の詳細を取得して直接リクエストを行う前に、API を公開し、リクエストが「有効」であることを確認します (支払いは決済できますが、アカウントへの発信送金のみなど)。API には SetAuthDetails を含めることができますが、GetAuthDetails を含めることはできません。ここでの利点は、攻撃者が妥協することが 1 つ増えることです。また、サーバーが単純であるほど、強化が容易になります。(SMTP、FTP、およびメイン サーバーにあるバグの可能性がある PHP スタックを実行する必要はありません。ほんの数個の PHP スクリプト、ポート 443、SSH、および sqlite インスタンスなどです。HTTPD と PHP を最新の状態に保つがあるので簡単にする必要があります」

  4. 危険にさらされ、潜在的な影響を監視/監査すると仮定します。(はい)ログインして認証ログを確認するための認証詳細(または、理想的には読み取り専用権限)を持つ別のサーバーをどこかに用意します。毎分チェックし、不正なログインおよび/またはトランザクションをチェックし、抜本的なことを行います (おそらく、パスワードをランダムなものに変更してページングします)。

*実際に支払いゲートウェイについて話しているかどうかにかかわらず、ハッキングされることを懸念しているサードパーティは、あなた (または他のクライアント) がハッキングされた場合を含め、自身のセキュリティについても心配する必要があります. 彼らはまた、ある程度の責任を負っているので、安全策を講じる必要があります.

于 2013-03-13T03:47:27.950 に答える
0

ユーザーはすべて技術に精通しているわけではありませんが、サードパーティのサイトのクレデンシャルを提供するように求められたユーザーは、できるだけ早くサイトから逃げる必要があります。これは単に悪い考えです。プレーンテキストのパスワードの保存に関する質問は、ここでも間違いなく当てはまります。しないでください。

あなたは、問題の第三者についても、彼らとの関係についても、多くの文脈を提供していません。しかし、彼らがあなたのユースケースをサポートするためにいくつかの変更を喜んで行うかどうかについて彼らと話し合うべきです。それらにoauthを実装することは良い解決策になるでしょう。

代替案を探したい場合は、フェデレーションIDを調べてください

于 2013-03-14T22:06:28.757 に答える
0

X 基準に基づいてランダムなソルトを使用する場合、予測できるがハッカーには予測できない場合、コードの書き方によっては、ハッカーがすべてにアクセスできたとしても、何が何であるかが明らかにならない場合があります。

たとえば、現在の日時とユーザーの IP アドレスをソルトとして使用します。次に、それらの値をハッシュとともにデータベースに保存します。ハッシュの作成に使用される関数を難読化すると、salt が何であったかがそれほど明白でなくなる可能性があります。もちろん、決心したハッカーなら最終的にはそれを破ることができますが、それによって時間と追加の保護レベルが得られます。

于 2013-03-02T21:15:28.080 に答える