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


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.