머리말
최근에 Next.js를 사용하여 몇 가지 프론트엔드/백엔드 프로젝트를 작성했는데, 그 중에서 better-auth를 사용자 인증에 사용했습니다.
하지만 실제로 AI가 직접 관련 코드를 작성하면 비밀번호를 평문으로 전송하기 쉽다는 것을 발견했습니다. 온라인에서는 일반적으로 HTTPS를 사용하고 전송 계층에 TLS 암호화가 이미 적용되어 있지만, 그렇다고 해서 비밀번호를 평문으로 요청 본문에 마음대로 넣어도 된다는 의미는 아닙니다.
HTTPS는 브라우저에서 TLS 종료 지점(예: nginx)까지의 전송 보안만 보장할 뿐, 비밀번호는 리버스 프록시, 게이트웨이, 로그, 데이터베이스 등에서 여전히 평문으로 존재합니다. 이는 누군가 이러한 접근 권한을 얻기만 하면 모든 사람의 계정에 쉽게 로그인할 수 있다는 의미입니다. 개인 프로젝트에서는 비밀번호 암호화를 완전히 무시할 수 있지만, 제품으로서는 용납될 수 없습니다.
그래서 이 블로그를 작성하게 되었습니다. better-auth를 사용할 때 애플리케이션 계층에서 비밀번호에 RSA 비대칭 암호화를 적용하는 방법을 설명합니다. 이 내용을 AI에 제공하면 다른 프로젝트에서도 유사하게 구현할 수 있을지도 모릅니다. 단, MIT 라이선스에 따라 저작자 표시를 해주시기 바랍니다. 자세한 내용은 글 말미를 참조하세요.
전체 흐름
- 사용자가 브라우저에 비밀번호를 입력합니다.
- 프론트엔드에서
NEXT_PUBLIC_RSA_PUBLIC_KEY를 읽습니다. - RSA-OAEP를 사용하여 비밀번호를 암호화합니다.
- 암호화 패키지에는 타임스탬프, 랜덤 솔트, 요청 ID, HMAC 서명이 포함됩니다.
- 프론트엔드에서 암호화된 문자열을 better-auth에 전달합니다.
- 서버에서 better-auth의
password.hash/password.verify에서 복호화합니다. - 최종적으로 데이터베이스에는 bcrypt 해시만 저장됩니다.
전체 과정에서 평문 비밀번호는 사용자가 비밀번호를 입력할 때만 나타납니다.
클라이언트 측 암호화
여기서는 비밀번호 자체만 암호화하지 않고, 여러 필드를 함께 RSA 암호문에 넣었습니다:
password|timestamp|salt|requestId
각각의 의미:
password는 사용자가 입력한 원본 비밀번호입니다.timestamp는 요청이 만료되었는지 판단하는 데 사용됩니다.salt는 동일한 비밀번호라도 매번 다른 암호문을 생성하도록 합니다.requestId는 재전송 공격을 방지하는 데 사용됩니다.
로그인 페이지에서 better-auth에 제출하기 전에 먼저 rsaEncrypt를 호출합니다.
회원가입 시에도 마찬가지입니다:
이렇게 하면 패킷 캡처 시 password 필드에 원본 비밀번호가 아닌 RSA 암호화된 데이터 패킷이 표시됩니다.

서버 측 복호화
먼저 개인 키로 복호화합니다:
그런 다음 일괄적으로 검증합니다:
여기서 주로 수행하는 작업은 다음과 같습니다:
- 암호화 패키지 형식을 확인합니다.
- 타임스탬프가 만료되었는지 확인합니다.
requestId가 이미 사용되었는지 확인합니다.- RSA 개인 키로 복호화합니다.
- 외부 타임스탬프와 내부 타임스탬프가 일치하는지 확인합니다.
- 외부 요청 ID와 내부 요청 ID가 일치하는지 확인합니다.
- HMAC 서명을 검증합니다.
- 비밀번호의 bcrypt 해시를 반환합니다.
requestId의 중복 제거는 Redis를 사용합니다:
즉, 첫 번째 요청만 성공적으로 기록되고, 이후 동일한 requestId를 재사용하면 거부되어 재전송 공격을 방지합니다.
공개 키/개인 키 쌍 생성 명령어는 다음과 같습니다:
물론 ssh-keygen을 직접 사용해도 가능하며, 반드시 개인 키의 보안을 보장해야 합니다.
better-auth 연동
better-auth는 기본적으로 이메일 비밀번호 로그인을 처리하지만, 여기서는 암호화와 해시를 구현하기 위해 비밀번호 처리 로직을 사용자 정의해야 합니다.
여기서 한 가지 세부 사항이 있습니다: better-auth의 minPasswordLength가 1로, maxPasswordLength가 4096으로 설정되었습니다.
그 이유는 better-auth에 전달되는 것이 더 이상 원본 비밀번호가 아니라 RSA 암호화된 데이터 패킷이기 때문입니다. 기본 길이 제한을 계속 사용하면 better-auth 계층에서 쉽게 차단될 수 있습니다.
비밀번호 길이를 제한해야 한다면 클라이언트 측 rsaEncrypt에서만 처리해야 합니다.
물론 better-auth는 기본적으로 로그인과 회원가입 시나리오에서만 비밀번호 검증이 필요합니다. 비밀번호 변경, 계정 탈퇴 등의 경우에는 rsaEncrypt와 rsaDecryptAndValidate를 재사용하기만 하면 됩니다.
better-auth 기타 보안 설정
비밀번호 암호화 외에도 betterAuth 초기화 시 몇 가지 보안 항목을 구성할 수 있습니다:
의미는 대략 다음과 같습니다:
- CSRF 검사를 비활성화하지 않습니다.
- 프로덕션 환경에서 Secure Cookie를 사용합니다.
- Cookie에
httpOnly를 설정합니다. - Cookie에
sameSite: "lax"를 설정합니다.
그리고 로그인, 회원가입, 비밀번호 찾기에 대한 속도 제한:
이렇게 하면 크리덴셜 스터핑, 무분별한 회원가입, 이메일 스팸 등의 문제를 줄일 수 있습니다.