모든 글
약 13분 분량 성능·용량

비밀번호 해시를 Argon2id로 바꿨더니 로그인 메모리가 문제가 됐다

목차

이 글의 대상은 인증되지 않은 사용자가 호출하는 /auth/login입니다. 서버는 이 경로에서 저장된 비밀번호 해시와 입력값을 비교하고, 로그인 이후의 API 요청에서는 비밀번호를 다시 계산하지 않습니다.

비밀번호 검증 알고리즘을 BCrypt에서 Argon2id로 교체했습니다. OWASP는 신규 시스템에 Argon2id를 우선 권고하지만, 알고리즘 이름만 바꾼다고 전환이 끝나지는 않습니다. 로그인 한 건의 CPU·메모리 비용, 동시에 실행할 해시 수, 기존 BCrypt 계정의 이관을 함께 정해야 합니다.

교체 후 부하를 걸자 예상하지 못한 문제가 나타났습니다. Argon2id는 비밀번호 1건을 검증할 때 약 19MiB를 사용합니다. 로그인 1건은 문제가 없었지만 동시 100건이면 해싱에만 약 2GB가 필요했고 512MB 힙에서는 동시 로그인 50건이 전부 OOM으로 종료됐습니다. 보안 강도를 올린 변경이 인증 없이 호출할 수 있는 /auth/login의 DoS 표면을 오히려 넓혔습니다.

결론부터

Argon2id 전환에서 가장 큰 문제는 해시 한 건의 속도가 아니라 동시 실행 수였습니다.

  1. 해시 한 건의 메모리 비용이 동시 로그인 수만큼 곱해졌습니다. 512MB 힙에서는 동시 로그인 50건이 OOM으로 끝났습니다.
  2. RPS 제한만으로는 부족했습니다. RPS는 일정 시간 동안 시작되는 요청 수를 제한하지만, OOM을 만드는 것은 같은 순간에 실행 중인 해시 수입니다.
  3. 동시 해시 수를 8로 제한하고 초과분은 즉시 429로 거절했습니다. 기존 BCrypt 사용자는 로그인 시점에 점진적으로 이관했고, 해시 컬럼 길이 문제는 별도로 수정했습니다.

알고리즘 교체는 PasswordEncoder 구현 하나를 바꾸는 것으로 끝나지 않았습니다.

1. 로그인 해시를 Argon2id로 전환

BCrypt를 유지하거나 scrypt·Argon2id 중 하나로 전환할 수 있었습니다. 둘 다 메모리 하드 비밀번호 해시 알고리즘이므로 scrypt를 사용해도 보안상 문제는 없습니다. 이번에는 신규 구현에서 우선 검토되는 Argon2id를 선택했고, 권고값을 그대로 적용하는 대신 우리 서비스의 메모리·동시성 예산 안에서 동작하는지 직접 측정했습니다.

bcrypt 항목의 설명은 “레거시 시스템을 위해”입니다. 안전하지 않다는 의미는 아니지만 신규 시스템에 권하는 표현도 아닙니다. BCrypt의 약점은 메모리 하드 특성이 약하다는 점입니다. 사용 메모리가 4KB 수준이어서 공격자가 GPU나 전용 하드웨어로 병렬화할 때 메모리가 병목이 되지 않습니다. Argon2id는 파라미터로 메모리 비용을 강제해 이 이점을 줄입니다.

두 알고리즘의 차이를 정리하면 이렇습니다.

관점Argon2idbcrypt
공격 방어CPU와 메모리 비용을 함께 부과합니다주로 CPU 비용을 부과합니다
서버 자원메모리를 많이 사용합니다메모리 부담이 작습니다
동시 로그인 부하실행 중인 해시 수를 함께 봐야 합니다상대적으로 다루기 쉽습니다
설정memory, time, parallelismcost 하나입니다
생태계·호환성비교적 현대적인 선택지입니다매우 널리 지원됩니다
오래된 시스템 연동불편할 수 있습니다유리합니다

비교해야 할 대상은 보안과 서버 자원·동시 처리량입니다. 설계 질문도 “Argon2id를 쓸까”에서 끝나지 않습니다.

공격자의 오프라인 크래킹 비용을 충분히 높이면서 정상 로그인 트래픽에서 서버가 감당할 수 있는 memory × iterations × concurrency 지점을 어디로 잡을 것인가?

