Зашифровать пароль в файлах конфигурации? [закрыто]


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);
    }
}

Остается одна проблема: где хранить пароль, который вы используете для шифрования паролей? Вы можете сохранить его в исходном файле и затемнить, но найти его снова не так уж и сложно. В качестве альтернативы вы можете указать его как системное свойство при запуске процесса Java ( -DpropertyProtectionPassword=...).

Та же проблема остается, если вы используете KeyStore, который также защищен паролем. По сути, вам понадобится где-то один мастер-пароль, и его довольно сложно защитить.


3
Спасибо за пример кода, так я и закончил. Что касается пароля, который защищает пароли, с которыми я столкнулся с той же проблемой, я пока использовал метод обфускации, но пока не нашел приемлемого решения, спасибо за ваши предложения.
— Petey B

7
«Кроме того, вы можете указать его как системное свойство при запуске процесса Java (-DpropertyProtectionPassword = ...)». Обратите внимание, что это позволит извлечь пароль с помощью «ps fax» в (GNU / Linux) / UNIX.
— Ztyx

7
@Ben Обычной практикой является кодирование в Base64, чтобы вы могли сохранить результирующее значение в текстовом файле или столбце базы данных на основе строк или аналогичном.
— РБ.

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

20

Да, определенно не пишите свой собственный алгоритм. В Java есть множество криптографических API.

Если ОС, на которую вы устанавливаете, имеет хранилище ключей, вы можете использовать его для хранения своих криптографических ключей, которые вам понадобятся для шифрования и дешифрования конфиденциальных данных в вашей конфигурации или других файлах.


4
+1 за использование KeyStore! Это не что иное, как обфускация, если вы храните ключ в файле Jar.
— ninesided

2
Если все, что нужно, - это не хранить пароль в виде открытого текста, то хранилища ключей будут излишними.
— Thorbjørn Ravn Andersen

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 = крипта: jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

Просто убедитесь, что храните оригиналы в надежном месте ...


2
Да, это было 3 года назад. Чтобы избежать использования главного ключа, я использовал ключи RSA, выданные нашим внутренним центром сертификации. Доступ к секретному ключу защищен путем шифрования с помощью отпечатка пальца аппаратного обеспечения машины.
— Petey B

Понятно, звучит довольно солидно. отлично.
— user1007231

@ user1007231 - Где хранить - «Только храните оригиналы в своем безопасном месте ...»?
— nanosoft

@PeteyB - Не понял? Не могли бы вы указать мне на некоторые ссылки, которые могут меня просветить. Спасибо
— nanosoft

@nanosoft - Получите «Aegis Secure Key USB» и храните в текстовом документе или на бумаге в своем кошельке
— user1007231

4

Главный момент, слон в комнате и все такое, в том, что если ваше приложение может получить пароль, то хакер, имеющий доступ к ящику, тоже сможет его получить!

Единственный способ обойти это - это то, что приложение запрашивает «главный пароль» на консоли с помощью стандартного ввода, а затем использует его для расшифровки паролей, хранящихся в файле. Конечно, это полностью делает невозможным запуск приложения без присмотра вместе с ОС при загрузке.

Однако даже при таком уровне раздражения, если хакеру удастся получить root-доступ (или даже просто доступ как пользователь, запускающий ваше приложение), он может сбросить память и найти там пароль.

Необходимо обеспечить, чтобы вся компания не имела доступа к производственному серверу (и, следовательно, к паролям), и убедитесь, что этот ящик невозможно взломать!


Реальное решение - хранить свой закрытый ключ где-нибудь еще, например, на карте или HSM: en.wikipedia.org/wiki/Hardware_security_module
— atom88 05

1

Попробуйте использовать методы шифрования ESAPI. Его легко настроить, и вы также можете легко изменить свои ключи.

http://owasp-esapi-java.googlecode.com/svn/trunk_doc/latest/org/owasp/esapi/Encryptor.html

Вы

1) зашифровать 2) расшифровать 3) подписать 4) без подписи 5) хешировать 6) подписи на основе времени и многое другое с помощью всего одной библиотеки.


1

Посмотрите, что доступно в Jetty для хранения пароля (или хэшей) в файлах конфигурации, и подумайте, может ли кодировка OBF быть полезной для вас. Тогда посмотрите в источнике, как это делается.

http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html


0

В зависимости от того, насколько безопасны вам файлы конфигурации или насколько надежно ваше приложение, http://activemq.apache.org/encrypted-passwords.html может быть для вас хорошим решением.

Если вы не слишком боитесь расшифровки пароля, и это может быть очень просто настроить с помощью bean-компонента для хранения ключа пароля. Однако, если вам нужна дополнительная безопасность, вы можете установить переменную среды с секретом и удалить ее после запуска. При этом вам нужно беспокоиться о том, что приложение / сервер выйдет из строя, а не о том, что приложение не перезапустится автоматически.


использование HSM - идеальный способ: en.wikipedia.org/wiki/Hardware_security_module
— atom88 05

-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

1
Base64 - это не шифрование, и это худший пример, который вы можете предоставить ... многие люди считают, что base64 - это алгоритм шифрования, поэтому лучше не путать их ...
— Робоб

обратите внимание, что метод decode () в верхней части исходного файла выполняет фактическое шифрование. Однако это кодирование и декодирование base64 необходимо для преобразования из строки байтов, чтобы передать этой функции то, что она может использовать (байтовый массив byte [])
— atom88 05
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.