
인터넷이 일상화된 현대 사회에서 보안은 가장 중요한 요소 중 하나다. 특히, 비밀번호 관리는 개인정보 보호의 핵심이다. 만약 어떤 서비스에서 비밀번호를 원본 그대로 저장한다면, 데이터베이스가 해킹당할 경우 사용자의 계정 정보가 그대로 유출될 위험이 있다. 이를 방지하기 위해 단방향 해시 함수(One-Way Hash Function)를 사용한다. 대표적인 알고리즘인 SHA-256은 입력값을 복호화할 수 없는 고유한 해시 값으로 변환하여 보관한다. 하지만, 동일한 입력값은 항상 같은 해시 값을 가지기 때문에 레인보우 테이블 공격에 취약할 수 있다. 이를 보완하기 위해 솔트(Salt)와 키 스트레칭(Key Stretching) 기법이 사용된다.
이번 포스팅에서는 SHA-256 해싱, 솔트 적용, 환경변수 설정을 통해 안전한 비밀번호 저장 방법을 배워보겠다.
*레인보우 테이블 공격: 동일한 비밀번호는 동일한 해시 값을 가지므로, 해커는 미리 계산된 해시 값과 비교하여 원본 비밀번호를 유추할 수 있다.
암호화(Encryption)는 데이터를 보호하기 위해 특정 알고리즘을 사용해 변환하는 과정이다.
암호화된 데이터는 원본 데이터를 알아볼 수 없는 형태로 변경되어 저장된다. 암호화된 데이터를 복호화하여 원래 상태로 되돌릴수 있으며, 이는 양방향 방식이라고 한다. 예를들어 온라인 결제 시스템에서 신용카드 정보를 암호화하여 해커가 원본 데이터를 알아낼 수 없도록 하거나 기업에서 기밀 문서를 암호화를 통해 보호하는 등 다양한 보안 시스템이 존재한다.
해시 함수(Hash Function)는 임의의 데이터를 고정된 길이의 고유한 값으로 변환하는 알고리즘이다.
해시함수는 암호화와 달리 단방향(one-way)로 변환되며, 복호화가 불가능하다. 또한, 동일한 입력값은 항상 동일한 해시값을 생성하게되는데, 다음과 같은 특징들이 있다.
예를들어 아래 이미지와 같이 해시 생성기를 통해 "안녕"이라는 문구를 출력하면 다음과 같이 다양한 해시 함수에서 값이 출력된다.

여기서 "안녕"이라는 문구에서 .하나만 추가를 해도 완전히 다른 해시 함수가 출력되는 것을 알 수 있다.

그런데 서론에서 언급한 바와 같이 동일한 입력값은 동일한 해시값만 출력하기 때문에 경험에 의한 추론이 가능하여 해커에게서 보안이 취약하다는 문제점이 있다. 예를들어, 어떤 사이트에서 회원가입시에 입력한 비밀번호의 해시값을 저장할 경우, 해커가 그 해시값을 유추하여 유저의 정보를 알아낼 수 있다는 것이다. 이를 방지하기 위해 다양한 알고리즘을 결합하여 보안을 강화하고는 하는데, 이어지는 챕터들에서 알아보자.
SHA-256(Secure Hash Algorithm 256-bit)은 256비트(32바이트) 길이의 해시 값을 생성하는 암호학적 해시 함수이다.
SHA-256은 보안성이 높아 비밀번호를 저장하거나 블록체인, 디지털 서명등에 사용된다. 하지만 앞선 챕터에서 설명한 바와 같이 SHA-256 알고리즘 또한 동일한 입력값에 대해 항상 같은 해시값을 생성하게 되어 레인보우 테이블 공격이나 브루트포스 공격으로부터 방어하기 위해 솔트(salt)와 키 스트레칭(key stretching)을 함께 사용해야한다.
솔트(Salt)는 해시 함수에 추가되는 무작위 값으로, 같은 비밀번호라도 서로 다른 해시 값을 생성하도록 만드는 기법이다. 단어 뜻 그대로 원문에 임의의 문자열을 붙인다는 의미의 소금친다(salting) 는 것이다.

