構成ファイルのパスワードを暗号化しますか?[閉まっている]


130

構成ファイルからサーバー情報を読み取るプログラムがあり、その構成でパスワードを暗号化して、自分のプログラムで読み取って復号化したいと考えています。

要件:

  • ファイルに保存される平文パスワードを暗号化する
  • プログラムからファイルから読み込まれた暗号化されたパスワードを解読する

これを行う方法についての推奨事項はありますか?私は自分のアルゴリズムを書くことを考えていましたが、それはひどく危険だと思います。

回答:


172

これを行う簡単な方法は、Javaでパスワードベースの暗号化を使用することです。これにより、パスワードを使用してテキストを暗号化および復号化できます。

これは基本的に初期化を意味javax.crypto.Cipherするアルゴリズムと"AES/CBC/PKCS5Padding"してからキーを取得javax.crypto.SecretKeyFactoryして"PBKDF2WithHmacSHA512"アルゴリズム。

以下はコード例です(安全性の低いMD5ベースのバリアントを置き換えるために更新されています)。

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class ProtectedConfigFile {

    public static void main(String[] args) throws Exception {
        String password = System.getProperty("password");
        if (password == null) {
            throw new IllegalArgumentException("Run with -Dpassword=<password>");
        }

        // The salt (probably) can be stored along with the encrypted data
        byte[] salt = new String("12345678").getBytes();

        // Decreasing this speeds down startup time and can be useful during testing, but it also makes it easier for brute force attackers
        int iterationCount = 40000;
        // Other values give me java.security.InvalidKeyException: Illegal key size or default parameters
        int keyLength = 128;
        SecretKeySpec key = createSecretKey(password.toCharArray(),
                salt, iterationCount, keyLength);

        String originalPassword = "secret";
        System.out.println("Original password: " + originalPassword);
        String encryptedPassword = encrypt(originalPassword, key);
        System.out.println("Encrypted password: " + encryptedPassword);
        String decryptedPassword = decrypt(encryptedPassword, key);
        System.out.println("Decrypted password: " + decryptedPassword);
    }

    private static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
        SecretKey keyTmp = keyFactory.generateSecret(keySpec);
        return new SecretKeySpec(keyTmp.getEncoded(), "AES");
    }

    private static String encrypt(String property, SecretKeySpec key) throws GeneralSecurityException, UnsupportedEncodingException {
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key);
        AlgorithmParameters parameters = pbeCipher.getParameters();
        IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
        byte[] cryptoText = pbeCipher.doFinal(property.getBytes("UTF-8"));
        byte[] iv = ivParameterSpec.getIV();
        return base64Encode(iv) + ":" + base64Encode(cryptoText);
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    private static String decrypt(String string, SecretKeySpec key) throws GeneralSecurityException, IOException {
        String iv = string.split(":")[0];
        String property = string.split(":")[1];
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
    }

    private static byte[] base64Decode(String property) throws IOException {
        return Base64.getDecoder().decode(property);
    }
}

問題が1つ残っています。パスワードの暗号化に使用するパスワードをどこに保存すればよいですか。ソースファイルに保存して難読化することはできますが、再び見つけるのはそれほど難しくありません。または、Javaプロセスを開始するときに、システムプロパティとして指定することもできます(-DpropertyProtectionPassword=...)。

パスワードで保護されているKeyStoreを使用する場合も、同じ問題が残ります。基本的には、どこかに1つのマスターパスワードが必要であり、保護するのはかなり困難です。


3
コードの例をありがとう、それは私がそれをやったことにかなり似ています。同じ問題に遭遇したパスワードを保護するパスワードに関して、私は今のところそれを難読化する方法はありませんでしたが、あなたの提案に感謝して、まだ受け入れ可能な解決策を考え出していません。
Petey B

7
「または、Javaプロセスを開始するときにシステムプロパティとして指定することもできます(-DpropertyProtectionPassword = ...)」。これにより、(GNU / Linux)/ UNIXで「ps fax」を使用してパスワードを抽出できるようになることに注意してください。
Ztyx 2013

7
@Benテキストファイルまたは文字列ベースのデータベース列などに結果の値を格納できるように、Base64にエンコードするのが一般的です。
RB。

4
@ V.7いいえ。MD5はパスワードハッシュに対して完全に安全ではなく、その目的で設計されたことはありません。そのために使用しないでください。最近では、Argon2が最適です。owasp.org/index.php/Password_Storage_Cheat_Sheetおよびparagonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson

3
この方法の方がはるかに優れています。もちろん、安全なランダムソルトと(保守的、ローエンド)40Kの反復カウントはより優れていますが、少なくともコメントでこれらのことを示し、PBKDF2とAES / CBCは明確な改善です。答えを更新して、これをどのように処理したかは素晴らしいと思います。警告を削除します。更新されたコードを見つけても驚かないように、コメントに投票しました(編集内容を見て、古いコードを見つけられると思います)。古いコメントもクリーンアップすることをお勧めします。
Maarten Bodewes 2017

20

はい、絶対に独自のアルゴリズムを記述しないでください。Javaにはたくさんの暗号化APIがあります。