이 질문에 답하려면 두 계열을 같은 조건에서 재야 합니다. 뒤에서 그 측정을 다룹니다.

OWASP Password Storage Cheat Sheet도 같은 결론을 제시합니다. 이상적인 work factor에 “황금률이 없다”고 적고, “서버 성능과 사용자 수에 달렸다. 해당 서버에서 실험해서 정해야 한다. 일반적으로 해시 1회는 1초 미만이어야 한다”고 설명합니다. work factor가 너무 높으면 애플리케이션 성능이 떨어지고, 공격자가 로그인을 대량으로 시도해 서버 자원을 소진시킬 수 있다는 경고도 덧붙입니다.

Argon2id는 그 실험의 출발점으로 동등한 방어 수준의 조합을 제시합니다. 차이는 CPU와 RAM 사용의 트레이드오프뿐입니다.

메모리iterationsparallelism
46MiB11
19MiB21
12MiB31
9MiB41
7MiB51

파라미터를 무작정 키우라는 지침이 아닙니다. 위로 갈수록 RAM을 더 쓰고 아래로 갈수록 CPU를 더 씁니다.

바로 전환할 수는 없었습니다. Argon2PasswordEncoder는 BouncyCastle을 요구하는데 이 시스템에는 해당 의존이 없었고, 저장된 $2a$10$... 해시의 알고리즘을 코드가 식별할 방법도 없었습니다. 이 상태에서 알고리즘을 바꾸면 모든 사용자에게 비밀번호 재설정을 요구해야 합니다. 우선 알고리즘은 유지하고 접두사만 부여했습니다.

DelegatingPasswordEncoder delegating = new DelegatingPasswordEncoder(
"bcrypt", Map.of("bcrypt", new BCryptPasswordEncoder()));
delegating.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());

새로 생성하는 해시에는 {bcrypt}가 붙고, 이미 저장된 접두사 없는 해시는 setDefaultPasswordEncoderForMatches가 계속 검증합니다. 강도는 상승하지 않습니다. 이 단계는 개선이 아니라 준비입니다. 실제 전환은 맵에 항목을 추가하고 기본 id를 바꿔서 마무리했습니다.

String encodingId = "argon2";
PasswordEncoder argon2 = new Argon2PasswordEncoder(16, 32, 1, 19456, 2);
DelegatingPasswordEncoder delegating = new DelegatingPasswordEncoder(
encodingId, Map.of(encodingId, argon2, "bcrypt", bcrypt));
delegating.setDefaultPasswordEncoderForMatches(bcrypt);

파라미터는 OWASP 최소 권고인 19MiB, iterations 2, parallelism 1로 설정했습니다. Spring의 defaultsForSpringSecurity_v5_8()은 메모리가 16MiB로 권고에 미달해 직접 구성했습니다. 동일한 전환을 다룬 토이토 님의 「아직도 Bcrypt만 쓰시나요? 이제는 Argon2id로 전환해야 할 때」는 new Argon2PasswordEncoder(16, 32, 1, 65536, 3), 즉 메모리 64MiB에 iterations 3을 사용합니다. OWASP 최소 권고의 세 배가 넘습니다.

적절한 설정은 한 대에서 동시에 몇 명이 로그인하는지에 따라 달라집니다. 이 메모리는 해시 1건당 값이므로 동시 요청 수만큼 곱해집니다. 19MiB 설정에서 동시 100건이면 Argon2 몫만 약 2GB임을 실측했습니다. 같은 비례라면 64MiB 설정은 같은 조건에서 6GB를 넘습니다. 최소 권고에서 시작하고 힙 여유를 측정한 뒤 상향하는 절차를 택했습니다.

이제 세 세대의 해시를 모두 검증합니다.

저장된 형태검증 주체
{argon2}$argon2id$...맵의 argon2 (신규 가입)
{bcrypt}$2a$...맵의 bcrypt (이관 중간 세대)
$2a$...setDefaultPasswordEncoderForMatches (가장 오래된 것)

기존 회원의 비밀번호 재설정은 필요하지 않습니다. 이 조건은 테스트로 고정했습니다. 하나라도 깨지면 해당 세대의 사용자 전원이 로그인하지 못합니다.

