86

基本認証/基本証明書を使用するRESTfulAPIからプルしようとしていると仮定すると、そのユーザー名とパスワードをプログラムに保存するための最良の方法は何でしょうか?今は平文で座っているだけです。

UsernamePasswordCredentials creds = new UsernamePasswordCredentials("myName@myserver","myPassword1234");

よりセキュリティを重視するこれを行う方法はありますか?

ありがとう

4

7 に答える 7

118

重要な注意点:

認証システム全体を設計している場合は、たとえ暗号化されていても、パスワードを保存しないでください。ハッシュを保存し、ログイン時に提供されたパスワードが同じハッシュと一致するかどうかを確認します。そうすることで、データベースのセキュリティ違反により、ユーザーのパスワードが公開されるのを防ぐことができます。

そうは言っても、データをそのまま(この場合はパスワード)保存し、次に内部から外部への考え方で保存する状況では、プロセスを保護するためのいくつかの手順を次に示します。


最初のステップでは、パスワード処理をからStringに変更する必要がありますcharacter array

これは、aStringimmutableオブジェクトであるため、オブジェクトがnull;に設定されていても、そのデータはすぐにはクレンジングされないためです。Stringデータは代わりにガベージコレクション用に設定されており、悪意のあるプログラムがクリーンアップされる前にその(パスワード)データにアクセスする可能性があるため、セキュリティ上の問題が発生します。

これが、 SwingのJPasswordFieldのgetText()メソッドが非推奨になり、getPassword()文字配列を使用する主な理由です。


2番目のステップは、認証プロセス中に一時的にクレデンシャルを復号化するだけで、クレデンシャルを暗号化することです。または、サーバー側でハッシュするには、そのハッシュを保存し、元のパスワードを「忘れる」。

これは、最初のステップと同様に、脆弱性時間を可能な限り短くすることを保証します。

クレデンシャルはハードコーディングせず、代わりに、構成ファイルやプロパティファイル、データベースなど、一元化された構成可能で保守が容易な方法で保存することをお勧めします。

ファイルを保存する前にクレデンシャルを暗号化する必要があります。さらに、ファイル自体に2番目の暗号化を適用できます(クレデンシャルには2層の暗号化、他のファイルの内容には1層の暗号化)。

上記の2つの暗号化プロセスは、それぞれ多層化できることに注意してください。各暗号化は、概念的な例として、トリプルデータ暗号化標準(別名TDESおよび3DES)の個別のアプリケーションにすることができます。


ローカル環境が適切に保護された後(ただし、「安全」になることは決してありません!)、3番目のステップは、TLS(トランスポート層セキュリティ)またはSSL(Secure Sockets Layer)を使用して、送信プロセスに基本的な保護を適用することです。


4番目のステップは、他の保護方法を適用することです。

たとえば、「使用する」コンパイルに難読化手法を適用して、プログラムがイブさん、マロリーさん、または他の誰か(悪い-みんな)そして逆コンパイルしました。


更新1:

@ Damien.Bellのリクエストにより、最初と2番目のステップをカバーする例を次に示します。

    //These will be used as the source of the configuration file's stored attributes.
    private static final Map<String, String> COMMON_ATTRIBUTES = new HashMap<String, String>();
    private static final Map<String, char[]> SECURE_ATTRIBUTES = new HashMap<String, char[]>();
    //Ciphering (encryption and decryption) password/key.
    private static final char[] PASSWORD = "Unauthorized_Personel_Is_Unauthorized".toCharArray();
    //Cipher salt.
    private static final byte[] SALT = {
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
        (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,};
    //Desktop dir:
    private static final File DESKTOP = new File(System.getProperty("user.home") + "/Desktop");
    //File names:
    private static final String NO_ENCRYPTION = "no_layers.txt";
    private static final String SINGLE_LAYER = "single_layer.txt";
    private static final String DOUBLE_LAYER = "double_layer.txt";

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws GeneralSecurityException, FileNotFoundException, IOException {
        //Set common attributes.
        COMMON_ATTRIBUTES.put("Gender", "Male");
        COMMON_ATTRIBUTES.put("Age", "21");
        COMMON_ATTRIBUTES.put("Name", "Hypot Hetical");
        COMMON_ATTRIBUTES.put("Nickname", "HH");

        /*
         * Set secure attributes.
         * NOTE: Ignore the use of Strings here, it's being used for convenience only.
         * In real implementations, JPasswordField.getPassword() would send the arrays directly.
         */
        SECURE_ATTRIBUTES.put("Username", "Hypothetical".toCharArray());
        SECURE_ATTRIBUTES.put("Password", "LetMePass_Word".toCharArray());

        /*
         * For demosntration purposes, I make the three encryption layer-levels I mention.
         * To leave no doubt the code works, I use real file IO.
         */
        //File without encryption.
        create_EncryptedFile(NO_ENCRYPTION, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 0);
        //File with encryption to secure attributes only.
        create_EncryptedFile(SINGLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 1);
        //File completely encrypted, including re-encryption of secure attributes.
        create_EncryptedFile(DOUBLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 2);

        /*
         * Show contents of all three encryption levels, from file.
         */
        System.out.println("NO ENCRYPTION: \n" + readFile_NoDecryption(NO_ENCRYPTION) + "\n\n\n");
        System.out.println("SINGLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(SINGLE_LAYER) + "\n\n\n");
        System.out.println("DOUBLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(DOUBLE_LAYER) + "\n\n\n");

        /*
         * Decryption is demonstrated with the Double-Layer encryption file.
         */
        //Descrypt first layer. (file content) (REMEMBER: Layers are in reverse order from writing).
        String decryptedContent = readFile_ApplyDecryption(DOUBLE_LAYER);
        System.out.println("READ: [first layer decrypted]\n" + decryptedContent + "\n\n\n");
        //Decrypt second layer (secure data).
        for (String line : decryptedContent.split("\n")) {
            String[] pair = line.split(": ", 2);
            if (pair[0].equalsIgnoreCase("Username") || pair[0].equalsIgnoreCase("Password")) {
                System.out.println("Decrypted: " + pair[0] + ": " + decrypt(pair[1]));
            }
        }
    }

    private static String encrypt(byte[] property) throws GeneralSecurityException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));

        //Encrypt and save to temporary storage.
        String encrypted = Base64.encodeBytes(pbeCipher.doFinal(property));

        //Cleanup data-sources - Leave no traces behind.
        for (int i = 0; i < property.length; i++) {
            property[i] = 0;
        }
        property = null;
        System.gc();

        //Return encryption result.
        return encrypted;
    }

    private static String encrypt(char[] property) throws GeneralSecurityException {
        //Prepare and encrypt.
        byte[] bytes = new byte[property.length];
        for (int i = 0; i < property.length; i++) {
            bytes[i] = (byte) property[i];
        }
        String encrypted = encrypt(bytes);

        /*
         * Cleanup property here. (child data-source 'bytes' is cleaned inside 'encrypt(byte[])').
         * It's not being done because the sources are being used multiple times for the different layer samples.
         */
//      for (int i = 0; i < property.length; i++) { //cleanup allocated data.
//          property[i] = 0;
//      }
//      property = null; //de-allocate data (set for GC).
//      System.gc(); //Attempt triggering garbage-collection.

        return encrypted;
    }

    private static String encrypt(String property) throws GeneralSecurityException {
        String encrypted = encrypt(property.getBytes());
        /*
         * Strings can't really have their allocated data cleaned before CG,
         * that's why secure data should be handled with char[] or byte[].
         * Still, don't forget to set for GC, even for data of sesser importancy;
         * You are making everything safer still, and freeing up memory as bonus.
         */
        property = null;
        return encrypted;
    }

    private static String decrypt(String property) throws GeneralSecurityException, IOException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
        return new String(pbeCipher.doFinal(Base64.decode(property)));
    }

    private static void create_EncryptedFile(
                    String fileName,
                    Map<String, String> commonAttributes,
                    Map<String, char[]> secureAttributes,
                    int layers)
                    throws GeneralSecurityException, FileNotFoundException, IOException {
        StringBuilder sb = new StringBuilder();
        for (String k : commonAttributes.keySet()) {
            sb.append(k).append(": ").append(commonAttributes.get(k)).append(System.lineSeparator());
        }
        //First encryption layer. Encrypts secure attribute values only.
        for (String k : secureAttributes.keySet()) {
            String encryptedValue;
            if (layers >= 1) {
                encryptedValue = encrypt(secureAttributes.get(k));
            } else {
                encryptedValue = new String(secureAttributes.get(k));
            }
            sb.append(k).append(": ").append(encryptedValue).append(System.lineSeparator());
        }

        //Prepare file and file-writing process.
        File f = new File(DESKTOP, fileName);
        if (!f.getParentFile().exists()) {
            f.getParentFile().mkdirs();
        } else if (f.exists()) {
            f.delete();
        }
        BufferedWriter bw = new BufferedWriter(new FileWriter(f));
        //Second encryption layer. Encrypts whole file content including previously encrypted stuff.
        if (layers >= 2) {
            bw.append(encrypt(sb.toString().trim()));
        } else {
            bw.append(sb.toString().trim());
        }
        bw.flush();
        bw.close();
    }

    private static String readFile_NoDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return sb.toString();
    }

    private static String readFile_ApplyDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException {
        File f = new File(DESKTOP, fileName);
        BufferedReader br = new BufferedReader(new FileReader(f));
        StringBuilder sb = new StringBuilder();
        while (br.ready()) {
            sb.append(br.readLine()).append(System.lineSeparator());
        }
        return decrypt(sb.toString());
    }

