> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MySQL의 비밀번호 해싱

> MySQL 내장 해시 함수가 비밀번호에 안전하지 않은 이유와, VillageSQL에서 PBKDF2를 사용해 비밀번호 해시를 올바르게 저장하는 방법입니다.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

MySQL에는 `MD5()`와 `SHA2()`가 있습니다. 개발자들은 비밀번호를 해싱할 때 이 함수들에 손을 뻗습니다. 그것은 실수입니다 — 이 함수들은 빠르도록 설계되었는데, 그것이 바로 비밀번호 저장에는 정반대로 잘못된 특성입니다.

## 빠른 해시가 비밀번호에 잘못된 이유

MD5와 SHA-256 같은 암호화 해시 함수는 속도에 최적화되어 있습니다. 최신 GPU는 초당 수십억 개의 SHA-256 해시를 계산할 수 있습니다. 체크섬과 데이터 무결성에는 유용합니다. 비밀번호에서는, 해시된 비밀번호 테이블을 손에 넣은 공격자가 몇 시간 안에 무차별 대입으로 깰 수 있다는 뜻입니다.

비밀번호 해싱 알고리즘 — bcrypt, Argon2, PBKDF2 — 은 의도적으로 느립니다. 비밀번호 하나를 확인하는 데 수십 밀리초가 걸리도록 설계되어 있어, 로그인에는 문제가 없지만 무차별 대입 공격은 비현실적으로 만듭니다.

```sql theme={null}
-- Don't do this
INSERT INTO users (email, password_hash)
VALUES ('user@example.com', SHA2('mypassword', 256));
```

## 표준 답: 애플리케이션 코드에서 해싱

대부분의 MySQL 애플리케이션은 애플리케이션 계층에서 해싱하여 이것을 올바르게 처리합니다 — PHP, Python, Node.js, 또는 앱을 실행하는 어떤 언어에서든 `bcrypt`를 사용합니다. 데이터베이스는 해시된 값만 봅니다.

이 방식은 동작하지만 분리가 생깁니다: 해싱 로직은 애플리케이션 코드에 있고 데이터는 데이터베이스에 있습니다. 마이그레이션, 대량 작업, 데이터 가져오기는 올바르게 해싱하려면 애플리케이션을 거쳐야 하거나, 여러 코드베이스에 해싱 로직을 복제해야 합니다.

## VillageSQL에서: SQL에서 직접 PBKDF2

VillageSQL의 `vsql_crypto` 확장은 `crypt()`와 `gen_salt()`를 추가합니다 — PostgreSQL의 pgcrypto를 본뜬 PBKDF2 기반 비밀번호 해싱 API입니다. 애플리케이션 코드를 거치지 않고 SQL에서 비밀번호를 해싱하고 검증할 수 있습니다.

```sql theme={null}
INSTALL EXTENSION vsql_crypto;
```

### 비밀번호 저장하기

```sql theme={null}
CREATE TABLE users (
    user_id       INT PRIMARY KEY AUTO_INCREMENT,
    email         VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL
);

-- gen_salt generates a random salt; the second argument is iteration count
-- Higher iterations = slower hash = harder to brute-force
INSERT INTO users (email, password_hash)
VALUES ('user@example.com', crypt('mypassword', gen_salt('pbkdf2-sha256', 10000)));
```

`gen_salt`는 PBKDF2 계열만 다룹니다 — `pbkdf2-sha256`과 `pbkdf2-sha512`, 그리고 더 짧은 별칭인 `pbkdf2`, `sha256`, `sha512`입니다. pgcrypto의 `bf`(bcrypt), `md5`, `des` 타입은 지원하지 않습니다: 그 이름에 대해 `gen_salt`는 NULL을 반환합니다.

### 비밀번호 검증하기

`crypt()`는 저장된 해시에서 솔트와 매개변수를 자동으로 추출합니다 — 저장된 해시를 두 번째 인수로 넘기면 됩니다:

```sql theme={null}
-- Returns TRUE if the password matches, FALSE otherwise
SELECT crypt('mypassword', password_hash) = password_hash AS is_valid
FROM users
WHERE email = 'user@example.com';
```

### 반복 횟수 선택하기

반복 횟수는 해시 계산에 걸리는 시간을 제어합니다. 반복이 많을수록 = 느릴수록 = 무차별 대입이 어렵지만, 로그인도 느려집니다.

| 반복 횟수 | 해시당 대략적인 시간 | 권장 사항 |
| :- | :- | :- |
| 1,000 | \< 1ms | 너무 빠름 — 비밀번호에 사용하지 마세요 |
| 10,000 | \~5ms | 프로덕션 최소값 |
| 100,000 | \~50ms | 더 좋음 — PBKDF2-SHA256에 대한 NIST 권장값 |
| 600,000 | \~300ms | OWASP 2023 권장값 |

10,000에서 시작해 하드웨어가 허용하는 만큼 늘리세요. 로그인 엔드포인트는 100ms를 견딜 수 있지만, 수십억 번 추측을 시도하는 공격자는 견딜 수 없습니다.

```sql theme={null}
-- More secure: 100,000 iterations
INSERT INTO users (email, password_hash)
VALUES ('user@example.com', crypt('mypassword', gen_salt('pbkdf2-sha256', 100000)));
```

## SQL과 애플리케이션 코드 중 어디서 해싱할지

SQL에서 해싱하는 것이 항상 올바른 선택은 아닙니다. 다음과 같을 때 사용하세요:

* 앱을 거치는 것이 현실적이지 않은 대량 삽입이나 마이그레이션을 수행할 때
* 해싱 로직을 데이터베이스 계층에 집중시키고 싶을 때
* SQL 기반 관리 도구나 시드 스크립트를 만들 때

다음과 같을 때는 애플리케이션 코드에서 해싱하세요:

* 애플리케이션 프레임워크에 이미 잘 검증된 bcrypt/Argon2 라이브러리가 있을 때
* 암호화 생태계가 성숙한 언어를 사용할 때(대부분 그렇습니다)
* 스키마 마이그레이션 없이 해싱 알고리즘을 교체하고 싶을 때

두 접근 방식 모두 안전한 해시를 만듭니다. 핵심은 느린 알고리즘 — PBKDF2, bcrypt, 또는 Argon2 — 을 사용하는 것이지, 날것의 SHA2가 아닙니다.

## 자주 묻는 질문

#### PBKDF2는 bcrypt만큼 강한가요?

같은 반복 횟수에서는 bcrypt가 메모리 집약적(GPU에서 병렬화하기 어려움)이라 약간 우위에 있습니다. 높은 반복 횟수(100,000 이상)의 PBKDF2도 여전히 강력하고 널리 배포되어 있습니다. 그 차이는 적대적 시나리오에서 중요합니다; 둘 다 SHA2보다 훨씬 낫습니다.

#### 기존 SHA2 해시를 마이그레이션할 수 있나요?

예. `needs_rehash` 플래그를 추가하세요. 로그인에 성공하면 해시가 오래된 SHA2 형식인지 확인하고 `crypt()`로 다시 해싱하세요. 활성 사용자가 모두 로그인한 뒤에 오래된 해시를 제거하세요.

#### PostgreSQL의 기존 bcrypt 해시를 마이그레이션할 수 있나요?

직접은 안 됩니다. `crypt()`는 PBKDF2 형식이 아닌 솔트나 해시에 대해 NULL을 반환하므로, pgcrypto의 bcrypt 해시는 여기서 검증할 수 없습니다. 기존 열에 그대로 두고, 위의 SHA2 해시와 같은 방식으로 각 사용자의 다음 로그인 성공 시 다시 해싱하세요. [PostgreSQL에서 MySQL로 마이그레이션 가이드](https://villagesql.com/docs/ko/guides/postgres-to-mysql.md)에 전체 확장 매핑이 있습니다.

#### 저장된 해시에 솔트가 포함되나요?

예. `crypt()`의 출력은 알고리즘, 반복 횟수, 솔트, 해시를 하나의 문자열에 인코딩합니다. 솔트를 따로 관리하지 않습니다.

## 문제 해결

| 문제 | 해결 방법 |
| :- | :- |
| `FUNCTION <database>.crypt does not exist` | `INSTALL EXTENSION vsql_crypto`를 실행 |
| 검증이 항상 FALSE를 반환 | `crypt()`의 두 번째 인수로 새 솔트가 아니라 저장된 해시를 넘기고 있는지 확인 |
| 해시 출력이 NULL | `crypt()`의 두 인수 모두 NULL이 아닌지, 솔트 인수가 PBKDF2 솔트 또는 해시인지 확인 — `crypt()`는 bcrypt, `md5`, `des` 값에 대해 NULL을 반환 |

## 함께 보기

* [MySQL의 데이터 해싱](https://villagesql.com/docs/ko/guides/hashing-data.md) — 무결성 검사와 중복 제거를 위한 비밀번호 외 해싱
* [MySQL의 대칭 암호화](https://villagesql.com/docs/ko/guides/symmetric-encryption.md) — (해싱이 아닌) 암호화가 올바른 선택일 때
* [MySQL의 랜덤 데이터 생성](https://villagesql.com/docs/ko/guides/random-data-mysql.md) — 암호학적으로 안전한 솔트와 토큰 생성


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.