비용을 측정한 결과는 예상과 달랐습니다. 비밀번호 해싱은 느린 것이 목적이므로 빨라진 것을 개선으로 볼 수는 없습니다. 그래도 이번 교체가 /auth/login에 주는 부하는 확인해야 했습니다. 로컬(Apple Silicon), 워밍업 후 7회 중앙값입니다.

알고리즘소요메모리
BCrypt (strength 10)87ms~4KB
Argon2id (19MiB, t=2, p=1)32ms19MiB

강한 쪽이 더 빨랐습니다. 처음에는 파라미터 입력 오류를 의심했습니다. 두 알고리즘의 비용이 발생하는 방식이 다르기 때문입니다. BCrypt는 반복 횟수에서, Argon2id는 상당 부분을 메모리에서 가져옵니다. 이 결과로 말할 수 있는 범위는 단일 머신에서 Argon2id 최소 설정이 BCrypt cost 10보다 빨랐다는 데까지입니다.

해시 비용 비교: BCrypt는 87ms에 4KB, Argon2id(19MiB·t=2)는 32ms에 19MiB. 메모리는 해시 1건당이라 동시 요청 수만큼 곱해진다

2. 전환 비용: 동시 로그인 100건에 약 2GB

시간은 줄었지만 비용은 남았습니다. 메모리라는 비용이 새로 생겼고, 인증 없이 호출할 수 있는 경로에서 발생합니다. 이것이 이번 교체의 실질적인 대가입니다. 메모리 하드는 공격자에게만 메모리를 요구하지 않습니다.

힙 상한(512MB·1GB·2GB) × 동시 로그인(25·50·100) 격자에 OOM 실패 건수를 음영으로 표시한 히트맵. 512MB·동시 50에서 50건 전부, 512MB·동시 100에서 75건, 1GB·동시 100에서 12건이 OOM으로 끝나고 2GB는 모두 성공했다

로그인 1건 19MiB
동시 로그인 100건 약 2GB

/auth/login은 인증되지 않은 사용자가 호출하는 로그인 입구이므로 인증 없이 열려 있습니다. 비밀번호가 틀려도 입력한 비밀번호와 저장된 해시를 비교해야 하므로 Argon2id 검증은 수행됩니다. 공격자는 실제 계정을 알지 않아도 이 비용을 발생시킬 수 있습니다.

알고리즘 강도는 올라갔지만 해당 경로의 DoS 표면은 넓어졌습니다.

동시 접속이 늘면 CPU보다 메모리가 먼저 한계에 도달합니다.

19MiB는 해시 1건당 값이므로 동시 로그인 수만큼 곱해집니다. 힙 상한만 바꿔 같은 스크립트를 반복했습니다.

각 조건 1회 실행이며 동시 시작을 배리어로 맞췄습니다. “실패”는 JVM 종료가 아니라 해당 요청이 OutOfMemoryError로 끝난 건수입니다. 실행마다 스케줄링이 달라 실패 건수는 변동합니다.

힙동시 25동시 50동시 100
512MB성공 (696ms)50건 전부 OOM75건 실패
1GB성공성공12건 실패
2GB성공성공성공 (1,984MB)

실패 양상은 지정한 메모리 비용에 거의 선형으로 늘었습니다. 동시 1건당 약 20MB이며 Tomcat 스레드 상한이 100이므로 Argon2 몫만 약 2GB입니다. 여기서 OOM(Out Of Memory)은 JVM이 새 해시에 필요한 메모리를 더 확보하지 못해 요청을 정상 처리하지 못하는 상태입니다. 실패 방식이 더 문제입니다. 성공 시 0.7초인 작업이 힙이 부족하면 12.1초를 사용하고 종료됩니다. 느려진 요청이 실패하면서 클라이언트가 재시도하고, 그 재시도가 다시 메모리를 점유합니다. 실패가 부하를 키우는 구조입니다.

기동 직후 스레드 수 × 20MB와 힙 상한을 비교해 경고하도록 했습니다. 전 스레드 동시 해시는 최악의 조건이며 힙이 부족하다는 이유로 기동을 거부하는 것이 더 큰 사고이므로 부팅은 막지 않았습니다.

RPS 제한만으로는 막히지 않는 메모리 비용