すべての保護ステップに対処する完全な例は、 「ステップとは何か」に関するものであり、「それらを適用する方法」に関するものではないため、この質問に対して私が合理的だと思うものをはるかに超えます。

それは私の答えをはるかに大きくします(最後にサンプリング)が、SOに関する他の質問はすでにそれらのステップの「方法」に向けられており、はるかに適切であり、個々のステップ。

于 2012-10-19T19:31:58.333 に答える
8

基本認証を使用している場合は、それをSSLと組み合わせて、base64でエンコードされたプレーンテキストで資格情報が渡されないようにする必要があります。誰かがあなたのパケットを盗聴してあなたの資格情報を簡単に取得できるようにしたくはありません。また、ソースコードにクレデンシャルをハードコーディングしないでください。それらを構成可能にします。設定ファイルからそれらを読み取ります。クレデンシャルを構成ファイルに保存する前に暗号化する必要があります。アプリは、クレデンシャルを構成ファイルから読み取った後、クレデンシャルを復号化する必要があります。

于 2012-10-17T15:21:51.643 に答える
1
  1. 要求を初期化する安全なコンピューター(コンピューター)。そのマシンが安全でない場合、何もあなたを保護しません。これは完全に別のトピックです(最新のソフトウェア、適切に構成された強力なパスワード、暗号化されたスワップ、ハードウェアスニファ、物理的セキュリティなど)
  2. ストレージを保護するクレデンシャルの保存に使用するメディアは暗号化する必要があります。復号化されたクレデンシャルは、セキュリティで保護されたマシンのRAMにのみ保存する必要があります
  3. そのハードウェアを維持する人々は信頼されなければなりません(おそらく最も弱いリンク)
  4. 彼らはまた、できるだけ少なく知っている必要があります。それはゴムホース暗号解読からの保護です
  5. クレデンシャルは、すべてのセキュリティ推奨事項(適切な長さ、ランダム性、単一の目的など)を満たす必要があります
  6. リモートサービスへの接続を保護する必要があります(SSLなど)
  7. リモートサービスは信頼できる必要があります(ポイント1〜4を参照)。さらに、ハッキングが発生しやすいはずです(データ/サービスが安全でない場合、資格情報を保護することは無意味です)。さらに、それはあなたの資格情報を保存するべきではありません

それに加えて、おそらく私が忘れていた何千ものこと:)

于 2012-10-20T22:36:47.223 に答える
1

一般に、クレデンシャルを暗号化することはお勧めできません。暗号化されたものは復号化できます。一般的なベストプラクティスは、パスワードをソルトハッシュとして保存することですハッシュは復号化できません。レインボーテーブルでブルートフォース推測を打ち負かすために塩が追加されます。すべてのuserIdが独自のランダムなソルトを持っている限り、攻撃者はソルトのすべての可能な値に対して一連のテーブルを生成する必要があり、ユニバースの存続期間内にこの攻撃を迅速に不可能にします。これが、パスワードを忘れた場合にWebサイトが通常パス​​ワードを送信できない理由ですが、パスワードを「リセット」することしかできません。彼らはあなたのパスワードを保存しておらず、そのハッシュだけを保存しています。

パスワードハッシュを自分で実装することはそれほど難しいことではありませんが、解決するのは非常に一般的な問題であり、無数の他の人があなたのためにそれを行っています。jBcryptは使いやすいと思いました。

