보안 적합성 검사
-국가 보안적합성 검증 체계의 이해
오늘의 목표
이름이 비슷한 세 가지 - 구별하기
- 보안기능 확인서
- KCMVP
- CC 인증
보안적합성 검증
국가/공공기관이 IT 제품을 도입하기 전, 안전성을 검증하는 제도
- 운용 주체: 국가정보원
- 도입 전 검증 → 취역점 보완 → 도입 - 공공 조달의 관문 역할
최근 편입 사례
영상정보처리기기(2024.4.1)
- TCP/IP로 연결되는 카메라 = 검증 대상
- SDI/아날로그 전송 카메라 = 대상 제외
- 기술 요구사항: 2024.4.1. V3.0—현행 기준
국가용 보안 요구사항
- 칩입차단(4) - FW/웹 방화벽 등
- 칩입방지(2) - IPS/무선 IPS
- 구간보안(4) - VPN/NAC 등
- 전송자료보안(4) - 보안/USB 등
- 보안관리(6) - 패치관리 등
- 가상화(1) - 가상화 관리
- 엔드포인트보안(5) - 안티바이러스 등
- 네트워크 장비(3) - 스위치/SDN
- 양자암호통신(3) - QKD 등
- 영상정보처리기기(2) - IP 카메라/영상 저장
공통 보안 요구 사항
서버 공통 9대 분류
- 식별 및 인증
- 보안관리
- 데이터 보호
- 자체보호
- 업데이트 보호
- 안전한 세션 관리
- 감사기록
- 암호 지원
- 취약성 대응
보안 기능 시험제도(VSFT)

역할 분담표
| 주제 | 이름 | 하는 일정 |
| 정책 기관 | NIS(국가정보원) | 검증 정책 수립, 시험기관 지정 승인, 확인서 효력 관리 |
| 검증 기관 | NSR(국가보안기술연구소) | 시험 가이드 제공, 시험결과 검증, 확인서 발급 승인 |
| 시험 기관 | TTA(영상기기분야) | 보안 기능 시험 수행, 보안 기능 확인서 발급 |
| 신청 기관 | 제조 업체 | 시험 신청, 제출문서 작성, 취약점 개선 |
End-to-End 절차

