[Spring Security 6] Password Storage

짱발자·2023년 1월 30일

Spring Security

목록 보기
1/1
post-thumbnail

비밀번호 저장의 역사

  1. 처음에는 그냥 plain text로 저장했었다. 비밀번호가 데이터 저장소에 액세스하는 데 필요한 자격 증명에 저장되었기 때문에 안전한 것으로 여겨졌었다.

    → 그러나 악의적인 사용자는 SQL 주입과 같은 공격을 사용하여 사용자 이름과 암호의 대규모 "데이터 덤프"를 얻을 수 있는 방법을 찾을 수 있었다. 점점 더 많은 사용자 자격 증명이 공개됨에 따라 보안 전문가들은 사용자의 암호를 보호하기 위해 더 많은 조치를 취해야 한다는 것을 깨달았다.

  2. 그래서 개발자는 SHA-256과 같은 단방향 해시를 통해 암호를 실행한 후 암호를 저장하도록 권장되었다. 사용자가 인증을 시도하면 해시된 비밀번호가 입력한 비밀번호의 해시와 비교된다. 즉, 시스템은 암호의 단방향 해시만 저장하면 된다. 위반이 발생하면 암호의 단방향 해시만 노출되었다. 해시는 단방향이고 해시가 주어진 암호를 추측하는 것은 계산상 어렵기 때문에 시스템에서 각 암호를 알아내려는 노력은 가치가 없다.

    → 이 새로운 시스템을 무력화하기 위해 악의적인 사용자는 Rainbow Tables로 알려진 조회 테이블을 만들었다. 매번 암호를 추측하는 작업을 하는 대신 암호를 한 번 계산하여 조회 테이블에 저장했다.

  3. Rainbow Tables를 무력화하기 위해 개발자는 salted 암호를 사용하도록 권장되었다. 해시 함수에 대한 입력으로 암호만 사용하는 대신, 모든 사용자의 암호에 대해 임의 바이트(salt라고 함)가 생성된다. 소금과 사용자의 암호는 해시 함수를 통해 고유한 해시로 만들어진다. 솔트는 일반 텍스트로 사용자 비밀번호와 함께 저장된다. 그런 다음 사용자가 인증을 시도하면 해시된 비밀번호가 저장된 솔트의 해시 및 사용자가 입력한 비밀번호와 비교된다. 고유한 솔트는 모든 솔트와 암호 조합에 대해 해시가 다르기 때문에 Rainbow Tables가 더 이상 효과적이지 않게 됐다.

    → 현대에는 암호화 해시(예: SHA-256)가 더 이상 안전하지 않다는 것을 알고 있다. 최신 하드웨어를 사용하면 초당 수십억 개의 해시 계산을 수행할 수 있기 때문이다. 즉, 각 비밀번호를 개별적으로 쉽게 해독할 수 있다.

  4. 이제 개발자는 적응형 단방향 함수를 활용하여 암호를 저장하도록 권장됩니다. 적응형 단방향 함수를 사용한 암호 유효성 검사는 의도적으로 리소스를 많이 사용합니다(의도적으로 CPU, 메모리 또는 기타 리소스를 많이 사용함). 적응형 단방향 함수를 통해 하드웨어가 향상됨에 따라 증가할 수 있는 "work factor"(공격자가 암호를 깨는데 드는 노력)를 구성할 수 있습니다. 시스템에서 암호를 확인하는 데 약 1초가 걸리도록 "work factor"를 조정하는 것이 좋습니다. 이 트레이드 오프는 공격자가 암호를 해독하기 어렵게 만들지만 비용이 많이 들기 때문에 시스템에 과도한 부담을 주거나 사용자를 짜증나게 하지 않습니다. Spring Security는 "work factor"에 대한 좋은 시작점을 제공하려고 시도했지만 시스템마다 성능이 크게 다르기 때문에 사용자가 자신의 시스템에 대해 "work factor"를 사용자 지정하도록 권장합니다. 사용해야 하는 적응형 단방향 함수의 예로는 bcrypt, PBKDF2, scryptargon2가 있습니다.

    적응형 단방향 함수는 의도적으로 리소스를 많이 사용하므로 모든 요청에 대해 사용자 이름과 암호를 확인하면 애플리케이션의 성능이 크게 저하될 수 있습니다. Spring Security(또는 다른 라이브러리)가 암호 유효성 검사 속도를 높이기 위해 할 수 있는 것은 아무것도 없습니다. 보안은 유효성 검사 리소스를 집약적으로 만들어서 얻을 수 있기 때문입니다. 사용자는 장기 자격 증명(즉, 사용자 이름 및 암호)을 단기 자격 증명(예: 세션 및 OAuth 토큰 등)으로 교환하는 것이 좋습니다. 단기 자격 증명은 보안 손실 없이 신속하게 유효성을 검사할 수 있습니다.




