Рубрики
Без рубрики

Кодирование пароля с защитой Spring

Автор оригинала: David Landup.

Вступление

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

Существует множество способов кодирования пароля – шифрование , хеширование , засолка , медленное хеширование

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

PasswordEncoder – это интерфейс Spring Security , который содержит очень гибкий механизм, когда дело доходит до хранения паролей.

Устаревшие Механизмы Безопасности

Буквальные значения

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

Быстро всплыли SQL-инъекции и Обфускации SQL , а также другие атаки. Эти виды атак основывались на том, что внешние пользователи получали права доступа к определенным таблицам базы данных.

Поскольку пароли хранились прямо в текстовом формате, этого было вполне достаточно, чтобы они могли получить все пароли и сразу же их использовать.

Шифрование

Шифрование является более безопасной альтернативой и первым шагом на пути к безопасности паролей. Шифрование пароля зависит от двух вещей:

  • Источник – Пароль, введенный при регистрации
  • Ключ – Случайный ключ, сгенерированный паролем

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

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

Перемешивание

Чтобы бороться с этими атаками, разработчикам пришлось придумать способ защиты паролей в базе данных таким образом, чтобы их невозможно было расшифровать.

Была разработана концепция одностороннего хеширования, и некоторыми из наиболее популярных функций хеширования в то время были MD5 , SHA1 , SHA-256 .

Однако эти стратегии не остались эффективными, так как злоумышленники начали хранить известные хэши с известными паролями и паролями, полученными в результате крупных утечек в социальных сетях.

Сохраненные пароли были сохранены в таблицах поиска, называемых радужными таблицами , и некоторые популярные из них содержали миллионы и миллионы паролей.

Самый популярный из них – RockYou.txt содержит более 14 миллионов паролей для более чем 30 миллионов учетных записей. Забавно, но почти 300 000 из них использовали пароль “123456”.

Это все еще популярный подход, и многие приложения по-прежнему просто хэшируют пароли, используя хорошо известные функции хэширования.

Засолка

Чтобы бороться с появлением радужных таблиц, разработчики начали добавлять случайную последовательность символов в начало хэшированных паролей.

Хотя это не полностью изменило игру, это, по крайней мере, замедлило атакующих, поскольку они не могли найти хэшированные версии паролей в общедоступных таблицах rainbow. Поэтому, если бы у вас был общий пароль, например “123456”, соль не позволила бы сразу идентифицировать ваш пароль, так как он был изменен перед хэшированием.

Медленное Перемешивание

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

Очень простое и простое решение этой проблемы заключается в реализации медленного хэширования – Алгоритмов, таких как BCrypt, Pbkdf2 , SCrypt и т. Д. Солируйте их хэшированные пароли и замедляйтесь после определенного количества итераций, что чрезвычайно затрудняет атаки с использованием грубой силы из-за количества времени, необходимого для вычисления одного хэша. Время, необходимое для вычисления хэша, может занять от нескольких миллисекунд до нескольких сотен миллисекунд, в зависимости от количества используемых итераций.

Кодировщики Паролей

Spring Security предоставляет несколько реализаций кодирования паролей на выбор. У каждого из них есть свои преимущества и недостатки, и разработчик может выбрать, какой из них использовать, в зависимости от требований аутентификации своего приложения.

BCryptPasswordEncoder

BCryptPasswordEncoder использует алгоритм BCrypt для хэширования паролей, который был описан ранее.

Параметр конструктора, за которым следует следить, – это сила . По умолчанию он установлен на 10, хотя может достигать 32 – чем больше сила , тем больше работы требуется для вычисления хэша. Эта “сила” на самом деле является количеством итераций (2 10 ) используется.

Другим необязательным аргументом является SecureRandom . SecureRandom – это объект, содержащий случайное число, которое используется для рандомизации сгенерированных хэшей:

// constructors
BCryptPasswordEncoder()
BCryptPasswordEncoder(int strength)
BCryptPasswordEncoder(int strength, java.security.SecureRandom random)

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); // Strength set as 12
String encodedPassword = encoder.encode("UserPassword");

Вот как выглядит хэшированный пароль:

