Skip to content

정리: 새 앱 비밀번호를 128비트 이상으로 만들고, 무작위 비밀은 sha256으로 저장해 바로 찾는다 - #53

Merged
mack-erel merged 2 commits into
mainfrom
app-passwords
Oct 19, 2026
Merged

mack-erel merged 2 commits into
mainfrom
app-passwords

Conversation

@mack-erel

Copy link
Copy Markdown
Contributor

요약

새 앱 비밀번호를 128비트 이상으로 만듭니다. 서버가 만든 무작위 비밀(새 앱 비밀번호·자체 발급 토큰)은 sha256$로 저장해서, 로그인할 때 해시값으로 바로 찾습니다. 회원 공통 수치 반영 계획의 P3입니다. 마이그레이션은 없습니다.

⚠ #52(P2) 위에 쌓은 PR입니다. base는 password-policy입니다. #52가 머지되면 base를 main으로 바꾸겠습니다.

왜

  • 메일 프로토콜은 앱 비밀번호로 로그인하는 쪽으로 갑니다(공통 수치 결정 1-2). 그런데 앱 비밀번호가 소문자 16자(약 75비트)라 기준(128비트 이상 CSPRNG)에 못 미쳤습니다.
  • 앱 비밀번호가 사람이 고른 비밀번호와 같은 scrypt로 저장됐습니다. 그래서 로그인할 때마다 계정의 자격증명 전부에 scrypt를 돌렸습니다. 기본 비밀번호 해시를 강화하면(P4) 이 비용이 그대로 곱해집니다.

바뀐 것

  • 새 앱 비밀번호: 소문자 28자이고, 4자씩 7묶음으로 보여 줍니다(log2(26^28) ≈ 131.6비트). 발급 때 한 번만 보이는 점, 여러 개 발급, 개별 철회, 마지막 사용 시각은 그대로입니다.
  • sha256$<hex> 저장과 바로 찾기:
    • 로그인할 때 해시값으로 바로 찾으므로 scrypt를 돌지 않습니다.
    • 빠른 경로가 받는 kind는 앱 비밀번호와 토큰뿐입니다(허용 목록).
    • 표면 제한과 마지막 사용 시각은 빠른 경로에서도 똑같이 지킵니다.
  • 서버 키(HMAC)는 쓰지 않습니다:
    • 결정은 "HMAC + 서버 키 등"을 예로 들었지만, 131비트 무작위 값에서는 키가 더하는 안전이 거의 없습니다.
    • 반대로 마스터 키에 묶이면, 키가 없거나 바뀔 때 모든 앱 비밀번호가 한꺼번에 무효가 됩니다(메일 앱이 전부 멈춥니다).
    • 리뷰어도 이 판단이 이 코드베이스에 맞다고 확인했습니다.
  • 예전 16자 앱 비밀번호·토큰(scrypt 저장)은 계속 받습니다. 이 창의 결정 B(재발급을 권하되 강제하지 않음)를 따랐습니다.
  • 계정 열거 방지: 예전 형식 자격증명이 없는 계정도 실패할 때 scrypt 1회 비용을 태웁니다. 그래야 응답 시간으로 계정이 있는지 드러나지 않습니다.

검증

  • npm run verify 통과(테스트 2750개).
  • 새 테스트 항목:
    • 생성 형식과 엔트로피
    • sha256$ 저장 형식
    • 하이픈·공백·대문자를 섞어 입력해도 로그인되는지
    • 표면 제한이 지켜지는지
    • 예전 16자 앱 비밀번호가 계속 되는지
    • 토큰을 바로 찾는지, 정규화한 토큰은 거부되는지
  • 독립 리뷰: CRITICAL·HIGH 없음. MEDIUM 1건(롤백 주의 문서)과 LOW 5건을 반영했습니다.

배포 영향

  • 마이그레이션은 없습니다(형식이 자기서술형).
  • ⚠ 롤백 주의: 이 릴리즈 뒤에 발급한 앱 비밀번호·토큰은 이전 릴리즈에서 로그인되지 않습니다. OPERATIONS에 적어 두었습니다.

🤖 Generated with Claude Code

Base automatically changed from password-policy to main October 19, 2026 01:43
mack-erel and others added 2 commits October 19, 2026 10:48
왜(회원 공통 수치 2026-10-18 결정 1-2, 이 창 결정 B): 메일 프로토콜은 앱 비밀번호로 로그인하는 쪽으로 간다. 그런데
앱 비밀번호가 소문자 16자(약 75비트)라 공통 기준(128비트 이상 CSPRNG)에 못 미쳤고, 사람이 고른 비밀번호와 같은
scrypt로 저장돼 로그인마다 계정의 자격증명 전부에 scrypt를 돌렸다. 기본 비밀번호 해시를 강화하면(P4) 이 비용이
그대로 곱해진다.

- 새 앱 비밀번호: 소문자 28자, 4자씩 7묶음(log2(26^28) ≈ 131.6비트). 발급 때 한 번만 보이고, 여러 개·개별
  철회·마지막 사용 시각은 그대로다.
- 서버가 만든 무작위 비밀(새 앱 비밀번호·자체 발급 토큰)은 `sha256$<hex>`로 저장한다. 엔트로피가 충분해 느린
  해시가 필요 없고(결정 1-2가 허용), 로그인 때 해시값으로 바로 찾는다 — scrypt를 돌지 않는다.
  서버 키(HMAC)는 쓰지 않는다: 이 엔트로피에서 더하는 안전이 거의 없고, 마스터 키에 묶이면 키가 없거나 바뀔 때
  모든 앱 비밀번호가 한꺼번에 무효가 된다(메일 앱 전부가 멈춘다). 사람이 고른 비밀번호에는 쓰지 않는다.
- 예전 16자 앱 비밀번호·토큰(scrypt 저장)은 계속 받는다(결정 B: 재발급을 권하되 강제하지 않는다).
- 표면 제한(scopes)·last_used_at은 빠른 경로에서도 같다. 마이그레이션 없음(형식이 자기서술형).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 빠른 경로가 `kind != 0`이라 앞으로 credentials에 새 kind를 같은 형식으로 넣으면 그대로 메일 로그인 수단이 됐다.
  허용 목록(앱 비밀번호·자체 토큰)으로 바꿨다 — fail closed.
- 예전 형식 자격증명이 없는 계정은 실패 때 scrypt를 한 번도 돌지 않아 없는 계정(더미 scrypt)보다 빨랐다. 같은 비용을 태운다.
- OPERATIONS: 앱 비밀번호를 별도 절로 옮겼고(비밀번호 규칙 목록이 앱 비밀번호 규칙처럼 읽혔다), 롤백 주의를 넣었다 —
  이 릴리즈 뒤에 발급한 sha256$ 앱 비밀번호·토큰은 이전 릴리즈에서 로그인되지 않는다.
- auth.ts 머리 주석에 sha256$ 형식, 테스트 두 개(표면 막힌 새 앱 비밀번호가 예전 루프에서도 null, 정규화한 토큰은 거부).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mack-erel
mack-erel merged commit efba4eb into main Oct 19, 2026
9 checks passed
@mack-erel
mack-erel deleted the app-passwords branch October 19, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant