Files
jongryangje/docs/기본 개발계획/29-비밀번호_및_개인정보_암호화_기술.md
2026-04-08 00:23:55 +09:00

153 lines
7.4 KiB
Markdown

# 비밀번호 및 개인정보 암호화 기술 정리
본 문서는 **종량제(jongryangje) 프로젝트에 실제 적용된** 비밀번호 처리와 개인정보(PII) 저장 방식을, 사용 기술·동작 원리·설정·코드 위치 기준으로 정리한다.
관련 코드·설정:
- `app/Controllers/Auth.php` — 회원가입 시 비밀번호 해시, 로그인 시 검증
- `app/Controllers/Admin/User.php` — 관리자 회원 등록·수정 시 비밀번호 해시
- `app/Helpers/pii_encryption_helper.php` — 이메일·연락처 암·복호화
- `app/Config/Encryption.php` — CodeIgniter 4 암호화 설정
---
## 1. 암호화와 해시의 구분 (이 프로젝트에서의 역할)
| 구분 | 대상 데이터 | 목적 | 복원 가능 여부 |
|------|-------------|------|----------------|
| **해시 (일방향)** | 로그인 비밀번호 | “같은 비밀번호인지”만 검증 | **불가** (설계상 원문 복구 불가) |
| **대칭키 암호화** | 이메일, 연락처 등 | DB 유출 시 평문 노출 완화, 화면 표시 시 복호화 | **가능** (암호화 키 보유 시) |
비밀번호는 **암호화가 아니라 해시**로 저장하는 것이 표준이다. “복호화해서 비교”하지 않고, `password_verify`로만 비교한다.
---
## 2. 비밀번호: PHP `password_hash` / `password_verify`
### 2.1 사용 API
- **저장(회원가입·비밀번호 변경 시)**
`password_hash(평문비밀번호, PASSWORD_DEFAULT)`
- **검증(로그인 시)**
`password_verify(입력평문, DB에_저장된_해시문자열)`
### 2.2 `PASSWORD_DEFAULT`의 의미
- PHP 버전에 따라 **권장 알고리즘**이 달라질 수 있으나, 일반적으로 **bcrypt** 기반 해시를 사용한다.
- 해시 문자열 내부에 **알고리즘 식별자·비용(cost)·salt** 등이 **한 문자열에 포함**되어 저장된다.
- 따라서 같은 비밀번호로 여러 번 `password_hash`를 호출해도 **매번 다른 해시**가 나올 수 있으며, `password_verify`는 저장된 해시 문자열을 해석해 올바르게 비교한다.
### 2.3 보안 관점에서의 특징
- DB가 유출되어도, 공격자가 해시만 가지고 **원 비밀번호를 바로 읽어낼 수는 없다** (무차별 대입·레인보우 테이블 등 별도 공격에는 여전히 취약할 수 있으므로 비밀번호 정책·2FA 등은 별도 검토).
- 애플리케이션은 **평문 비밀번호를 DB에 저장하지 않는다**.
### 2.4 프로젝트 내 호출 위치
| 위치 | 용도 |
|------|------|
| `Auth::register()` | 가입 시 `mb_passwd`에 해시 저장 |
| `Auth::login()` | `password_verify`로 로그인 검증 |
| `Admin\User::store()` | 관리자가 등록한 회원 비밀번호 해시 |
| `Admin\User::update()` | 비밀번호 변경 시에만 새 해시 저장 |
---
## 3. 개인정보(PII): CodeIgniter 4 Encrypter + 커스텀 헬퍼
### 3.1 적용 필드
`app/Helpers/pii_encryption_helper.php` 상수 `PII_ENCRYPTED_FIELDS` 기준:
- `member.mb_email`
- `member.mb_phone`
`mb_id`, `mb_name` 등은 **현재 코드 경로상 평문 저장**이다. (추가 암호화는 별도 설계·마이그레이션 필요.)
### 3.2 설정: `app/Config/Encryption.php`
| 항목 | 값 | 설명 |
|------|-----|------|
| `driver` | `OpenSSL` | OpenSSL 확장 기반 암호화 핸들러 사용 |
| `cipher` | `AES-256-CTR` | AES 블록암호, 256비트 키, **CTR** 모드 |
| `digest` | `SHA512` | 프레임워크 내부에서 무결성·키 유도 등에 사용되는 다이제스트 설정 (OpenSSL 핸들러와 연동) |
| `rawData` | `true` | 암호문을 raw 바이너리로 다룸 (이후 헬퍼에서 base64로 한 번 더 감쌈) |
| `key` | `.env``encryption.key` | **64자리 16진수(hex)** 문자열만 유효로 인식 후 `hex2bin`으로 바이너리 키로 변환. 형식이 맞지 않으면 **빈 문자열**이 되어 암호화가 비활성화된다. |
키 로드 로직 요약:
- `env('encryption.key')` → 길이 64이고 모두 16진수인 경우에만 `hex2bin` 적용.
- 그 외는 `$key === ''` 로 간주되어 **암호화 미적용 분기**로 이어진다.
### 3.3 AES-256-CTR (개념)
- **AES**: 대칭키 블록암호. 같은 키로 암호화·복호화한다.
- **256**: 키 길이(비트). 설정·키 생성 시 이 길이에 맞춰야 한다.
- **CTR (Counter)**: 블록 모드의 하나. 스트림 암호에 가깝게 동작하며, **같은 키·같은 nonce/IV 재사용**만 피하면 안전하게 쓰는 패턴이 널리 쓰인다. (실제 IV/nonce 처리는 CodeIgniter Encrypter 구현에 따름.)
### 3.4 애플리케이션 레이어: `pii_encrypt` / `pii_decrypt`
동작 요약:
1. 값이 비어 있으면 빈 문자열 반환.
2. `config('Encryption')->key`가 비어 있으면 **평문 그대로 반환** (키 미설정·개발 편의·기존 데이터 호환).
3. 키가 있으면 `service('encrypter')->encrypt($value)` 호출 후, DB 저장용으로
**`ENC:` + base64_encode(암호문 바이너리)** 형태로 만든다.
4. 복호화 시 `ENC:` 접두사가 없으면 **레거시 평문**으로 간주해 그대로 반환.
5. 예외 발생 시 안전하게 **원 입력값을 되돌리는** 폴백이 들어 있다 (운영에서는 로깅 정책 별도 검토 권장).
이렇게 하면:
- DB 덤프를 보더라도 `ENC:` 로 시작하는 필드는 **즉시 읽을 수 있는 평문이 아니다**.
- 복호화에는 **애플리케이션이 읽는 동일한 `encryption.key`** 가 필요하다.
### 3.5 키 로테이션(설정상 지원)
`Encryption` 설정에 `previousKeys` 개념이 문서화되어 있다. 키 교체 시 구 데이터 복호화를 위해 **이전 키 목록**을 두는 용도이며, 실제 운영 적용 여부는 `.env` 및 CI4 버전 문서와 맞춰 설정해야 한다.
---
## 4. 데이터 흐름 요약
### 4.1 회원가입 (`Auth::register`)
1. 비밀번호 → `password_hash``member.mb_passwd`
2. 이메일·연락처 → `pii_encrypt``member.mb_email`, `member.mb_phone` (키 있을 때만 암호문)
### 4.2 로그인 (`Auth::login`)
1. 아이디로 `member` 조회
2. `password_verify(입력비밀번호, mb_passwd)`
3. 승인 상태 등 추가 정책 후 세션 설정
### 4.3 관리자 회원 목록·수정 (`Admin\User`)
- 목록·수정 폼 표시 전 `pii_decrypt`로 이메일·연락처 복호화
- 저장 시 다시 `pii_encrypt`
---
## 5. 운영·보안 체크리스트
- [ ] **`.env``encryption.key` 설정**
- 32바이트 난수의 hex 표현 = **64자리 hex** (프로젝트 주석 예: `php -r "echo bin2hex(random_bytes(32));"`)
- [ ] `.env`**저장소에 커밋하지 않음** (`.env.example`에는 키 없이 변수명만)
- [ ] 백업·로그에 **복호화된 PII**가 불필요하게 남지 않도록 주의
- [ ] 비밀번호는 **절대 복호화·로그 출력하지 않음**
- [ ] 키 분실 시: 기존 `ENC:` 데이터는 **복구 불가**에 가깝다 → 키 관리 절차 필요
---
## 6. 참고 문서
- 동일 레포 내 설계 방향: `docs/기본 개발계획/19-db-personal-data-encryption.md`
- PHP: [password_hash](https://www.php.net/manual/en/function.password-hash.php), [password_verify](https://www.php.net/manual/en/function.password-verify.php)
- CodeIgniter 4: [Encryption 서비스](https://codeigniter.com/user_guide/libraries/encryption.html)
---
## 7. 변경 이력
- 최초 작성: 프로젝트 코드(`Auth`, `User`, `pii_encryption_helper`, `Config\Encryption`) 기준 정리