$2a$12$DlfnjD4YgCNbDEtgd/ITeOj.jmUZpuz1i4gt51YzetW/iKY2O3bqa

Несколько вещей, которые следует иметь в виду в отношении хэшированного пароля, это:

Кодер Pbkdf2PasswordEncoder

Pbkdf2PasswordEncoder использует алгоритм PBKDF2 для хэширования паролей.

Он имеет три необязательных аргумента:

  • Секретный – Ключ, используемый в процессе кодирования. Как следует из названия, это должно быть секретно.
  • Итерация – Количество итераций, используемых для кодирования пароля, в документации указано, что для хеширования вашей системы требуется 0,5 секунды.
  • Хэш С – Размер самого хэша.

Секрет-это тип объекта java.lang.CharSequence и когда разработчик предоставляет его конструктору, закодированный пароль будет содержать секрет.

// constructors
Pbkdf2PasswordEncoder()
Pbkdf2PasswordEncoder(java.lang.CharSequence secret)
Pbkdf2PasswordEncoder(java.lang.CharSequence secret, int iterations, int hashWidth)

Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder("secret", 10000, 128);
String encodedPassword = encoder.encode("UserPassword");

Вот как выглядит хэшированный пароль:

zFRsnmzHKgWKWwgCBM2bfe0n8E9EZRsCtngcSBewray7VfaWjeYizhCxCkwBfjBMCGpY1aN0YdY7iBNmyiT+7bdfGfCeyUdGnTUVxV5doJ5UC6m6mj2n+60Bj8jGBMs2KIMB8c/zOZGLnlyvlCH39KB5xewQ22enLYXS5S8TlwQ=

Здесь важно отметить длину хэша, на которую мы можем напрямую влиять.

Мы можем определить короткий хэш (5):

zFRsnmw=

Или очень длинный (256):

zFRsnmzHKgWKWwgCBM2bfe0n8E9EZRsCtngcSBewray7VfaWjeYizhCxCkwBfjBMCGpY1aN0YdY7iBNmyiT+7bdfGfCeyUdGnTUVxV5doJ5UC6m6mj2n+60Bj8jGBMs2KIMB8c/zOZGLnlyvlCH39KB5xewQ22enLYXS5S8TlwQMmBkAFQwZtEdYpWySRTmUFJRkScXGev8TFkRAMNHoceRIf8eF/C9VFH0imkGuxA7r2tJlyo/n0vLNan6ZBngt76MzgF+S6SCNqGwUn5IWtfvkeL+Jyz761LI39sykhVGp4yTxLLRVmvKqqMLVOrOsbo9xAveUOkIzpivqBn1nQg==

Чем длиннее вывод, тем безопаснее пароль, верно?

Да, но, пожалуйста, имейте в виду – это безопаснее до определенного момента, после чего это просто становится излишеством. Обычно нет необходимости хэшировать дальше 2 128 поскольку это уже хэш, который практически невозможно взломать с помощью современных технологий и вычислительной мощности.

Шифратор паролей SCrypt

Шифратор паролей SCrypt использует алгоритм шифрования для хэширования паролей.

Выводом его конструктора является производный ключ, который на самом деле является ключом на основе пароля, используемым для хранения в базе данных. Вызов конструктора имеет необязательные аргументы:

  • Стоимость процессора – Стоимость процессора алгоритма, по умолчанию 2 14 – 16348. Это значение int должно быть в степени 2.
  • Стоимость памяти – По умолчанию составляет 8
  • Распараллеливание – Хотя формально присутствует, Скрипт не использует преимущества распараллеливания.
  • Длина ключа – Определяет длину выходного хэша, по умолчанию она равна 32.
  • Длина соли – Определяет длину соли, значение по умолчанию равно 64.

Пожалуйста, имейте в виду, что кодировщик паролей SCrypt редко используется в производстве. Отчасти это связано с тем, что изначально он не был предназначен для хранения паролей.

Хотя это спорно, дать “Почему я не рекомендую сценарий” чтение может помочь вам сделать выбор.

// constructors
SCryptPasswordEncoder()
SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength)

SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String encodedPassword = encoder.encode("UserPassword");