이 경로의 유입 제한은 나중에 추가했습니다. /auth/login과 /members/signup은 인증 전이라 principal(현재 로그인한 사용자를 나타내는 정보)이 없어 사용자별 제한 필터를 통과했고, 나머지 경로는 모두 제한되는데 이 둘만 열려 있었습니다. 요청마다 최소 110ms(그중 해시 87ms)를 소비하므로 미인증 공격자에게 비대칭 DoS였고, 알려진 이메일로 무제한 시도하는 크리덴셜 브루트포스도 가능했습니다. IP는 소켓 피어(getRemoteAddr) 기준으로 제한하고 위조·회전 가능한 X-Forwarded-For는 신뢰하지 않습니다. 다만 IP 단위만으로는 부족해 공유 NAT 뒤 사용자가 함께 제한되며 계정별 제한과 반복 실패 지연은 아직 없습니다.

외부 리뷰에서 지적받았고, 실제로 맞는 지적이었습니다.

RPS(Requests Per Second) 제한은 1초 동안 시작되는 요청 수를 제한합니다. Argon2가 점유하는 메모리는 같은 순간에 실행 중인 해시 수에 비례합니다. 두 값은 다른 자원입니다.

고정 윈도우(매초 시작 시 카운터가 0으로 초기화되어 초 경계에서 최대 두 배가 통과할 수 있습니다)이므로 초당 100건 상한에서도 매초 초입에 100건이 겹칠 수 있습니다. 이 경우 해시당 약 19.84MB × 100 ≈ 약 2GB를 동시에 요구합니다. 실험 결과가 이미 그 방어로도 OOM이 발생함을 보였지만 당시 결론은 “안전한 변경”이었습니다.

동시 실행 수를 따로 제한했습니다. 대기시키지 않고 즉시 거절합니다.

if (!permits.tryAcquire()) {
throw new HashCapacityExceededException(); // 429
}
try {
return delegate.matches(raw, encoded);
} finally {
permits.release();
}

대기열을 두지 않았습니다. 이 경로에서는 지연이 방어가 아니라 증폭으로 작용하기 때문입니다. 대기하는 동안 스레드가 묶이고, 그 스레드가 다시 메모리를 점유합니다. 다만 upgradeEncoding()은 저장된 해시의 접두사만 확인하는 문자열 검사라 메모리를 사용하지 않습니다. 이 경로를 막으면 점진 이관만 조용히 중단됩니다. 상한값은 기본 8(app.auth.hash-concurrency)입니다.

상한의 근거를 측정했습니다. 동시 실행 수를 바꿔 가며 피크 힙을 측정하면 동시 1건당 약 21MB입니다(로컬, Argon2id 19MiB·t=2). 힙 512MB에서는 동시 32까지 피크 508MB로 힙을 거의 모두 사용하며 동시 64부터 39건이 실패하고 소요가 0.7초에서 7.4초로 증가합니다. 힙 1GB에서는 동시 64가 피크 1,019MB로 유지되고 동시 100에서 21건이 실패합니다. 따라서 상한은 (힙 − 기준 사용량) ÷ 약 21MB로 산정할 수 있습니다. 512MB 힙에서 기본 8은 피크 177MB로 힙의 34%를 사용합니다. 운영 힙에서 동일한 측정을 반복하는 것이 남은 작업입니다.

두 계열을 같은 조건에서 재면

같은 힙(2GB)에서 인코더 설정과 동시 로그인 수를 바꿔 검증 1건의 지연, 처리량, 피크 힙을 측정했습니다.

설정검증 1건처리량동시 100 피크 힙동시 100 결과
bcrypt cost 1090ms약 100/s8MB정상, 1.0초
bcrypt cost 12350ms약 28/s7MB정상, 3.6초
bcrypt cost 141,400ms약 7/s7MB정상, 13.7초
Argon2id 16MiB31ms약 137/s1,712MB정상, 0.7초
Argon2id 19MiB (현재 설정)41ms약 65/s2,035MB정상, 1.5초
Argon2id 32MiB81ms약 35/s2,037MB정상, 2.8초
Argon2id 64MiB327ms약 6/s포화32건 실패
Argon2id 128MiB290ms약 3/s포화38건 실패

두 계열의 차이는 분명합니다. bcrypt는 메모리가 거의 늘지 않고(동시 500에서도 9MB) CPU와 시간이 비쌉니다. bcrypt cost 14는 동시 500에서 20초 안에 끝나지 않았습니다. Argon2id는 시간이 짧은 대신 메모리가 동시 수만큼 곱해집니다. 19MiB 설정에서 동시 100건이 피크 2,035MB이므로 동시 1건당 약 20MB가 잡힙니다. 앞의 512MB 측정(약 21MB)과 같은 값입니다.