インストールするOSにキーストアがある場合、それを使用して、構成やその他のファイルの機密データを暗号化および復号化するために必要な暗号キーを保存できます。


4
キーストアを使用するための+1!キーをJarファイルに格納するのは、難読化にすぎません。
2009

2
必要なのがパスワードをクリアテキストで保存することだけではない場合、キーストアは過剰です。
するThorbjörnRavnアンデルセン

20

最小限の労力で基本的な暗号化機能を提供するライブラリーであるjasyptをチェックしてください。


16

最良の方法は、(パスワードを含む)構成ファイルに特定のユーザーアカウントのみがアクセスできるようにすることです。たとえば、appuser信頼できる人だけがパスワードを持っている(そしてその人が持っている)アプリケーション固有のユーザーがいるとしますsu

そうすれば、煩わしい暗号化のオーバーヘッドがなくなり、安全なパスワードを手に入れることができます。

編集: 私はあなたが信頼された環境の外であなたのアプリケーション設定をエクスポートしていないと仮定しています(質問を考えると、それが意味をなさないかわかりません)


4

マスターパスワードの問題を解決するには-最善の方法はパスワードをどこにも保存しないことです。アプリケーションはそれ自体のパスワードを暗号化し、それだけがパスワードを復号化できるようにする必要があります。したがって、.configファイルを使用していた場合、mySettings.configを実行します。

encryptTheseKeys = secretKey、anotherSecret

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

したがって、encryptTheseKeysに記載されているキーを読み取り、上からBrodwallsの例を適用して、何らかのマーカー(crypt:と言う)を付けてファイルに書き戻し、アプリケーションにそれを行わないように通知します。この場合も、出力は次のようになります。

encryptTheseKeys = secretKey、anotherSecret

secretKey = crypt:ii4jfj304fjhfj934fouh938

anotherSecret = crypt:jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

オリジナルを安全な場所に保管してください...


2
ええ、これは3年前のものです。マスターキーを回避するために、私は内部CAから発行されたRSAキーを使用することになりました。秘密鍵へのアクセスは、マシンのハードウェアの指紋で暗号化されることによって保護されます。
Petey B 2012

確かに、かなり堅実に聞こえます。いいね。
user1007231 2012年

@ user1007231-保管場所-「オリジナルを安全な場所に保管してください...」?
nanosoft 2015年

@PeteyB-理解できませんでしたか?私を啓発することができるいくつかのリンクを私に示すことができますか?ありがとう
nanosoft 2015年

@nanosoft-「Aegis Secure Key USB」を入手し、テキストドキュメントまたはウォレットの紙に
保存

4

重要な点、そして部屋の中の象など、アプリケーションがパスワードを入手できる場合、ボックスにアクセスできるハッカーもパスワードを入手できるということです。

これをいくらか回避する唯一の方法は、アプリケーションが標準入力を使用してコンソールで「マスターパスワード」を要求し、これを使用してファイルに保存されているパスワードを復号化することです。もちろん、これにより、起動時にOSと共にアプリケーションを無人で起動することは完全に不可能になります。

ただし、このレベルの煩わしさがあっても、ハッカーがrootアクセス権(またはアプリケーションを実行しているユーザーとしてアクセス権)を取得できた場合、メモリをダンプしてパスワードを見つけることができます。

確認すべきことは、会社全体が本番サーバー(およびパスワード)にアクセスできないようにし、このボックスをクラックできないようにすることです。


実際の解決策は、カードやHSMなどの別の場所に秘密鍵を保存することです:en.wikipedia.org/wiki/Hardware_security_module
atom88



0

構成ファイルがどれほど安全であるか、またはアプリケーションの信頼性に応じて、http://activemq.apache.org/encrypted-passwords.htmlが適切なソリューションになる場合があります。

解読されるパスワードをそれほど恐れていない場合、Beanを使用してパスワードキーを格納するように構成するのは非常に簡単です。ただし、さらにセキュリティが必要な場合は、シークレットを使用して環境変数を設定し、起動後にそれを削除できます。これにより、アプリケーション/サーバーがダウンすることを心配し、アプリケーションが自動的に再起動しないことを心配する必要があります。


HSMの使用が理想的な方法です:en.wikipedia.org/wiki/Hardware_security_module
atom88

-8

Java 8を使用している場合、内部Base64エンコーダーおよびデコーダーの使用は、

return new BASE64Encoder().encode(bytes);

return Base64.getEncoder().encodeToString(bytes);

そして

return new BASE64Decoder().decodeBuffer(property);

return Base64.getDecoder().decode(property);

復号化のメソッドは同じ場所に保存されるため、このソリューションではデータが保護されないことに注意してください。壊れるのを難しくするだけです。主にそれを印刷して、誤ってみんなに見せることを避けます。


26
Base64は暗号化されていません。
jwilleke 2015年

1
Base64は暗号化ではなく、提供できる最悪の例です...多くの人々は、base64は暗号化アルゴリズムであると信じているので、混乱させない方がいいです...
robob

ソースファイルの先頭にあるdecode()メソッドが実際の暗号化を行うことに注意してください。ただし、このbase64エンコードとデコードは、文字列からバイトを変換して、この関数が使用できるもの(バイト配列byte [])を渡すために必要です
atom88
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.