Вот как выглядит хэшированный пароль:

e8eeb74d78f59068a3f4671bbc601e50249aef05aae1254a2823a7979ba9fac0

Делегирование Кодировщика паролей

Кодировщик Делегирования пароля , предоставленный Spring, делегирует другой Кодировщик паролей с использованием идентификатора с префиксом.

В индустрии программного обеспечения многие приложения все еще используют старые кодеры паролей. Некоторые из них нелегко перенести на более новые кодеры и технологии, хотя с течением времени требуются новые технологии и подходы.

Реализация Делегирования кодировщика паролей решает многие проблемы, включая ту, которую мы обсуждали выше:

Git Essentials

Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!

  • Обеспечение кодирования паролей с использованием текущих рекомендаций по хранению паролей
  • Что позволит в будущем модернизировать кодеры
  • Простое создание экземпляра Делегирования кодировщика паролей использование Фабрики кодировщиков паролей
  • Возможность проверки паролей в современных и устаревших форматах
Map encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());

PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);
passwordEncoder.encode("UserPassword");

В вызове конструктора мы передаем два аргумента:

  • (Строка) “bcrypt” – Идентификатор кодировщика пароля в виде строки
  • (HashMap) кодеры – Карта, содержащая список кодеров

Каждая строка списка содержит префикс типа кодера в строковом формате и соответствующий ему кодер.

Вот как выглядит хэшированный пароль:

$2a$10$DJVGD80OGqjeE9VTDBm9T.hQ/wmH5k3LXezAt07EHLIW7H.VeiOny

Во время аутентификации предоставленный пользователем пароль, как обычно, сопоставляется с хэшем.

Демонстрационное приложение

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

Зависимости

Как и во всех проектах Spring и Spring Boot, давайте начнем с необходимых зависимостей:


    
        org.springframework.boot
        spring-boot-starter
    

    
        org.springframework.boot
        spring-boot-starter-test
        test
    

    
        org.springframework.security
        spring-security-core
        {version}
    

    
    
        org.bouncycastle
        bcprov-jdk15on
        {version}
        true
    

Позаботившись о наших зависимостях, давайте продолжим и протестируем наш кодировщик по выбору:

@SpringBootApplication
public class DemoApplication {

   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); // Strength set as 16
        String encodedPassword = encoder.encode("UserPassword");
        System.out.println("BCryptPasswordEncoder");
        System.out.println(encodedPassword);
        System.out.println("\n");
   }
}

Запуск этого фрагмента кода приведет к:

$2a$16$1QJLYU20KANp1Vzp665Oo.JYrz10oA0D69BOuckubMyPaUO3iZaZO

Он работает правильно с использованием BCrypt, пожалуйста, имейте в виду, что вы можете использовать любую другую реализацию кодера паролей здесь, все они импортированы в spring-security-core .

Конфигурация на основе XML

Одним из способов настройки приложения Spring Boot для использования кодировщика паролей при входе в систему является использование конфигурации на основе XML .

В файле .xml , в котором вы уже определили конфигурацию Spring Security , в теге <менеджер аутентификации> нам нужно будет определить другое свойство:

 
        
            
        
    

    
        
        
    

    
        
    

Конфигурация на основе Java

Мы также можем настроить кодировщик паролей в файле конфигурации на основе Java:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dataSource;

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth)
        throws Exception {

        auth.jdbcAuthentication().dataSource(dataSource)
            .passwordEncoder(passwordEncoder())
            .usersByUsernameQuery("{SQL}") //SQL query
            .authoritiesByUsernameQuery("{SQL}"); //SQL query
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder;
    }

Модель пользователя

После завершения всей настройки приложения мы можем продолжить и определить Пользователя модель:

@Entity
public class User {

    @Id
    @GeneratedValue
    private int userId;
    private String username;
    private String password;
    private boolean enabled;

    // default constructor, getters and setters
}

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

Уровень обслуживания

Весь уровень обслуживания обрабатывается UserDetailsManager для краткости и ясности. Для этой демонстрации нет необходимости определять пользовательский уровень обслуживания.

Это позволяет очень легко сохранять, обновлять и удалять пользователей для целей этой демонстрации, хотя я лично рекомендую определить уровень пользовательского сервиса в ваших приложениях.

Контроллер

Контроллер выполняет две задачи – позволяет пользователям регистрироваться и позволяет им впоследствии входить в систему:

@Controller
public class MainController {

    @Autowired
    private UserDetailsManager userDetailsManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/register")
    public String test(Model model) {
        User user = new User();
        model.addAttribute("user", user);
        return "register";
    }

    @RequestMapping(value = "register", method = RequestMethod.POST)
    public String testPost(@Valid @ModelAttribute("user") User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "register";
        }
        String hashedPassword = passwordEncoder.encode(user.getPassword());

        Collection roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));

        UserDetails userDetails = new User(user.getUsername(), hashedPassword, roles);
        userDetailsManager.createUser(userDetails);
        return "registerSuccess";

 @RequestMapping("/login")
    public String login(
            @RequestParam(value = "error", required = false) String error,
            @RequestParam(value = "logout", required = false) String logout, Model model) {
        if (error != null) {
            model.addAttribute("error", "Wrong username or password!");
        }

        if (logout != null) {
            model.addAttribute("msg", "You have successfully logged out.");
        }
        return "login";
    }
  }
}

После получения запроса POST мы получаем информацию Пользователя и хэшируем пароль с помощью нашего кодера.

После этого мы просто предоставляем полномочия нашему зарегистрированному пользователю и объединяем имя пользователя, хэшированный пароль и полномочия в один объект, используя UserDetails – Опять же, для краткости и простоты демонстрационного приложения.

Смотреть

Теперь, чтобы завершить все, нам нужно несколько простых представлений, чтобы сделать наше приложение функциональным:

  • индекс – Главная/индексная страница приложения
  • регистрация – Страница с регистрационной формой, которая принимает имя пользователя и пароль
  • регистрация прошла успешно – Дополнительная страница, на которой отображается сообщение об успешном завершении регистрации
  • войти – Страница, позволяющая зарегистрированным пользователям войти в систему
Индекс

    
        Home
    
    
        
            

Please login or register.

Welcome ${pageContext.request.userPrincipal.name}! | ">Logout

Зарегистрировать

    
        Title
    
    
        

Please fill in your credentials to register:

Username

Password

Примечание: В предыдущих версиях Spring обычной практикой было использовать имя команды вместо Атрибут модели , хотя в более новых версиях рекомендуется использовать новый подход.

Регистрация Прошла Успешно

    
        Title
    
    
        

You have registered successfully!

Авторизоваться


    Login

    
        

Log in using your credentials!

${msg}
" method="post"">
${error}

Примечание: j_spring_security_check был заменен на login , хотя большинство людей до сих пор не перешли на Spring Security 4, где он был представлен. Чтобы избежать путаницы, я включил старое ключевое слово, хотя оно не будет работать, если вы используете новую версию Spring Security.

Тестирование приложения

Давайте продолжим и запустим наше приложение, чтобы проверить, нормально ли оно работает.

Поскольку мы не вошли в систему, страница индекса просит нас либо зарегистрироваться, либо войти в систему:

При перенаправлении на страницу регистрации мы можем ввести нашу информацию:

Все прошло гладко, и нам будет предложено перейти на страницу успешной регистрации:

Сделав шаг назад, в базе данных мы можем заметить нового пользователя с хэшированным паролем:

Добавленный пользователь также имеет ROLE_USER , как определено в контроллере:

Теперь мы можем вернуться в приложение и попытаться войти в систему:

После ввода правильных учетных данных нас снова встречает наша страница индекса, но на этот раз с другим сообщением:

Вывод

Реализации популярных алгоритмов хеширования Spring Security работают как заклинание, при условии, что пользователь не выбирает действительно плохой пароль. Мы обсудили необходимость кодирования паролей, некоторые устаревшие подходы к защите паролей от потенциальных злоумышленников и реализации, которые мы можем использовать для этого в более безопасном и современном подходе.

В конце концов, мы создали демонстрационное приложение, чтобы показать BCryptPasswordEncoder в использовании.