64MiB와 128MiB가 더 안전한 것은 아닙니다. 동시 50만 넘어도 OOM이 발생해 19MiB보다 먼저 무너집니다. 파라미터는 배포 환경의 힙과 예상 동시성을 기준으로 정해야 합니다.

동시 실행 상한을 걸면

같은 조건에서 동시 실행 상한만 8로 두면 결과가 달라집니다.

조건거절피크 힙p95처리량
상한 없음, 동시 1000건2,035MB1,514ms65.5/s
상한 8, 동시 10092건173MB96ms77.7/s
상한 8, 동시 500492건173MB127ms49.7/s

피크 힙은 2,035MB에서 173MB로, p95는 1,514ms에서 96ms로 줄었습니다. 대신 초과분은 즉시 거절됩니다. 상한은 지연을 낮추는 장치가 아니라 지연이 무너지지 않게 하는 장치이며 그 대가가 거절입니다.

위 측정은 로컬 Apple Silicon, 힙 2GB, 각 조건 1회입니다. 지연은 검증 1회의 p50·p95이고 처리량은 완료 건수를 소요 시간으로 나눈 값입니다.

크래킹 비용은 문헌 값으로 본다

공격자 하드웨어에서의 크래킹 비용은 직접 재지 않고 공개 벤치마크를 인용합니다. OWASP의 PBKDF2 권고 수치도 RTX 4000 GPU 테스트를 기준으로 산출된 것입니다. 비밀번호 관리자 쪽 수치가 참고가 됩니다. Bitwarden 커뮤니티에 따르면 기본값인 PBKDF2-SHA256 600,000회는 RTX 4090에서 초당 약 15,000 추측으로 알려져 있습니다.

같은 도구로 두 알고리즘을 비교할 수는 있습니다. hashcat은 bcrypt가 모드 3200, Argon2가 모드 34000이고 6.2.5+ 는 Apple Silicon Metal 백엔드도 지원합니다. 다만 그 값은 그 장비에서의 상대값입니다. 파라미터를 정하는 근거는 자기 서버에서의 검증 지연과 메모리 예산입니다. 크래킹 비용은 문헌 값으로 규모를 보정합니다.

3. 테스트가 통과했는데 가입은 깨질 수 있었다

교체 후 기존 자동화 테스트는 모두 통과했습니다. 하지만 “프로젝트의 모든 동작이 안전하다”는 뜻은 아닙니다. 테스트가 작성한 입력과 경로만 검증했다는 뜻입니다. 실제 데이터베이스에 저장할 해시 문자열의 길이는 따로 측정해야 했습니다.

{bcrypt}$2a$10$... 68자
{argon2}$argon2id$v=19$m=19456,t=2,p=1$... 105자
password_hash varchar(100) not null

저장할 수 없습니다. 여기서 password_hash는 사용자의 비밀번호가 아니라, 비밀번호를 검증하기 위해 저장하는 해시 문자열을 담는 데이터베이스 컬럼입니다. 실제 가입에서는 Data too long으로 실패했을 것입니다. 인코더 단위 테스트가 데이터베이스까지 거치지 않아 이 문제를 찾지 못했습니다. 인코딩과 검증만 확인하므로 컬럼 길이는 검증 대상이 아니었습니다. 알고리즘 교체에는 애플리케이션 코드뿐 아니라 스키마 변경도 필요했습니다. 컬럼은 255로 확장했습니다. 이후 파라미터를 상향하면 문자열이 더 길어질 수 있으므로, 매번 마이그레이션을 추가하지 않도록 여유를 둔 것입니다.

4. 점진 이관의 종료 조건

신규 가입은 Argon2id로 처리됩니다. 기존 BCrypt 해시를 가진 회원은 그대로 남으며 저장된 값이 해시뿐이라 배치로 이관할 수 없습니다. 원문 비밀번호는 로그인 요청을 처리하는 순간에만 서버에 들어옵니다. 로그인에 성공한 순간 새 알고리즘으로 다시 해싱합니다. Spring Security는 이 흐름을 위한 확장 지점을 제공합니다.

