Menu
CoddyTech

비밀번호 확인하기

Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 24개 중 6번째.

로그인은 저장된 솔트와 원래 키 길이를 사용해 입력한 비밀번호에서 키를 도출합니다. 새 솔트를 사용하면 올바른 비밀번호를 입력해도 다른 키가 생성됩니다. 원래 비밀번호는 저장된 데이터에서 절대로 복호화되지 않습니다.

// 초대 코드가 salt.keyHex 형식으로 저장되었습니다
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);

길이가 같은지 확인한 다음, 결과 바이트 시퀀스를 timingSafeEqual을 사용해 비교합니다. 이 함수는 콘텐츠에 따라 조기에 종료되어 타이밍을 통해 정보가 노출될 수 있는 상황을 방지합니다. Buffer의 ===는 동일성을 확인하며, 일반적인 문자열 비교는 상수 시간 처리를 보장하지 않습니다. 비밀번호 함수는 비밀번호 레코드의 콜론 구분자와 64바이트 키를 사용합니다. 추가하는 POST /login 경로는 결과를 사용해 200 또는 401을 반환합니다.

challenge icon

챌린지

쉬움

이 단계에서는 Never Store Passwords의 프로젝트 파일( passwords.js 포함)이 아니라, 강좌용으로 준비된 비공개 노트 API 프로젝트를 엽니다. 이전 작업은 해당 강좌에 그대로 남아 있습니다. passwords.js를 수정하기 전에 제공된 파일을 읽어 보세요. 이 복사본에는 비밀번호 해싱 도우미와 제공된 디버깅 경로가 들어 있습니다.

이제 회원 가입 시 솔트가 적용된 해시를 저장합니다. 회원 가입 시 저장한 값과 비밀번호를 대조하는 로그인을 추가하세요.

  1. passwords.js에서 async 함수 verifyPassword(password, stored)를 추가하고 내보내세요. stored는 회원 가입 시 저장된 값으로, 예를 들면 f2bb...f065:c3f7...dbd2와 같습니다. password가 해당 값을 만든 비밀번호라면 true를 반환하고, 그렇지 않으면 false를 반환하세요.
    • 콜론 앞에서 솔트를 가져오세요.
    • hashPassword와 같은 방식으로 해당 솔트를 사용해 password를 64바이트 키로 해싱하세요.
    • 콜론 뒤에 저장된 키를 16진수에서 디코딩하세요. 두 키의 바이트 길이가 다르면, 키 바이트 비교에서처럼 timingSafeEqual을 호출하지 말고 false를 반환하세요. 그렇지 않으면 비교 결과를 반환하세요.
  2. server.js에서 hashPassword 옆에 verifyPassword를 가져오세요. POST /login을 추가하세요. 이 경로는 회원 가입과 같은 본문을 받습니다. 예를 들면 {"username": "ada", "password": "correct horse"}입니다.
    • 해당 사용자 이름으로 사용자를 찾으세요. verifyPassword는 느리고 프라미스를 반환하므로, 완료될 때까지 기다리세요.
    • 사용자가 있고 비밀번호가 맞으면 상태 코드 200과 {"username": "ada"}를 응답으로 보내세요.
    • 그렇지 않으면 상태 코드 401과 {"error": "Wrong username or password"}를 응답으로 보내세요. 401 Unauthorized는 요청자가 누구인지 증명하지 못했다는 뜻입니다.
    • 비밀번호가 틀린 경우와 가입한 적 없는 사용자 이름인 경우에 정확히 같은 401 응답을 보내세요. 그러면 응답 본문으로는 어떤 사용자 이름이 존재하는지 알 수 없습니다.

직접 해 보기: 실행을 누르세요. POST /signup에 {"username": "ada", "password": "correct horse"}를 보내세요. 상태 코드는 201입니다. 같은 본문으로 POST /login을 보내세요. 상태 코드 200과 {"username": "ada"}가 응답됩니다. {"username": "ada", "password": "wrong horse"}를 사용해 POST /login을 보내세요. 상태 코드 401과 {"error": "Wrong username or password"}가 응답됩니다. {"username": "nobody", "password": "correct horse"}를 사용해 POST /login을 보내세요. 똑같이 401이 응답됩니다.

직접 해보기

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// 비밀번호를 signup이 저장하는 텍스트로 변환합니다.
export async function hashPassword(password) {
    const salt = randomBytes(16).toString('hex');
    const key = await scryptAsync(password, salt, 64);
    return `${salt}:${key.toString('hex')}`;
}

인증과 보안의 모든 레슨

직접 연습해 보세요: Express 플레이그라운드