주민번호 복호화 경로를 찾아 문서로 남긴다 — 앱에 키를 넣지 않아도 된다
환자 태그의 다음 관문. 성별(277건)·나이(204건)가 전부 복호화된 주민번호를 선행 조건으로 삼는다. ## 가장 중요한 발견 복호화가 <b>DB 안에서</b> 일어난다(dtCommonLib.vb:6960). SELECT SUBSTR(:enc,1,7) || CRYPTO_AES256.DEC_AES(SUBSTR(:enc,8,256)) FROM DUAL 바인드 변수이고 키는 오라클 패키지 안에 있다 — 접근 통제가 DB 권한이라는 뜻이고 그것이 있어야 할 자리다. <b>앱은 키를 갖지 않는다.</b> 이 성질을 깨는 이식(키를 상수로 옮기는 등)은 하지 않는다. 앞 7자리는 평문이고 8자리부터만 암호화되어 있다. 병원 SCHILDREN 만 UDF_GETDECRESNUM_damo 로 갈린다(2023-10-14 DAMO 전환). ## 어떤 병원에서 켜지는가 M_DtlMst(HSPCFG/PersonalDataEnc) 가 Y 일 때만 M_DtsMst(HSPCFG/PersonalDataEnc/ResNum) 를 한 번 더 본다(clsCommonLib.vb:5835-5841). N 이면 P_PatInf.PatResNum 이 평문이고, Y 면 그 컬럼은 쓰지 말고 PatResEnc 를 복호화해야 한다. 앞 커밋에서 PAT_주민번호 를 뺀 이유가 정확히 이것이다. ## 복호화 뒤 보정이 더 있다 SRCH·JINJU 는 주민번호에 기관기호가 들어간 경우를 PatBthDay 로 고친다. <b>이 개발 DB 가 SRCH 다 — 살아 있는 갈래다.</b> 외국인은 7번째 자리 0/9 를 PatBthDay 세기에 따라 6/8 또는 5/7 로 바꾼다. 둘 다 순수 문자열 계산이라 표로 고정할 수 있다. ## 성별은 지금 옮길 수 있다 7번째 자리 mod 2(0=F, 1=M). 암호화 여부로 갈라지지만 두 갈래 본문이 같다. 단 PAT_성별 에는 CoiCalCod 가 "24" 로 시작하면 PatSexTyp 을 쓰는 선행 분기가 있고, CoiCalCod 는 P_CoiInf 라 이미 문맥에 들어와 있다. ## 나이는 지금 옮기지 않는다 AgeCheck(clsCommonLib.vb:735)는 7번째 자리로 세기를 정하는데 갈래가 많고 REDCROSS 별도 규칙과 의료급여 전산관리번호 규약이 붙는다. 보험코드까지 결과를 바꾼다. 원본 주석이 <b>1901~1919년생 외국인과 2001년 이후 보호기관 영아는 원리상 구분 불가</b>라고 명시한다(20080821). 절반만 옮기면 종이에 그럴듯한 나이가 찍힌다 — 아무도 의심하지 않으므로 빈칸보다 나쁘다. 사례 표를 먼저 만드는 별도 작업으로 둔다. ## 개인정보 접근 로그 레거시에 C_PdrInf 전용 경로가 있다(clsCommonLib.vb:28279). 주민번호를 복호화하기 시작하면 가장 민감한 필드를 다루는 것이므로 기록 범위를 정해야 한다. 그 표에 쓰는 것은 운영 DB 쓰기라 사용자 승인이 필요하다 — 정하기 전에는 파일 로그에 사실만 남기는 쪽이 안전하다. 코드 변경 없음(문서만). 게이트는 앞 커밋 상태 그대로다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
128aedc34f
commit
34c53e3d69
@@ -0,0 +1,121 @@
|
|||||||
|
# 주민번호 복호화 경로 — 조사 결과
|
||||||
|
|
||||||
|
환자 태그의 다음 관문이다. `PAT_성별`·`PAT_한글성별`(277건)과 `PAT_나이` 계열(204건)이
|
||||||
|
전부 **복호화된 주민번호**를 선행 조건으로 삼는다.
|
||||||
|
|
||||||
|
조사 대상은 읽기 전용 레거시(`C:\MsystechHIS_Ver.2`)이고, 아래 줄 번호는 그 스냅샷 기준이다.
|
||||||
|
|
||||||
|
## 결론 먼저 — 앱에 키를 넣지 않아도 된다
|
||||||
|
|
||||||
|
복호화는 **DB 안에서** 일어난다. 클라이언트는 암호문을 넘기고 평문을 받는다.
|
||||||
|
|
||||||
|
```
|
||||||
|
dtCommonLib.vb:6960 GetDecResNum(psEncResNum)
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 기본
|
||||||
|
SELECT SUBSTR(:psEncResNum,1,7) || CRYPTO_AES256.DEC_AES(SUBSTR(:psEncResNum,8,256)) DecResNum
|
||||||
|
FROM DUAL
|
||||||
|
|
||||||
|
-- 병원 SCHILDREN 만(2023-10-14 DAMO 로 변경)
|
||||||
|
SELECT UDF_GETDECRESNUM_damo(:psEncResNum) DecResNum FROM DUAL
|
||||||
|
```
|
||||||
|
|
||||||
|
바인드 변수를 쓴다. 키는 오라클 패키지 `CRYPTO_AES256` 안에 있고 접근 통제는 **DB 권한**이다 —
|
||||||
|
그것이 있어야 할 자리다. **앱은 키를 갖지 않는다.** 이 성질을 깨는 이식(키를 상수로 옮기는 등)은 하지 않는다.
|
||||||
|
|
||||||
|
앞 7자리는 평문으로 두고 8자리부터만 암호화되어 있다는 점에 주의 —
|
||||||
|
`SUBSTR(...,1,7)` 을 그대로 이어 붙인다.
|
||||||
|
|
||||||
|
## 어떤 병원에서 켜지는가
|
||||||
|
|
||||||
|
```
|
||||||
|
clsCommonLib.vb:4847-4848 HspCfg.PersonalDataEnc / PersonalDataEnc_ResNum
|
||||||
|
clsCommonLib.vb:5835-5841 적재
|
||||||
|
```
|
||||||
|
|
||||||
|
두 단이다.
|
||||||
|
|
||||||
|
1. `M_DtlMst` (DtlTblCod='HSPCFG', DtlCod='PersonalDataEnc') → `DtlCodVal` 의 `=` 앞 조각이 Y/N
|
||||||
|
2. 그것이 `Y` 일 때만 `M_DtsMst` (DtsTabCod='HSPCFG', DtlCod='PersonalDataEnc', DtsCod='ResNum')
|
||||||
|
→ `DtsCodVal` 이 주민번호 개별 Y/N (`GetDtsCodVal`, :7302)
|
||||||
|
|
||||||
|
`N` 이면 `P_PatInf.PatResNum` 이 **평문**이다. `Y` 면 그 컬럼은 쓰지 말고 `PatResEnc` 를 복호화해야 한다.
|
||||||
|
|
||||||
|
> 지금 SheetMe 의 `PatientTagResolver` 가 `PAT_주민번호` 를 뺀 이유가 이것이다.
|
||||||
|
> `PatResNum` 을 그대로 쓰면 암호화 병원에서 **암호문이 종이에 찍힌다**.
|
||||||
|
|
||||||
|
## 복호화 뒤에 붙는 보정 (bzPatientInfo.vb:546-580)
|
||||||
|
|
||||||
|
평문을 얻은 뒤에도 그대로 쓰지 않는다.
|
||||||
|
|
||||||
|
- **SRCH · JINJU**: 주민번호에 기관기호가 들어간 경우가 있다(예 `9750012203265`).
|
||||||
|
`Substring(1,6)` 이 날짜로 해석되지 않으면 `PatBthDay` 뒤 6자리로 앞자리를 갈아 끼운다.
|
||||||
|
**이 개발 DB 가 SRCH 다 — 이 갈래가 살아 있다.**
|
||||||
|
- **외국인 보정**: 7번째 자리가 `0` 또는 `9` 이고 `PatNatCod` 가 비어 있지 않으면,
|
||||||
|
`PatBthDay` 앞 2자리가 `19` 인지에 따라 그 자리를 `6/8`(0인 경우) 또는 `5/7`(9인 경우)로 바꾼다.
|
||||||
|
|
||||||
|
둘 다 `PatBthDay`·`PatNatCod` 만 쓰는 **순수 문자열 계산**이라 그대로 옮기고 표로 고정할 수 있다.
|
||||||
|
|
||||||
|
## 성별 — 지금 옮길 수 있다
|
||||||
|
|
||||||
|
```
|
||||||
|
clsCommonLib.vb:613 GetSexAge(psResNum, ByRef psSex, ByRef psAge)
|
||||||
|
```
|
||||||
|
|
||||||
|
암호화 여부로 갈라지지만 **두 갈래의 본문이 같다**. 성별은 순수 계산이다.
|
||||||
|
|
||||||
|
```
|
||||||
|
7번째 자리 mod 2 == 0 → "F", 아니면 "M"
|
||||||
|
```
|
||||||
|
|
||||||
|
`PAT_한글성별` 은 거기에 F→"여", M→"남" 을 붙인다(bzDataInterface.vb:305-316).
|
||||||
|
|
||||||
|
단 `PAT_성별` 에는 앞 분기가 하나 더 있다 —
|
||||||
|
`CoiCalCod` 가 `"24"` 로 시작하면 주민번호를 보지 않고 `PatSexTyp` 을 쓴다(:283-291).
|
||||||
|
`CoiCalCod` 는 `P_CoiInf` 에 있으므로 이미 문맥에 들어와 있다.
|
||||||
|
|
||||||
|
## 나이 — 별도 작업이 필요하다 (지금 옮기지 말 것)
|
||||||
|
|
||||||
|
```
|
||||||
|
clsCommonLib.vb:735 AgeCheck(psResNum, psToDate, psInsCod)
|
||||||
|
```
|
||||||
|
|
||||||
|
7번째 자리로 세기를 정하는데 갈래가 많고 역사적 예외가 붙어 있다.
|
||||||
|
|
||||||
|
| 7번째 자리 | 처리 |
|
||||||
|
| --- | --- |
|
||||||
|
| 1, 2 | `19` + 주민번호 |
|
||||||
|
| 3, 4 | `20` + 주민번호 |
|
||||||
|
| 5, 6 | 앞 2자리 < 20 이면 2000년대 보호기관 영아, 아니면 1900년대 외국인. **REDCROSS 는 서버 연도와 비교하는 별도 규칙** |
|
||||||
|
| 7, 8 | 의료급여 전산관리번호(행려환자·시설입소자) 규약 |
|
||||||
|
|
||||||
|
`psInsCod`(보험코드)까지 결과를 바꾸고, 1901~1919년생 외국인과 2001년 이후 보호기관 영아는
|
||||||
|
**원리상 구분이 불가능하다**고 원본 주석이 명시한다(20080821).
|
||||||
|
|
||||||
|
절반만 옮기면 종이에 그럴듯한 나이가 찍힌다. 그건 빈칸보다 나쁘다 —
|
||||||
|
아무도 의심하지 않기 때문이다. 사례 표를 먼저 만들고 한 갈래씩 고정하는 별도 작업으로 둔다.
|
||||||
|
|
||||||
|
## 개인정보 접근 로그
|
||||||
|
|
||||||
|
레거시에 전용 기록 경로가 있다.
|
||||||
|
|
||||||
|
```
|
||||||
|
clsCommonLib.vb:28279 WriteLogPersonalDataAccess(chtNum, prcNam, dataTyp, comNum) → C_PdrInf
|
||||||
|
```
|
||||||
|
|
||||||
|
SheetMe 는 지금 파일 로그(`AppLog.Audit`)에 `[환자 미리보기] 사용자=… 내원=…` 만 남긴다
|
||||||
|
(환자 이름은 남기지 않는다). 주민번호를 복호화하기 시작하면 **가장 민감한 필드**를 다루는 것이므로
|
||||||
|
기록 범위를 정해야 한다. `C_PdrInf` 에 쓰는 것은 운영 DB 쓰기이므로 사용자 승인이 필요하다 —
|
||||||
|
정하기 전에는 파일 로그에 "주민번호 복호화" 사실만 남기는 쪽이 안전하다.
|
||||||
|
|
||||||
|
## 작업 순서 제안
|
||||||
|
|
||||||
|
1. `Core`: `ResidentNumber` 순수 함수 — 보정(SRCH/JINJU·외국인) + 성별. 사례 표로 고정.
|
||||||
|
2. `Data`: 암호화 플래그 2단 조회 + `DUAL` 복호화(바인드). 병원 갈래(SCHILDREN) 포함.
|
||||||
|
3. `Designer`: `PAT_주민번호`·`PAT_주민번호_Dash`·`PAT_성별`·`PAT_한글성별` 매핑.
|
||||||
|
`PAT_성별` 은 `CoiCalCod` 선행 분기를 함께.
|
||||||
|
4. 게이트: 실DB 로 "암호화 병원에서 평문이 나오는가"를 확인.
|
||||||
|
**값은 리포트에 쓰지 않는다** — 길이와 형식만 적는다.
|
||||||
|
5. 나이는 별도 작업.
|
||||||
Reference in New Issue
Block a user