이끌든지 따르든지 비키든지

Project/Node.js

[Node.js] Crypto 모듈로 비밀번호 암호화하기

SeongHo5 2023. 9. 14. 17:11

웹 서비스를 운영하면 사용자 정보를 안전하게 저장하고 관리해야 한다.

이를 위해 암호화하거나 해싱하는 등의 작업이 필요한데, Node.js 환경에서는 bcrypt, Scrypt, crypto 등의 모듈을 통해 이 작업을 수행할 수 있다.

 

이 중, crypto 모듈을 사용해 사용자의 정보(비밀번호)를 암호화하는 방법에 대해 알아본다.


Crypto 모듈 설치

npm install crypto

위 명령어를 통해 crypto 모듈을 설치할 수 있다. package.json의 dependencies에도 자동으로 crypto가 추가된다.

Typescript 환경에서 작업하는 경우에도 특별히 다른 모듈 설치 없이 사용할 수 있다.


Crypto로 비밀번호 암호화하기

Hash 알고리즘과 Salt를 사용한 비밀번호 암호화

 

비밀번호 암호화는 위 그림의 순서대로 진행되는데, 그림에 설명을 덧붙여보자면

  1. 사용자의 비밀번호를 받아온다.
  2. 무작위로 생성된 Salt 값과 사용자 비밀번호를 합쳐 암호화, 해싱을 진행한다.
  3. 암호화 처리된 비밀번호를 저장소에 보관한다.

 

※ Salt란?
비밀번호 해싱에 사용하는 무작위 문자열을 Salt라 한다.
Salt를 사용해 해싱하면 , 문자열 사이사이에 무작위 값을 추가하여 암호화하기 때문에, 같은 암호여도 서로 다른 문자열을 생성해 이를 유추할 수 없고, 암호를 안전하게 보관할 수 있다.

 

위 과정을 코드로 구현하면 아래와 같다.

이 코드에서는 암호화와 해싱에 PBKDF2, SHA512 알고리즘을 사용했지만,

반드시 이 2가지로 작업을 해야 하는 것은 아니고, 여러 다른 암호화 알고리즘 중 적절한 알고리즘을 선택해 적용하면 된다.

 

import crypto, { randomBytes } from 'crypto';

const userPassword: string = '사용자의 비밀번호';

// Salt값 생성
const salt: string = crypto.randomBytes(16).toString('hex');

// 비밀번호 해싱
function hashPassword(password: string, salt: string): string {
    const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'SHA512').toString('hex');
    return hash;
}

// 비밀번호를 해싱하여 저장
const hashedPassword: string = hashPassword(userPassword, salt);

 

 

해싱은 단방향이기 때문에, 위 작업으로 해싱된 비밀번호는 어떤 방법으로도 평문(암호화 전 상태)으로 되돌릴 수 없다.

 

 

※ 암호화 함수의 매개변수
pbkdf2Promise('password', 'salt', 'iterations', 'keyLength', 'digest');
iterations : 반복할 횟수(정수값)
keyLength : 해싱으로 생성할 결과값의 길이(정수값)
digest : 해싱에 사용할 알고리즘(SHA-512, SHA-256 등)

 

그렇다면 사용자가 로그인을 요청할 때 입력한 비밀번호가 저장소에 저장된 비밀번호와 일치하는지 어떻게 확인할까?

 

// DB에서 로그인을 요청한 사용자의 usersalt와 암호화된 userPassword 값을 가져온다.
const verifyPassword = async (inputPassword: string, userSalt: string, userPassword: string) => {
    const key = await pbkdf2Promise(inputPassword, userSalt, 10000, 64, "SHA512");
    const hashedPassword = key.toString("base64");

    if (hashedPassword === userPassword) {
        return true;
    } else {
        return false;
    }
}

const isPasswordValid = await util.verifyPassword(password, salt, hashedPassword);
if (isPasswordValid) {
	// 로그인 성공시 작업
} else {
	// 로그인 실패시 작업
}

 

입력한 암호를 (사용자의 회원가입 때 사용했던) salt 값으로 암호화 & 해싱을 했을 때, DB에 저장된 암호와 일치하는지 비교하는 방식으로 사용자의 로그인을 처리한다.

 

반복 횟수, 사용 알고리즘 등은 회원가입 로직 등을 통해 다시 확인할 수 있지만, salt 값은 항상 무작위로 생성되므로 분실할 경우 재생성할 수 없다.  따라서 정상적으로 사용자의 로그인 요청을 처리하려면, 회원가입 시 생성된 사용자별 Salt 값과 암호화된 비밀번호를 DB 등 저장소에 적절히 관리해야 한다.