NestJS/Typescript : Bcrypt 모듈을 통한 비밀번호 암호화

NestJS/Typescript : Bcrypt 모듈을 통한 비밀번호 암호화

단방향 암호화

  • 한쪽 방향으로 암호화하는 것을 의미
  • 암호화는 가능하지만 복호화는 불가능 함
    • 양방향 암호화 : 암호화된 데이터를 복호화 할 수 있으며, 대칭키, 공개키 암호화 알고리즘 등이 있다.
  • 해시(Hash) 함수MD5, SHA 등의 알고리즘이 있다.

Bcrypt

  • Blowfish 암호에 기반을 둔 암호화 해시 함수
  • Niels ProvosDavid Mazières가 설계
  • 단방향 해시 함수의 한계점을 극복하기 위해 설계

해시 함수의 한계점

  • 레인보우 테이블 : 동일한 메시지는 동일한 다이제스트(digest)를 갖는 테이블로, 일반적인 해시 함수의 특징
  • 무차별 대입 공격(Brute-Force Attack)에 취약
    • 해시 함수는 빠른 연산 속도가 장점이지만, 고성능 GPU를 이용해 무차별적으로 해시 함수를 대입하면 같은 다이제스트를 찾을 수 있음
  • bcryptSalting 방식을 통합하여 위와 같은 취약점을 보완

Salting

  • 임의의 salt 값을 덧붙여 hash 함수를 진행하는 것
  • 같은 비밀번호여도 다른 다이제스트 값을 발생시켜, 다이제스트 값끼리 비교하기 어렵게 함

Bcrypt 사용법

  • bcrypt 설치
npm install bcrypt
# 타입스크립트(typescript) 사용할 경우
npm install -D @types/bcrypt
  • 비밀번호 암호화
import * as bcrypt from 'bcrypt';
// typescript 프로젝트에서 CommonJS 모듈을 사용할 경우 사용

/* password: 비밀번호
 * saltRound: 암호화 연산에 사용되는 salt의 cost, 높을수록 속도는 느려진다. */
const password = 'helloworld!';
const saltRound = 10;

// 비동기 콜백 방식
bcrypt.hash(password, saltRound, (err, salt) => {
  // 콜백 함수 내
});

// 비동기 방식
awiat
bcrypt.hash(password, saltRound);

// 동기 방식 
bcrypt.hashSync(password, saltRound);
  • 같은 패스워드여도 bcrypt 해시 함수를 사용할 경우 결과값이 나온다.
// 첫 번째 결과
$2b$10$jT8I0bOm7TbXpVCyy6E81.4i4xkovLK5JR1FMRhPu.PjQT5j0gtbC
// 두 번째 결과
$2b$10$qm7LX5KjZ0saJvDEjS3eHuBF34SZshumNBGwMHJYy0CGWmy23bCmq
// 세 번째 결과
$2b$10$2cdNyz9.rGEQekSCCYTF3eA.iYfj4numnz0P9qyaH5aiVITH8Lafy
  • 비밀번호 검증
import * as bcrypt from 'bcrypt';
// typescript 프로젝트에서 CommonJS 모듈을 사용할 경우 사용

/* password: 비밀번호  
 * hash: 암호화된 비밀번호 */
const password = 'helloworld!';

// 비동기 콜백 방식
bcrypt.compare(password, hash, (err, res) => {
  console.log(res); // 비교 결과 출력
});

// 비동기 방식
await bcrypt.compare(password, hash);

// 동기
bcrypt.compareSync(password, hash);
const password = 'helloworld!';

const hash01 = '$2b$10$jT8I0bOm7TbXpVCyy6E81.4i4xkovLK5JR1FMRhPu.PjQT5j0gtbC';
const hash02 = '$2b$10$qm7LX5KjZ0saJvDEjS3eHuBF34SZshumNBGwMHJYy0CGWmy23bCmq';
const hash03 = '$2b$10$2cdNyz9.rGEQekSCCYTF3eA.iYfj4numnz0P9qyaH5aiVITH8Lafy';

console.log(bcrypt.compareSync(password, hash01)); // true
console.log(bcrypt.compareSync(password, hash02)); // true
console.log(bcrypt.compareSync(password, hash03)); // true

활용 코드

  • Entity에서 데이터를 삽입할 경우, BeforeInsert 데코레이터를 통해 데이터베이스에 삽입하기 전 암호화를 진행하고 데이터베이스에 삽입한다.
import {
  BeforeInsert,
  BeforeUpdate,
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import * as bcrypt from 'bcrypt';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id!: number;

  @Column({
    type: 'varchar',
    length: 100,
    unique: true,
  })
  username!: string;

  @Column({
    type: 'varchar',
    length: 100,
  })
  email!: string;

  @Column({
    type: 'varchar',
    length: 200,
  })
  password!: string;

  @Column({ name: 'is_active', default: false })
  isActive!: boolean;

  @CreateDateColumn({ name: 'created_at' })
  createdAt!: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt!: Date;

  @BeforeInsert()
  @BeforeUpdate()
  private async beforeInsert() {
    this.password = await bcrypt.hash(this.password, 10);
  }
}

참고 자료

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.