로그인 성공
→ DaoAuthenticationProvider가 PasswordEncoder.upgradeEncoding(저장된해시) 질의
→ 참이면 UserDetailsPasswordService.updatePassword(user, 새로_인코딩된_해시)

DelegatingPasswordEncoder는 저장된 해시 앞의 표식을 보고 어떤 알고리즘으로 검증할지 선택하는 구성 요소입니다. 현재 알고리즘과 다르면 upgradeEncoding()이 “새 해시로 바꿀 대상”이라고 알려줍니다. 따라서 {bcrypt}와 접두사 없는 레거시가 모두 대상입니다. Argon2id 인코더는 파라미터가 변경될 때도 참을 반환하므로 이후 상향 시에도 같은 경로로 이관됩니다. Spring의 UserDetailsPasswordService.updatePassword()가 새 해시를 저장합니다. 이 기능이 자동으로 켜져 있지 않은 것은 기존 운영 코드를 예기치 않게 바꾸지 않도록 옵트인으로 남겨 둔 설계입니다.

구현 원칙은 간단합니다.

이관 실패가 로그인을 막지 않습니다.

회원을 찾지 못하거나 데모 계정이면 이관을 건너뛰고 인증은 통과시킵니다. 이관은 편의이고 인증은 기능이므로 이관 로직의 버그가 전체 로그인을 막아서는 안 됩니다. 이 조건도 테스트로 고정했습니다.

문제는 남습니다. 로그인하지 않는 계정은 옛 해시로 남습니다. 원문 비밀번호 없이는 재인코딩할 수 없고, 해시는 단방향이라 코드만으로 이관을 끝낼 수도 없습니다. 해당 사용자가 로그인하거나 비밀번호를 재설정하는 방법뿐입니다. 레거시 인코더를 제거하면 재설정이 강제되지만 이 시스템에는 비밀번호 재설정 플로우가 없습니다. 그런 상태에서 제거하면 이관이 아니라 계정 잠금이 됩니다.

종료 시점을 확인할 수 있도록 남은 레거시 해시 수를 게이지로 노출합니다.

password_hash_legacy_count

이 값이 0이 되면 레거시 인코더를 제거할 수 있습니다. 그 시점이 이관의 종료입니다.

7일 이상 남아 있으면 info 심각도로 알림이 발생합니다. 경보가 아니라 종료 시점을 확인하기 위한 용도입니다.

한계와 후속 작업

  • 동시 실행 상한과 인코더 파라미터는 로컬 2GB 힙 실측 위에 있습니다. 배포 환경의 힙과 예상 동시성에서 같은 측정을 반복해 값을 확정해야 합니다.
  • 크래킹 비용은 공개 벤치마크를 인용했습니다. 같은 파라미터로 GPU를 직접 재지는 않았습니다.

정리: 강도는 서버 예산 안에서 선택한다

Argon2id가 더 현대적인 선택이라는 사실만으로 전환을 끝낼 수는 없습니다. 이 변경에서 함께 결정해야 했던 것은 네 가지였습니다.

  • 보안 비용: 오프라인 크래킹 비용을 얼마나 높일 것인가
  • 실행 비용: 해시 한 건이 CPU와 메모리를 얼마나 쓰는가
  • 동시성 경계: 같은 순간에 해시를 몇 건까지 허용할 것인가
  • 이관 경계: 레거시 해시를 언제 제거할 수 있는가

중요한 질문은 Argon2id를 사용할 것인가가 아니었습니다.

이 서버의 힙과 예상 동시성 안에서, 정상 로그인은 지키면서 공격자의 비용을 어디까지 올릴 것인가?

이번 변경의 답은 로컬 측정 기준 19MiB · t=2 · p=1, 동시 해시 상한 8, 즉시 429 거절, 로그인 시점의 점진 이관과 레거시 해시 수 게이지였습니다. 다만 상한과 파라미터는 배포 환경에서 다시 재야 합니다. 측정하지 않은 운영 환경까지 안전하다고 확장해서 말할 수는 없습니다.

참고

  • OWASP Password Storage Cheat Sheet: 알고리즘 순위와 최소 파라미터(Argon2id 19MiB·t=2·p=1, bcrypt work factor 10), 그리고 work factor를 “해당 서버에서 실험해 정하라”는 지침과 DoS 경고.
프로필 사진
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.