docs: add project docs and test updates
This commit is contained in:
152
docs/기본 개발계획/29-비밀번호_및_개인정보_암호화_기술.md
Normal file
152
docs/기본 개발계획/29-비밀번호_및_개인정보_암호화_기술.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# 비밀번호 및 개인정보 암호화 기술 정리
|
||||
|
||||
본 문서는 **종량제(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`) 기준 정리
|
||||
Reference in New Issue
Block a user