パスワードのブルートフォース推測に対する追加の保護として、間違ったパスワードで特定の回数のログイン試行が行われた後、userIdまたはリモートIPを数秒間待機させるのが一般的なベストプラクティスです。これがないと、ブルートフォース攻撃者はサーバーが処理できる数のパスワードを1秒あたりに推測できます。10秒間に100個のパスワードを推測できるか、100万個を推測できるかには大きな違いがあります。

ソースコードにユーザー名とパスワードの組み合わせが含まれているようです。これは、パスワードを変更したい場合は、サービスを再コンパイル、停止、再起動する必要があることを意味します。また、ソースコードを入手した人は誰でも、パスワードを持っていることを意味します。一般的なベストプラクティスは、これを行うことは決してありませんが、クレデンシャル(ユーザー名、パスワードハッシュ、パスワードソルト)をデータストアに保存することです。

于 2012-10-26T08:21:23.553 に答える
1

クレデンシャルをソースコードに保存してみませんか

一般に、クレデンシャルをソースコードに保存しないことをお勧めします。問題は、コードへのアクセスと、資格情報へのアクセス権を持つべき人が時間の経過とともに変化することが多いということです。プロジェクトがより成熟すると、通常、知る必要がないため、特定の資格情報を知る必要のない開発者がいます。さらに、コードはわずかに異なる目的で再利用されたり、オープンソースになることさえあります。また、コードベースがより複雑になるにつれて、コードの途中に埋め込まれているクレデンシャルを特定するのは非常に面倒になります。

何億人ものユーザーが、ハードコードされたクレデンシャルによって引き起こされる問題の影響をすでに受けていると言っても過言ではありません。ここにいくつかの例を含む記事があります

アプリにクレデンシャルを提供する方法

クレデンシャルがコードの一部ではない場合、これにより、アプリケーションにクレデンシャルを提供する方法が疑問視されます。これは、アプリケーションが実行されているプラ​​ットフォームによって異なります。たとえば、アプリケーションをクラウドサービスでホストする場合、このサービスには、保存された方法でクレデンシャルを保存し、それらをアプリケーションのオペレーティングシステム環境に挿入するメカニズムがあります。具体的な例を示すために、Herokuでホストされているアプリのクレデンシャルを提供する方法のドキュメントを次に示します。アプリケーションコードでは、環境からそれらにアクセスできます。たとえば、Javaの場合はgetenvを使用できます

String apiPassword = getenv("API_PASSWORD");

ここでAPI_PASSWORDは、アプリのホスティングメカニズムによって環境に提供する必要があります。

参考文献

私はこのトピックをより詳細にカバーするトピックについてのブログ記事を書きました:パスワードをソースコードから遠ざけてください-理由と方法

于 2021-07-15T15:23:18.227 に答える
0

なぜ人々はハッシュについて話しているのですか。OPは、外部リソースにアクセスするためにユーザーの資格情報を保存したいと考えています。彼のパスワードをハッシュしても役に立ちません。

今では邪魔になりません。すべてのレイヤーの簡単なベストプラクティスを紹介します。

1。Javaアプリにパスワードを保存します。:Char配列として保存します。パスワードストアクラスを作成し、アクセスするリソースとしてキーを使用してハッシュマップとしてパスワードを保存し、ユーザー名とパスワードを含むオブジェクトとして評価します。いくつかの認証を使用して、エントリポイントをこのAPIに制限します。例:ログインしたユーザーのクレデンシャルを受け入れて、そのリソースに対するそのユーザーのアクセスレベルを検証します(ユーザーをアクセス可能なパスワードのリストにマップするだけです。多くの場合、グループを作成しますそして、passwordmapキーをそのグループにマップします)これを超えてパスワードを保存するものは、jvm自体がそれをリークすることについてどれほどパラノイアであるかによって異なります。

  1. パスワードを送信するには、セキュリティで保護されたポートでパスワードを送信していることを確認してください(例:Httpsは良好、httpは不良)。本当に安全でないプロトコルを介して送信する必要がある場合は、暗号化してbase64と言うようにエンコードします。受信者がパスワードをデコードして復号化できることを確認します。
于 2020-11-27T11:55:51.450 に答える
-1

プログラムが実行されている環境を信頼できないが、プレーンなパスワードまたは証明書を使用して認証する必要がある場合、資格情報を保護するためにできることは何もありません。あなたができることのほとんどは、他の回答で説明されている方法でそれらを難読化することです。

回避策として、RESTful APIへのすべてのリクエストを、信頼できるプロキシを介して実行し、そこからクリアテキストのパスワード認証を実行します。

于 2012-10-26T15:46:24.917 に答える