보안 기능 확인서
- 시험기준 만족을 확인하여, 검증기관 승인하에 시험기관이 발급하는 증서
- ‘발급일까지 알려진 취약점 제거’를 확인
- 발급 이후의 안전까지 보장하는 것은 아님
- 도입기관은 이 확인서로 제품의 검증 상태를 봄
제출 문서
| 문서 | 내용 |
| 제품설명서 | 제품 개요/구성 |
| 보안기능 구현명세서 | 보안기능/모든 인터페이스/모든 계정 |
| 운용설명서 | 설치/운용 방법 |
| 시험결과서 | 시험 결과 |
| 취약점 개선내역서 | 발견된 취약점의 조치 내역 |
KCMVP
Korea Cryptographic Module Validation Program — 암호모듈 검증 제도
- 암호모듈이 규격대로 구현됐는지를 검증
- 결과물 = ‘검증필 암호모듈’ — 검증필 목록에 등재됨
- NIS가 안전성을 확인하고 공지
암호모듈 경계
- 알고리즘 구현
- 키 관리, 난수 생성
- 자가시험(KAT/무결성)
보안 수준
| 수준 | 요구 수준 |
| Level 1 | 기본 — SW 중심 기본 구현 검증 |
| Level 2 | 변조 흔적/ 역할 기반 통제 |
| Level 3 | 물리 침해 대응/ 키 접근 보호 |
| Level 4 | 환경 이상(전압,온도) 대응 |
보안요구 영역
- 명세
- 포트/인터페이스
- 역할/서비스 인증
- 유한상태모델
- 물리보안
- 운영환경
- 키 관리
- 자가시험
- 설계보증
KCMVP 검증 절차
- 신청
- 시험기관 시험
- NSR 검토
- 목록 등재
목록에서 반드시 읽을 다섯 가지
- 모듈명/버전
- 알고리즘
- 보안 수준
- 유효 기간
- 운영환경
검증 대상 암호 알고리즘
- 블록 암호
- SEED · ARIA · HIGHT · LEA
- 해시
- SHA-2 (SHA-224 이상) · LSH
- 메시지 인증
- HMAC
- 공개키·서명
- RSA · KCDSA · ECDSA
- 키설정
- 키 교환 · 키 유도
- 난수
- DRBG
국산 검증필
- 주권
- 국가 기밀을 지키는 암호
- 검증 주체가 국내에 있어야 함
- 검증 가능성
- 설계 근거와 시험 기준을 국가가 직접 확인
- 국제 표준 등재
- SEED·ARIA·HIGHT:
ISO/IEC 18033-3
LEA: ISO/IEC 29192-2
- SEED·ARIA·HIGHT:
범용 블록 암호
SEED
| 개발 | KISA · 1999 |
| 블록 | 128비트 |
| 표준 | ISO/IEC 18033-3 |
| 자리 | 금융·공공에서 오래 사용 |
ARIR
| 개발 | 학·연·관 공동 (Academy·Research·Agency) |
| 블록/키 | 128비트 · 키 128/192/256 |
| 표준 | KS 표준 |
| 특징 | involution 구조 — 암·복호 대칭 |
경량 블록 암호
HIGHT
| 블록 | 64비트 — 경량 |
| 무대 | RFID · 센서 · 저전력 |
| 표준 | ISO/IEC 18033-3 |
| 특징 | 자원 제약 장치용 설계 |
LEA
| 개발 | NSR · 2013 |
| 블록 | 128비트—SW 고속 |
| 표준 | ISO/IEC 29192-2 (경량암호) |
| 무대 | IoT · 고속 SW 처리 |
국산 블록 암호 - 비교
| 알고리즘 | 블록 크기 | 성격/용도 | 표준화 |
| SEED | 128비트 | 범용 — 금융·공공의 초기 표준 | ISO/IEC 18033-3 |
| ARIA | 128비트 | 범용 — 키 128/192/256, involution 구조 | KS 표준 |
| HIGHT | 64비트 | 경량 — RFID·센서·저전력 | ISO/IEC 18033-3 |
| LEA | 128비트 | 경량·SW 고속 — IoT (NSR 2013) | ISO/IEC 29192-2 |
보안 요구 사항 - 공통의 뼈대
- 식별 및 인증
- 보안관리
- 데이터 보호
- 자체 보호
- 업데이트 보호
- 안전한 세션 관리
- 감사기록
- 암호 지원
- 취약성 대응
식별/인증과 보안 관리 - 패스워드
| 구분 | 규칙 |
| 준수 | 9자리 이상 |
| 준수 | 숫자·대문자·소문자·특수문자 각 1자 이상 |
| 금지 | 사용자 계정(ID)과 동일 |
| 금지 | 동일 문자·숫자 연속 반복 |
| 금지 | 키보드·순차 나열 (4자 이상) |
| 금지 | 직전 패스워드 재사용 |
데이터 보호와 자체 보호
- 전송
- 관리 접속도 HTTPS(TLS) 전용
- 저장
- 패스워드는 bcrypt 일방향 해시(salt), 영상 키는 DEK→KEK 구조
- 자체 시험
- 구동 시 암호·TLS 자체시험, 실패하면 fail-closed 종료
- 무결성
- 설정·실행파일을 SHA-256으로 검증 (실행 시 + 주기적)
세션 관리와 감사 기록
- 감사기록 생성 — 로그인·잠금·설정변경·세션종료·중복접속 차단 등
- 기록 항목 — 발생일시 · 사건유형 · 주체 · 결과 (인증정보·암호키는 기록 금지)
- 보호 — 감사기록은 변경·삭제 불가, 조회는 인가된 관리자만
암호 지원
| 항목 | 요구 사항 |
| 암호 통신 | HTTPS — TLS 1.2 이상(RFC 5246 이상) · 원격관리는 SSH V2 |
| 알고리즘 | 보안강도 112bit 이상 · 검증대상 알고리즘 (9.1.1) |
| 키 생성 | PBKDF2 — salt 128bit 이상 · 난수발생기 생성 |
| 키 저장 | DEK를 KEK로 암호화 저장 · 암호키 하드코딩 금지 |
| 키 파기 | 사용 종료 시 파기 — 덮어쓰기로 흔적 제거 |
코드
TLS 1.2 강제
def create_ssl_context(cert_path: str, key_path: str) -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# [NIS 1.2.1(1), 4.1.1(1)] TLS 1.2 이상만 허용
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
# [NIS 1.2.1(1)] TLS 1.0, 1.1 및 SSL 2/3 명시적 차단
ctx.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 \
| ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
ctx.load_cert_chain(cert_path, key_path)
return ctx
- 한 줄(minimum_version)이 아니라 두 겹
- 명시적 차단 옵션까지 건다
- 조항 번호가 주석으로 코드에 박혀 있다
= 추적매트릭스의 실체
패스워드 표 1
def validate_password(password, username=None):
# [표1 금지] 사용자 계정(ID)과 동일한 패스워드 금지
if username and password.lower() == username.lower():
return False, "사용자 계정(ID)과 동일한 패스워드는 사용할 수 없습니다"
# [표1 준수] 9자리 이상의 길이 확보
if len(password) < 9:
return False, "패스워드는 9자 이상이어야 합니다"
def _has_keyboard_sequence(password, min_length=4):
# [표1 금지] qwer/asdf/1234 등 키보드 연속 4자 이상 차단
pw_lower = password.lower()
for row in KEYBOARD_ROWS:
for i in range(len(row) - min_length + 1):
seq = row[i:i + min_length]
if seq in pw_lower or seq[::-1] in pw_lower:
return True
return False
5회 잠금, 구현과 검사가 마주본다
# 구현 — server/auth.py
if not self._verify_password(password, user.password_hash):
attempts = self.db.increment_failed_attempts(username)
# [NIS 2.2.1(2)] 5회 이하 연속 실패 시 인증 비활성화
if attempts >= self.max_failures:
self.db.lock_user(username, self.lockout_duration)
# [NIS 8.1.1 표3] 한계치 도달 -> 감사기록
self.audit.log(AuditEventType.ACCOUNT_LOCKED, ...)
- 5회째에 잠금
- 잠금 사건을 검사기록으로 남김
- 잠금과 기록이 한 몸
# 검사 — validator/main.py
# 5회 실패 시도
for i in range(5):
resp = self._login(test_user, wrong_pass)
# 6번째 -- 정상 비밀번호여도 잠금 상태여야 한다
resp = self._login(test_user, self._test_pass)
if resp.status_code == 401:
self._add("인증 실패 5회 잠금", True, "잠금 동작 확인")
- 검사기는 서버 코드를 보지 않음
- 밖에서 실제로 두들겨 ‘행동’으로 판정함
지울 수 없는 기록, 사라져야 하는 키
# 감사기록 — 지울 수 없게 · server/audit.py
-- [NIS 8.3.1(2)] 인가된 관리자라도 삭제 불가
CREATE TRIGGER prevent_audit_delete
BEFORE DELETE ON audit_log
BEGIN
SELECT RAISE(ABORT, '감사기록은 삭제할 수 없습니다');
END;
- DB 트리거가 DELETE 자체를 실패시킴
- 관리자 권한으로도 우회할 수 없음
# 암호키 — 반드시 사라지게 · client/crypto.py
def wipe(self) -> None:
# [NIS 9.4.1(2)] 0으로 3회 덮어쓰기
for _ in range(3):
ctypes.memset(
(ctypes.c_char * len(self._buf)).from_buffer(self._buf),
0, len(self._buf))
self._buf = bytearray()
- 메모리를 0으로 3회 덮어씀 - 흔적까지 지운다
속지 않는 서버
# 실패 사유를 말하지 않는다 · server/auth.py
AUTH_FAIL_MSG = "인증 실패"
# 없는 계정인지, 틀린 패스워드인지 알려주지 않는다
# [NIS 2.5.2] 인증 실패 사유 미제공
- 계정 열거 차단 - CTF에서 늘 먼저 찔러보는 곳
# 헤더를 믿지 않는다 · server/main.py
def get_client_ip(request: Request) -> str:
# [NIS 3.3.1] X-Forwarded-For를 신뢰하면
# IP 화이트리스트 우회 가능 -- 사용 금지
return request.client.host if request.client else "unknown"
- XFF 헤더는 공격자가 마음대로 씀 - 소켓의 실제 peer만 신뢰
입구는 한 명씩만
# server/main.py
def _validate_single_host_ip(ip_str: str) -> tuple[bool, str]:
# [NIS 3.3.1(4)] 와일드카드, any, 범위 표현 차단
blocked_patterns = ["*", "any", "0.0.0.0", "::", "0:0:0:0:0:0:0:0"]
if ip_str.lower() in blocked_patterns:
return False, f"'{ip_str}'는 허용되지 않는 IP 형식입니다"
if "*" in ip_str or "?" in ip_str:
return False, "와일드카드(*, ?)를 포함한 IP는 허용되지 않습니다"
...
# 미들웨어 — 차단 + 감사기록
@app.middleware("http")
async def ip_whitelist_middleware(request, call_next):
# [NIS 3.3.1] 관리용 단말 IP 제한 + 차단 시 감사기록
client_ip = get_client_ip(request)
if not auth_manager.check_ip_whitelist(client_ip):
audit_logger.log(AuditEventType.IP_BLOCKED, ...)
return JSONResponse(status_code=403, ...)
- ‘전부 허용’으로 도망갈 길을 코드가 막음
- 차단도 감사기록이 남음
키를 지키는 계층
# 패스워드 → bcrypt · server/auth.py
def _hash_password(self, password: str) -> str:
# [NIS 4.2.1(5)] 일방향 해시 + salt 128bit
# bcrypt는 salt 랜덤 생성 + iteration 내장
return bcrypt.hashpw(
password.encode("utf-8"), bcrypt.gensalt()
).decode("utf-8")
- 복원할 필요가 없는 값
- 일방향 해시로만 저장함
- salt·iteration은 bcrypt에 내장.
# 패스프레이즈 → KEK · client/crypto.py
def derive_kek(self, passphrase, salt=None):
# [NIS 9.2.1(1)] PKCS#5 v2.1(RFC 8018) -- PBKDF2HMAC
if salt is None:
salt = os.urandom(16) # [9.2.1(5)] salt 128bit
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32, # [9.1.1] 보안강도 112bit 이상
salt=salt,
iterations=self.kek_iterations, # 100000회
)
- 패스프레이즈에서 KEK를 유도
- 기준의 숫자 세 개가 인자에 그대로 박혀 있음
부팅부터 의심
# 구동 시 자체시험 · client/selftest.py
def run_self_test(config) -> tuple[bool, list[str]]:
# [NIS 5.1.1(1)] 구동 시 필수 자체시험
# 1. [5.1.1(5)] 카메라 하드웨어 접근
# 2. [5.1.1(2)] 서버 연결 (네트워크 프로세스)
# 3. [5.1.1(2)] TLS 인증서 유효성
# 4. [5.1.1(2)] 암호화 모듈 동작
...
- 켜질 때마다 카메라·서버·TLS·암호 모듈을 스스로 점검
# 주기적 무결성 검증 · client/integrity.py
def verify(self) -> list[dict]:
# [NIS 5.2.1(2)(4)] 현재 해시를 기준(baseline)과 비교
# 실행 시 + 24시간 주기 자동 검증
with open(self.hash_file) as f:
baseline_doc = json.load(f)
...
- 돌아가는 중에도 24시간마다 파일 해시를 기분과 대조
검증 자동화
요구사항 검사는 어떻게 자동화되는가
검사 대상 서버 → 검사기 → PASS/FAIL (리포트 + NIS 조항)
- 검사기기는 문서를 읽지 않음
- 실제로 접속하고, 로그인하고, 공격을 흉내 내서 방어를 확인함
- 38개 세부 항목을 자동 판정하고, 각 판정에 NIS 조항 번호를 붙임
validator가 보는 것
| 검사/항목 | 내용 | NIS 조항 |
| TLS 버전 | TLS 1.0/1.1 거부 · 1.2 이상만 허용 | 1.2.1① · 4.1.1① |
| 암호 스위트 | 112bit 이상 · 금지 알고리즘 없음 | 9.1.1①② |
| 불필요 포트 | 지정 포트 외 열린 포트 없음 | 3.2.1 · 10.3.1 |
| 인증 실패 잠금 | 5회 실패 → 잠금 | 2.2.1②③ |
| 중복 로그인 | 동일 계정 동시 접속 차단 | 7.2.1① |
| 세션 만료 | 유휴 시 토큰 만료 | 7.1.1② |
| 패스워드 정책 | 9종 입력 — 표1 준수·금지 | 2.3.1 표1 |
| IP 범위 차단 | 7종 입력 — 0.0.0.0 · * · CIDR 거부 | 3.3.1③④ |
| 계정명 차단 | 8종 입력 — admin·root 등 거부 | 3.4.1④ |
| 감사 기록 | 이벤트 생성·항목 확인 | 8.1.1 · 8.1.2 |
심사의 벽
- 문서의 벽
- 제출문서 5종 전부 한국어
- 구현명세서에는 모든 인터페이스·모든 계정을 전수 기재, 미재 발견
= 백도어 간주 → 발급 거부
2. 이력의 벽
- 취약점 개선내역서
- 신청일 3년 전부터 신청 전일까지, 제품에 해당하는 공개 취약점(CVE 등)의 개선 완료 내역을 기재
3. 요구사항의 벽
- 공통 9대분류 + 제품별 요구사항
- 조항 하나하나를 구현·문서·시험과 1:1로 잇는 추적매트릭스
4. 시간의 벽
- 권장 시험기간
- 공통요구사항 35~40일 + 제품 요구사항 5~10일, ‘부적합’ 판정마다 보완 루프로 되돌아감
5. 유지의 벽
- 확인서 유효기간 5년/2년
- 형상(버전) 변경 시 추가시험·동일성 확인 시험, 발급 후에도 관리가 계속됨
AI가 가속하는 것들
| 과거 | 지금 |
| 조항 코드 추적매트릭스를 수작업으로 관리 | 코드 주석 기반 자동 생성·동기화 — 앞의 [NIS 2.3.1] 주석이 그 재료 |
| 요구사항·지침 문서 수백 쪽을 정독·해석 | 조항 요약·해석 보조, 구현 체크리스트로 변환 |
| 구현명세서 등 문서 5종 초안을 손으로 작성 | 코드에서 인터페이스·계정 목록 추출, 한국어 기술문서 초안 생성 |
| 3년치 공개 취약점(CVE) 이력을 일일이 조사 | 의존성·CVE 스캔과 개선내역 정리 자동화 |
| 시험 전 자체 점검(사전 모의심사)을 수동으로 | 자동 검증기 생성 — 38항목 PASS/FAIL (앞의 validator) |
방법론