아래의 예제는 솔트를 무작위로 생성하여 원본 비밀번호와 조합하여 해싱하는 코드이다.
const crypto = require('crypto');
console.log("dd")
// 원본 비밀번호
const password = "admin123";
// 솔트 생성 (무작위 문자열)
const salt = crypto.randomBytes(16).toString('hex');
// 솔트를 포함한 비밀번호 해싱
const hash = crypto.createHash("sha256").update(password + salt).digest('hex');
console.log("Salt:", salt); //> 6212f2d1c7bec7847a4383153fc530c6
console.log("Hashed Password:", hash); //> eb3787c4176b07c106718b7cfed0258ebfbf8ed6a8539ae02056d155618f45ca
솔트 생성시에 toString('hex')는 createHash()로 해싱한 값을 16진수 문자열로 변환하고, 비밀번호 해싱시에 digest('hex')는 Buffer 데이터를 16진수로 변환한다.
이렇게 솔트 알고리즘을 비밀번호 해싱에 사용하게 되면 같은 비밀번호를 사용하는 사용자 중 한명이 해커로부터 공격을 받게 되어도 다른 사용자는 다른 해시값을 가지기 때문에 사용자 정보를 유추하기 어려워진다.
키 스트레칭(Key Stretching)은 해싱을 여러 번 반복하여 해커의 무작위 대입 공격(Brute Force Attack)을 어렵게 만드는 기법이다.
SHA-256 같은 해시 함수는 빠른 연산이 장점이지만, 이 때문에 해커가 초당 수억 개의 비밀번호 조합을 시도할 수 있다. 이를 방어하기 위해 의도적으로 연산 속도를 늦추는 방법이 키 스트레칭이다.

아래의 예제는 pbkdf2Sync를 사용하여 100,000번 키 스트레칭을 통해 비밀번호를 암호화한 코드이다.
const crypto = require('crypto');
// 원본 비밀번호
const password = "admin123";
// 키 스트레칭 적용 (솔트 적용 x )
const derivedKey = crypto.pbkdf2Sync(password, '', 100000, 64, 'sha256');
console.log("Stretched Hashed Password:", derivedKey.toString('hex'));
//> Stretched Hashed Password: e737966066900353dd8561f75f160efc52830abadb2e0f333c4a1e110d6af8cd723de1cd284e5d60f7a6ac44004ed37ba83f8d3772d67c517af4f66f47b7fa93
앞서 설명한 솔트와 키 스트레칭 기법을 조합하여 SHA-256 해싱을 하여 비밀번호를 저장하게 된다면 비로소 해커로부터 보다 보안이 강화된 개인정보 보호가 가능할 것이다.

아래 예제는 솔트와 키 스트레칭 기법을 조합하여 SHA-256 해싱을 하는 코드이다.
const crypto = require('crypto');
// 원본 비밀번호
const password = "admin123";
// 솔트 생성 (16바이트의 랜덤 값)
const salt = crypto.randomBytes(16).toString('hex');
// 키 스트레칭 적용 (PBKDF2)
const derivedKey = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256');
console.log("Salt:", salt); //> Salt: e450bd20445a88f3b9667d0daeae630c
console.log("Stretched Hashed Password:", derivedKey.toString('hex')); //> Stretched Hashed Password: f3c69369e6ad74ea9c89655a64acd1c663ead0bfaff1cc37f9c037e41209b592b4db760aa5c15a91db029277c6fc10a307757167c874ac62c07431f43ea10292
이번 포스팅에서는 솔트, 키 스트레칭과 더불어 SHA-256을 활용한 비밀번호 해싱을 보다 안전하게 저장하는 방법에 대해 다루었다.
- SHA-256 해시 함수를 사용하여 비밀번호를 암호화하지만, 동일한 입력값은 항상 같은 해시 값을 가지므로 레인보우 테이블 공격에 취약할 수 있다. 이를 방어하기 위해 솔트(Salt)와 키 스트레칭(Key Stretching) 기법을 적용해야 한다.
- 솔트(Salt)는 비밀번호에 무작위 값을 추가하여 같은 비밀번호라도 다른 해시 값이 생성되도록 만들며, 레인보우 테이블 공격을 방어할 수 있다.
- 키 스트레칭(Key Stretching)은 해싱 연산을 여러 번 반복하여 해커가 무작위 대입(Brute Force Attack)을 시도하는 속도를 늦춰 보안을 강화한다.
- 솔트 + 키 스트레칭을 적용한 SHA-256 해싱을 사용하면, 비밀번호를 보다 안전하게 보호할 수 있으며, 해커가 해싱된 데이터를 유추하는 것을 어렵게 만들 수 있다.
Reference