DelegatingPasswordEncoder

  • Spring Security 5.0 이전 : 기본 PasswordEncoder가 일반 텍스트 암호가 필요한 NoOpPasswordEncoder였다.
  • Spring Security 5.0 이후 : 기본 PasswordEncoderBCryptPasswordEncoder 이다.

✅ 그러나 세 가지 문제가 있음.

  1. 많은 애플리케이션이 쉽게 마이그레이션할 수 없는 이전 암호 인코딩을 사용
  2. 비밀번호 저장 방법에 대한 모범 사례가 다시 변경된다.
  3. 프레임워크로서 스프링 시큐리티는 빈번하게 breaking change를 만들 수 없다.

✅ 그래서, Spring Security는 문제를 해결하기 위해 DelegatingPasswordEncoder 도입.

  1. 현재 암호 저장 권장 사항을 사용하여 암호가 인코딩되었는지 확인
  2. 최신 및 레거시 형식의 암호 유효성 검사 허용
  3. 향후 인코딩 업그레이드 허용

  • 코드
    PasswordEncoder passwordEncoder =
        PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String idForEncode = "bcrypt";
    Map encoders = new HashMap<>();
    encoders.put(idForEncode, new BCryptPasswordEncoder());
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
    encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
    encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
    encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
    encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
    encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
    encoders.put("sha256", new StandardPasswordEncoder());
    
    PasswordEncoder passwordEncoder =
        new DelegatingPasswordEncoder(idForEncode, encoders);




비밀번호 저장 포맷

{id}encodedPassword
  1. id : 어떤 PasswordEncoder를 사용해야 하는지 조회하는 데 사용되는 식별자
    • 비밀번호의 시작 부분에 있어야 한다.
    • {로 시작하고 }로 끝나야 한다.
    • id를 찾을 수 없으면 id는 null로 설정된다.
  2. encodingPassword : 선택한 PasswordEncoder의 원래 인코딩된 비밀번호입니다.

  • 예시
    {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG //1
    {noop}password  //2
    {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc //3
    {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  //4
    {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 //5
    1. 일치하는 경우 BCryptPasswordEncoder에 위임
      • id : bcrypt
      • encodingPassword : $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
    2. 일치하는 경우 NoOpPasswordEncoder에 위임
      • id : noop
      • encodingPassword : password
    3. 일치하는 경우 Pbkdf2PasswordEncoder에 위임
      • id : pbkdf2
      • encodingPassword : 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
    4. 일치하는 경우 SCryptPasswordEncoder에 위임
      • id : scrypt
      • encodingPassword : $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
    5. 일치하는 경우 StandardPasswordEncoder에 위임
      • id : sha256
      • encodingPassword : 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

💡 일부 사용자는 잠재적인 해커를 위해 스토리지 형식이 제공되는 것을 우려할 수 있습니다. 하지만 비밀번호 저장은 비밀인 알고리즘에 의존하지 않기 때문에 이는 문제가 되지 않습니다. 또한 대부분의 형식은 공격자가 접두사 없이 쉽게 알아낼 수 있습니다. 예를 들어 BCrypt 암호는 종종 $2a$로 시작합니다.

출처
Spring Security>features>authentication>password-storage 공식 Doc

profile
안 되는 게 어딨어🎵 전부 해보면 되지🎵 디버깅 되고 되고 되고🎵

0개의 댓글