본문 바로가기
NestJS

[NestJS] bcrypt로 패스워드 해시화 하기

by 개복취 2024. 2. 6.

https://www.geeksforgeeks.org/hashing-passwords-in-python-with-bcrypt/

 

 

0. 배경

1. bcrypt?

2. 용례 및 주의점


배경

https://github.com/tscenping/BE/pull/94

사용자 정보를 담고있는 DB에서 암호를 해시화 시킬 필요가 있다고 생각해서 bycrypt 로 해싱 시켜주었는데 한번만 업데이트 되고 이후 업데이트에선 해싱되지 않아서 알아보게 되었다.

 

bcrypt?

bcrypt 이전에 'crypt' 라는 함수를 통해 유닉스(Unix) 시스템에서 비밀번호를 해싱하기 위해 처음 도입된 함수가 있었다. 그러나, 기본적인 crypt 함수는 솔트를 사용하여 해시 충돌의 위험을 줄였지만, 초기 버전은 현대의 컴퓨팅 파워에 의해 쉽게 깨질 수 있는 약한 해싱 알고리즘을 사용했다고 한다.

 

이러한 배경으로 나오게 된게 bcrypt 이다.  bcrypt는 'Blowfish' 알고리즘을 사용했는데 이를 통한 이점은 다음과 같다.

 

Blowfish
Blowfish 암호는 키를 변경할 때를 제외하면 빠른 블록 암호이다. 각각의 새 키 변경에는 약 4KB의 텍스트를 암호화하는 것과 동일한 전처리가 필요하고 이는 다른 블록 암호에 비해 느리다. 이러한 느린 키 변경은 추가 계산 요구가 공격 속도를 늦춤 으로써 사전 및 무차별 대입 공격으로부터 보호하는 데 도움이 되기 때문에 비밀번호 해싱 방법에 유용하다.

 

즉, 블로우피시는 키 변경에는 비효율적인 특징이 공격속도를 늦춰 브루트포스 등에 강점을 보인다.

 

그래서, bcrypt는 PHP, Python, Java, Ruby 등 다양한 프로그래밍 언어에서 사용할 수 있는 라이브러리를 제공한다고 한다. 이는 개발자가 쉽게 비밀번호 보안 기능을 구현할 수 있게 해주도록 한다.

 

 

용례 + 주의점

실제로 사용할 때에 있어서는 큰 어려움이 없었다. 다만, TypeORM등을 같이 쓰다가 삽질하게 되는 경우가 있었다.

 

npm install 로 설치

$ npm i bcrypt
$ npm i -D @types/bcrypt

 

 

모듈을 설치하고 나면 아래와 같이 실제 bcrypt 에서의 compare 메소드를 사용해 패스워드가 일치하는지 체크해 줄 수 있다.

 

 

channels.service.ts

    const isPasswordMatching = await bcrypt.compare(
        password,
        channel.password!,
    );
    if (!isPasswordMatching) {
        throw new BadRequestException('비밀번호가 일치하지 않습니다.');
    }

 

 

(변경전)channels.entity.ts

import { InternalServerErrorException } from '@nestjs/common';
import * as bycrypt from 'bcrypt';
import { IsString } from 'class-validator';
import { BaseEntity } from 'src/common/base-entity';
import { ChannelType } from 'src/common/enum';
import { BeforeInsert, Column, Entity } from 'typeorm';

@Entity()
export class Channel extends BaseEntity {
	
    ...

	@BeforeInsert()
	async hassPassword() {
		try {
			this.password = await bcrypt.hash(this.password, 10);
		} catch (e) {
			console.error(e);
			throw new InternalServerErrorException();
		}
	}
}

 

Entity 에 패스워드를 넣을 때 hashPassword라는 메소드를 거쳐 bcrypt로 해시화 시켜줄 수 있다.

 

근데 최초로 패스워드를 설정해줄 때는 해시화가 잘 되었는데 패스워드를 변경할 때 해시화가 되지 않고 평서문으로 저장되는 문제가 발생했다.

 

블로그에서 나와 비슷한 증상을 가지고 있는 사람을 찾아볼 수 있었다. @BeforeUpdate 어노테이션을 추가해주고 패스워드를 변경해줄 때 .update() 대신 .save() 로 변경해주었다.

 

(변경 후)channels.entity.ts

	@BeforeInsert()
	@BeforeUpdate()
	async hashPassword() {
		if (this.password) {
			try {
				this.password = await bycrypt.hash(this.password, 10);
			} catch (e) {
				console.error(e);
				throw new BadRequestException(
					`비밀번호 암호화에 실패했습니다.`,
				);
			}
		}
	}

 

channel.service.ts

	private async updateChannelPassword(
		channelId: number,
		newPassword: string,
	) 
    
    	...
    
		channel.password = newPassword;
		const updateChannel = await this.channelsRepository.save(channel);
		if (!updateChannel)
			throw DBUpdateFailureException('update channel password failed');
	}
    const channelUser = await this.channelUsersRepository.findOne({
        where: { userId, channelId },
        withDeleted: true,
        order: { createdAt: 'DESC' },
    });
  		...

        const isPasswordMatching = await bycrypt.compare(
            password,
            channel.password!,
        );
        if (!isPasswordMatching) {
            throw new BadRequestException('비밀번호가 일치하지 않습니다.');
        }
    }

입력된 값과 해시된 패스워드가 서로 일치하는지 확인하기위해 bcrypt 내부의 .compare 메소드로 비교할 수 있다.  

 

+ 추가)

📌 다른 작업자가 install하여 package.json에 bycypt가 있는 파일을 pull 받은 상태에서, 패키지 조건을 맞추고자 npm i(nstall) 하고 docker를 올렸더니 해당 오류 발생.

Error: /.../node_modules/bcrypt/build/Release/bcrypt_lib.node: invalid ELF header

dockerfile에 npm i 하는 부분이 있어서 mac os에서 npm i 한 것이 docker linux 환경에 안맞는 모양. <bcrypt compiled on OSX will not quite work on Linux>

 

해결방법: node_module을 삭제 한 뒤, 바로 docker compose up을 실행하면 된다. 빌드하면서 npm i 를 해주기 때문에 linux에 맞는 패키지가 설치되고 node_module 디렉토리가 생성된다. volume이 우리 /BE 와 연결되어있기때문에 컨테이너 환경에 생성된 node_module 이 호스트에 동기화된다.


요약하자면...

1. crypt에서 발전된 bcrypt로 해시화 할 수 있음

2. bcrypt의 메소드의 hash 로 해시화, 그리고 compare을 사용해 비교가능함

3. 비밀번호를 변경할 때 TypeORM의 .update대신 .save 메소드를 사용하자


https://do-dam.tistory.com/158

https://auth0.com/blog/hashing-in-action-understanding-bcrypt/

https://docs.nestjs.com/security/encryption-and-hashing