실전
프롬프트 예시
① “IP카메라 요구사항 2.2.1을 구현해줘 — 연속 인증 실패 5회 이하에서 계정 잠금,
5분 경과 후 해제, 잠금 발생 시 감사기록. 구현부마다 [NIS 2.2.1] 주석을 달아줘.”
→ 산출물: 잠금 로직 코드 — 앞 ‘코드로 보는 조항 ③’에서 본 것
② “코드베이스 전체에서 [NIS *] 주석을 스캔해 조항 파일 함수 추적매트릭스를
표로 만들어줘. 체크리스트와 대조해 미구현 조항을 표시해줘.”
→ 산출물: 추적매트릭스 — 앞 추적매트릭스 장에서 본 것
③ “이 서버를 밖에서 검사하는 자동 검증기를 만들어줘 —
TLS 1.0/1.1 접속이 거부되는지, 틀린 패스워드 5회 후 정상 패스워드로도 로그인이 막히는지,
판정마다 조항 번호를 붙여 PASS/FAIL 출력.”
→ 산출물: validator — 앞 ‘출력 예시’ 장에서 본 것
'2026 unknown 하계 세미나' 카테고리의 다른 글
| 물리계층 보안 (PHY-Security) 입문 (0) | 2026.07.13 |
|---|---|
| 시큐어 코딩 (0) | 2026.07.08 |
| 자동차 보안 (0) | 2026.07.08 |
| PQC II (0) | 2026.07.08 |
| 격자 기반 암호SIS · LWE부터 Kyber와 ML-KEM (0) | 2026.07.08 |