[Express] Crypto를 사용한 비밀번호 암호화 구현

JuseungL·2024년 1월 10일
0

Node.js

목록 보기
7/8
post-thumbnail

이전까지 단방향 암호화와 양방향 암호화(대칭키, 비대칭키) 그리고 단방향 암호화의 단점을 보완하기 위해 솔팅과 키 스트레칭 기법에 대해 알아보았고 실제로 Node.js에서 어떻게 비밀번호 암호화를 구현했는지에 대한 포스팅이다.

우선 다양한 암호화 방법에 대해 알아보자

다양한 비밀번호 암호화 방법

SHA-2(Secure Hash Algorithm 2)

  • SHA-256, SHA-384, SHA-512 등을 포함한 이 해시 함수는 고정 크기 Digest Size(해시값)을 생성
  • GPU를 이용한 연산속도가 매우 빠르기 때문에 password 암호화에 권장되지 않음

    이전 게시글들에서 다뤘듯이 해싱 함수의 연산속도가 빠를수록 공격자(해커)의 하드웨어를 통한 오프라인 무차별 대입 공격(Brute-force Attack)에 더 취약하다.
    빠른 해시를 사용하여 암호화를 진행시 공격자는 오프라인 공격으로 초당 수십억개의 해시를 계산할 수 있다.

PBKDF2

  • 해시함수의 컨테이너 역할을 한다.
  • 지정한 해시함수, 솔팅 그리고 키 스트레칭을 사용하여 암호화한다.
  • 가장 많이 사용되는 함수이며 ISO 표준에 적합하며 NIST에서 승인된 알고리즘이라고 한다.
  • Node.js의 내장 모듈인 crypto의 메소드이다.

Bcrypt

  • 키(key) 방식의 대칭형 블록 암호(Blowfish)에 기반을 둔 암호화 해시 함수
  • 레인보우 테이블 공격을 방지하기 위해 솔팅과 키 스트레칭을 적용한 대표적인 예
  • 보안에 신경 많이 쓰는 오픈 소스 운영체제 OpenBSD에서 사용한다. 그만큼 보안성이 뛰어나다.

Scrypt

  • 오프라인 Brute-force attack에 대해 더 강력하지만, 많은 메모리와 CPU를 사용한다.
  • 여러 언어의 라이브러리로 제공된다.
  • 다른 암호기반 KDF에 비해 많은 양의 메모리를 사용하도록 설계되었다.
  • 하드웨어 구현을 하는데 크기와 비용이 훨씬 더 비싸기 때문에, 주어진 자원에서 공격자가 사용할 수 있는 병렬처리의 양이 한정적이다.

❗️ SHA와 Bcrypt 중 어떤것을 택해야하는가❗️
정답이 없는 것 같다. 여러가지 글을 찾아봤을때 Bcrpt를 선호하는 이유는 SHA 관련 해싱 함수들은 GPU를 이용한 공격에 취약하며 (산속도가 매우빠르기 떄문) 많은 메모리를 필요로 하지 않아 공격자에게 유리하기 때문이다.
그러나 반대로 아래 스택오버플로우 글을 보면
https://stackoverflow.com/questions/6951867/nodejs-bcrypt-vs-native-crypto "외부 모듈을 사용하면 악성 코드가 주입될 가능성이 있어 원래의 보안 목표를 무너뜨릴 수 있습니다."라고 한다. 그래서 내장 모듈인 crypto를 활용한 암호화 방식을 채택하는 것을 권하고 있다.
두 가지 방법을 모두 알고 상황에 따라 사용하는 것이 중요할 것 같다.

Crypto를 활용한 비밀번호 암호화

ISO-27001 보안 규정에 적합한 PBKDF2를 통해 비밀번호 암호화를 구현해 봤다

import util from 'util';
import crypto from 'crypto';

const randomBytesPromise = util.promisify(crypto.randomBytes);
const pbkdf2Promise = util.promisify(crypto.pbkdf2);

// 솔트 생성
const createSalt = async () => {
  const buf = await randomBytesPromise(64);
  return buf.toString("base64");
};

// pbkdf2를 활용하여 해싱
const createHashedPassword = async (password) => {
  const salt = await createSalt();
  const key = await pbkdf2Promise(password, salt, 104906, 64, "sha512");
  const hashedPassword = key.toString("base64");
  return { hashedPassword, salt };
};

첫 번째 함수를 통해서 솔트를 생성하고 두 번째 함수에서 SHA512 해싱 알고리즘을 통해 위에서 생성한 솔트와 함께 104906번 키 스트레칭을 하여 암호화를 했다.

그렇게 해서 위에서 생성된 해싱 함수 출력값(Digest)과 솔트를 데이터베이스에 저장해둔다.

//비밀번호 검증
const verifyPassword = async (password, userSalt, userPassword) => {
  const key = await pbkdf2Promise(password, userSalt, 104906, 64, "sha512");
  const hashedPassword = key.toString("base64");
  if (hashedPassword === userPassword) return true;
  return false;
};

실제로 비밀번호를 검증할때는 로그인 요청이 들어왔을때 입력값을 데이터베이스에서 해당 유저의 솔트 값을 들고와서 똑같은 방식으로 입력값을 해싱해서 데이터베이스에 저장되어있던 암호화된 비밀번호와 비교해서 Authorization을 진행한다.

Bcrypt를 활용한 비밀번호 암호화

실제로 개발 중인 서비스에 적용해보지는 못했지만 보안이 강력한 만큼 높은 수준의 보안을 필요로하는 경우에 한번 사용해보겠다.

Reference

https://inpa.tistory.com/372
https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-bcrypt-%EB%AA%A8%EB%93%88-%EC%9B%90%EB%A6%AC-%EC%82%AC%EC%9A%A9%EB%B2%95#sha-2secure_hash_algorithm_2
https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-crypto-%EB%AA%A8%EB%93%88-%EC%95%94%ED%98%B8%ED%99%94
https://velog.io/@yenicall/%EC%95%94%ED%98%B8%ED%99%94%EC%9D%98-%EC%A2%85%EB%A5%98%EC%99%80-Bcrypt

profile
기록

0개의 댓글