카드 데이터를 다루기 시작할 때 /payment-processing-pci-compliance는 PCI DSS 범위, 토큰화, 감사 로그를 매핑합니다. — Claude Skill
Claude Code용 Claude 스킬 · 제공: Seth Hobson · 실행: /payment-processing-pci-compliance (Claude 내)·업데이트: 2026년 6월 14일·v1.0.0
PCI DSS 요구사항, 토큰화, 암호화, 감사 로그를 매핑합니다.
- 12개 PCI DSS 요구사항을 아키텍처에 매핑합니다: 네트워크, 데이터, 접근, 모니터링, 정책.
- 토큰화 패턴: 처리기 토큰(Stripe.js), Fernet 암호화를 쓰는 자체 토큰 금고.
- 저장 데이터에는 AES-256-GCM 암호화와 nonce 회전을 적용하고, 전송 중에는 TLS 1.2+를 강제합니다.
- 역할 기반 PCI 게이트가 있는 접근 제어 데코레이터와 모든 조회에 대한 감사 로그 쓰기를 제공합니다.
- SAQ 지침: A, A-EP, D 범위를 줄여 300문항 대신 약 20문항만 작성하도록 돕습니다.
대상
기능
다음 스프린트에 Stripe 연동을 시작합니다. /payment-processing-pci-compliance는 아키텍처를 SAQ A 범위로 매핑하고 토큰화 흐름을 생성하며 PAN이 서버에 닿지 않는지 확인합니다.
QSA가 6주 뒤에 옵니다. /payment-processing-pci-compliance는 12개 요구사항 대비 체크리스트를 만들고 적용되는 SAQ를 식별하며 CVV나 트랙 데이터의 금지된 저장을 표시합니다.
반복 청구에 토큰 저장이 필요합니다. /payment-processing-pci-compliance는 Fernet 암호화, 안전한 무작위 토큰, detokenize/delete 메서드를 갖춘 TokenVault 클래스를 생성합니다.
엔지니어가 감사 흔적 없이 payments 테이블을 SELECT할 수 있습니다. /payment-processing-pci-compliance는 require_pci_access 데코레이터와 append-only PCIAuditLogger를 추가합니다.
작동 방식
현재 또는 예정된 카드 데이터 흐름을 설명합니다.
12개 PCI DSS 요구사항 기준의 범위 맵과 위험 신호를 받습니다.
PAN이 서버에 닿지 않게 하는 토큰화 코드를 받습니다(Stripe.js 또는 자체 금고).
암호화, 접근 제어, 감사 로그 모듈을 바로 넣을 수 있는 형태로 받습니다.
적절한 수준(A, A-EP, D)의 SAQ 자체 평가를 실행합니다.
예시
프런트엔드: Stripe.js 카드 요소 백엔드: 카드가 아니라 Stripe 토큰만 수신 저장: customer_id + payment_method_token 거래: 연 50K 지역: 미국
SAQ A 적용(전자상거래, 호스팅 결제 페이지) Level 4 가맹점(100만 건 미만 거래) SAQ D의 300문항 대신 약 20문항 카드 데이터는 서버에 전혀 닿지 않음
프런트엔드는 Stripe.js로 토큰화(준수) 백엔드는 토큰만 저장(준수) 어떤 데이터베이스에도 CVV, PAN, 트랙 데이터 없음(검증됨) 로그는 카드 번호를 마스킹하도록 정리됨
PaymentData.sanitize_log: PAN을 6***4 형식으로 마스킹 require_pci_access 데코레이터: 역할 확인 + 감사 PCIAuditLogger: 카드 소유자 데이터 접근 추가 전용 로그 Validate-no-prohibited-storage: CVV 쓰기 거부
SAQ A 설문 완료(약 20문항) 매입사에 준수 증명 제출 분기별 ASV 스캔 예약 범위 확산을 막기 위해 분기별 검토
개선되는 지표
지원 도구
PCI 준수을(를) 사용해 보시겠어요?
시작 방법을 선택하세요.
이 스킬을 컴퓨터에 로컬로 설치하고 실행합니다.
컴퓨터에서 터미널을 열고 이 명령을 붙여넣으세요:
이 명령은 스킬과 모든 파일을 컴퓨터에 다운로드합니다:
모든 프로젝트에서 사용하려면 끝에 -g를 추가하세요.
Claude Code를 시작한 다음 명령을 입력하세요:
PCI 준수
안전한 결제 처리와 카드 소유자 데이터 취급을 위한 PCI DSS(Payment Card Industry Data Security Standard) 준수를 다룹니다.
이 스킬을 사용할 때
- 결제 처리 시스템을 구축할 때
- 신용카드 정보를 다룰 때
- 안전한 결제 흐름을 구현할 때
- PCI 준수 감사를 수행할 때
- PCI 준수 범위를 줄일 때
- 토큰화와 암호화를 구현할 때
- PCI DSS 평가를 준비할 때
PCI DSS 요구사항(12개 핵심 요구사항)
안전한 네트워크 구축 및 유지
- 방화벽 구성을 설치하고 유지합니다.
- 비밀번호에 공급업체 기본값을 사용하지 않습니다.
카드 소유자 데이터 보호
- 저장된 카드 소유자 데이터를 보호합니다.
- 공용 네트워크를 통해 전송되는 카드 소유자 데이터를 암호화합니다.
취약점 관리 유지
- 악성코드로부터 시스템을 보호합니다.
- 안전한 시스템과 애플리케이션을 개발하고 유지합니다.
강력한 접근 제어 구현
- 업무상 필요한 사람에게만 카드 소유자 데이터 접근을 제한합니다.
- 시스템 구성요소 접근을 식별하고 인증합니다.
- 카드 소유자 데이터에 대한 물리적 접근을 제한합니다.
네트워크 모니터링 및 테스트
- 네트워크 리소스와 카드 소유자 데이터에 대한 모든 접근을 추적하고 모니터링합니다.
- 보안 시스템과 프로세스를 정기적으로 테스트합니다.
정보보안 정책 유지
- 정보보안을 다루는 정책을 유지합니다.
준수 레벨
Level 1: 연 600만 건 초과 거래(연간 ROC 필요) Level 2: 연 100만-600만 건 거래(연간 SAQ) Level 3: 연 2만-100만 건 전자상거래 거래 Level 4: 전자상거래 2만 건 미만 또는 총 거래 100만 건 미만
데이터 최소화(절대 저장 금지)
# 절대 저장 금지
PROHIBITED_DATA = {
'full_track_data': 'Magnetic stripe data',
'cvv': 'Card verification code/value',
'pin': 'PIN or PIN block'
}
# 저장 가능(암호화된 경우)
ALLOWED_DATA = {
'pan': 'Primary Account Number (card number)',
'cardholder_name': 'Name on card',
'expiration_date': 'Card expiration',
'service_code': 'Service code'
}
class PaymentData:
"""안전한 결제 데이터 처리."""
def __init__(self):
self.prohibited_fields = ['cvv', 'cvv2', 'cvc', 'pin']
def sanitize_log(self, data):
"""로그에서 민감 데이터를 제거합니다."""
sanitized = data.copy()
# PAN 마스킹
if 'card_number' in sanitized:
card = sanitized['card_number']
sanitized['card_number'] = f"{card[:6]}{'*' * (len(card) - 10)}{card[-4:]}"
# 금지 데이터 제거
for field in self.prohibited_fields:
sanitized.pop(field, None)
return sanitized
def validate_no_prohibited_storage(self, data):
"""금지 데이터가 저장되지 않도록 보장합니다."""
for field in self.prohibited_fields:
if field in data:
raise SecurityError(f"Attempting to store prohibited field: {field}")
토큰화
결제 처리기 토큰 사용
import stripe
class TokenizedPayment:
"""토큰을 사용해 결제를 처리합니다(서버에는 카드 데이터 없음)."""
@staticmethod
def create_payment_method_token(card_details):
"""카드 상세 정보에서 토큰을 생성합니다(클라이언트 측에서만)."""
# 이 작업은 STRIPE.JS로 클라이언트 측에서만 수행해야 합니다.
# 카드 상세 정보를 절대 서버로 보내지 마세요.
"""
// Frontend JavaScript
const stripe = Stripe('pk_...');
const {token, error} = await stripe.createToken({
card: {
number: '4242424242424242',
exp_month: 12,
exp_year: 2024,
cvc: '123'
}
});
// token.id만 서버로 전송합니다(카드 상세 정보는 전송하지 않음).
"""
pass
@staticmethod
def charge_with_token(token_id, amount):
"""토큰으로 청구합니다(서버 측)."""
# 서버는 카드 번호가 아니라 토큰만 봅니다.
stripe.api_key = "sk_..."
charge = stripe.Charge.create(
amount=amount,
currency="usd",
source=token_id, # 카드 상세 정보 대신 토큰
description="Payment"
)
return charge
@staticmethod
def store_payment_method(customer_id, payment_method_token):
"""향후 사용을 위해 결제 수단을 토큰으로 저장합니다."""
stripe.Customer.modify(
customer_id,
source=payment_method_token
)
# 데이터베이스에는 customer_id와 payment_method_id만 저장합니다.
# 실제 카드 상세 정보는 절대 저장하지 않습니다.
return {
'customer_id': customer_id,
'has_payment_method': True
# 저장 금지: 카드 번호, CVV 등
}
자체 토큰화(고급)
import secrets
from cryptography.fernet import Fernet
class TokenVault:
"""카드 데이터를 위한 안전한 토큰 금고입니다(반드시 저장해야 하는 경우)."""
def __init__(self, encryption_key):
self.cipher = Fernet(encryption_key)
self.vault = {} # production에서는 암호화된 데이터베이스 사용
def tokenize(self, card_data):
"""카드 데이터를 토큰으로 변환합니다."""
# 안전한 무작위 토큰 생성
token = secrets.token_urlsafe(32)
# 카드 데이터 암호화
encrypted = self.cipher.encrypt(json.dumps(card_data).encode())
# token -> encrypted data 매핑 저장
self.vault[token] = encrypted
return token
def detokenize(self, token):
"""토큰에서 카드 데이터를 검색합니다."""
encrypted = self.vault.get(token)
if not encrypted:
raise ValueError("Token not found")
# 복호화
decrypted = self.cipher.decrypt(encrypted)
return json.loads(decrypted.decode())
def delete_token(self, token):
"""금고에서 토큰을 제거합니다."""
self.vault.pop(token, None)
암호화
저장 데이터
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
class EncryptedStorage:
"""AES-256-GCM으로 저장 데이터를 암호화합니다."""
def __init__(self, encryption_key):
"""256비트 키로 초기화합니다."""
self.key = encryption_key # 32바이트여야 함
def encrypt(self, plaintext):
"""데이터를 암호화합니다."""
# 무작위 nonce 생성
nonce = os.urandom(12)
# 암호화
aesgcm = AESGCM(self.key)
ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
# nonce + ciphertext 반환
return nonce + ciphertext
def decrypt(self, encrypted_data):
"""데이터를 복호화합니다."""
# nonce와 ciphertext 추출
nonce = encrypted_data[:12]
ciphertext = encrypted_data[12:]
# 복호화
aesgcm = AESGCM(self.key)
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
return plaintext.decode()
# 사용 예
storage = EncryptedStorage(os.urandom(32))
encrypted_pan = storage.encrypt("4242424242424242")
# encrypted_pan을 데이터베이스에 저장
전송 중 데이터
# 항상 TLS 1.2 이상 사용
# Flask/Django 예시
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
# HTTPS 강제
from flask_talisman import Talisman
Talisman(app, force_https=True)
접근 제어
from functools import wraps
from flask import session
def require_pci_access(f):
"""카드 소유자 데이터 접근을 제한하는 데코레이터입니다."""
@wraps(f)
def decorated_function(*args, **kwargs):
user = session.get('user')
# 사용자에게 PCI 접근 역할이 있는지 확인
if not user or 'pci_access' not in user.get('roles', []):
return {'error': 'Unauthorized access to cardholder data'}, 403
# 접근 시도 로그 기록
audit_log(
user=user['id'],
action='access_cardholder_data',
resource=f.__name__
)
return f(*args, **kwargs)
return decorated_function
@app.route('/api/payment-methods')
@require_pci_access
def get_payment_methods():
"""결제 수단을 검색합니다(제한된 접근)."""
# pci_access 역할이 있는 사용자만 접근 가능
pass
감사 로그
import logging
from datetime import datetime
class PCIAuditLogger:
"""PCI 준수 감사 로그."""
def __init__(self):
self.logger = logging.getLogger('pci_audit')
# 안전한 append-only 로그에 쓰도록 구성
def log_access(self, user_id, resource, action, result):
"""카드 소유자 데이터 접근을 기록합니다."""
entry = {
'timestamp': datetime.utcnow().isoformat(),
'user_id': user_id,
'resource': resource,
'action': action,
'result': result,
'ip_address': request.remote_addr
}
self.logger.info(json.dumps(entry))
def log_authentication(self, user_id, success, method):
"""인증 시도를 기록합니다."""
entry = {
'timestamp': datetime.utcnow().isoformat(),
'user_id': user_id,
'event': 'authentication',
'success': success,
'method': method,
'ip_address': request.remote_addr
}
self.logger.info(json.dumps(entry))
# 사용 예
audit = PCIAuditLogger()
audit.log_access(user_id=123, resource='payment_methods', action='read', result='success')
보안 모범 사례
입력 검증
import re
def validate_card_number(card_number):
"""카드 번호 형식을 검증합니다(Luhn 알고리즘)."""
# 공백과 대시 제거
card_number = re.sub(r'[\s-]', '', card_number)
# 모두 숫자인지 확인
if not card_number.isdigit():
return False
# Luhn 알고리즘
def luhn_checksum(card_num):
def digits_of(n):
return [int(d) for d in str(n)]
digits = digits_of(card_num)
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
checksum = sum(odd_digits)
for d in even_digits:
checksum += sum(digits_of(d * 2))
return checksum % 10
return luhn_checksum(card_number) == 0
def sanitize_input(user_input):
"""주입 공격을 막기 위해 사용자 입력을 정리합니다."""
# 특수 문자 제거
# 예상 형식에 맞는지 검증
# 데이터베이스 쿼리용 escape 적용
pass
PCI DSS SAQ(자가 평가 설문)
SAQ A(요구사항 가장 적음)
- 호스팅 결제 페이지를 사용하는 전자상거래
- 카드 데이터가 자체 시스템에 없음
- 약 20문항
SAQ A-EP
- 내장 결제 양식을 사용하는 전자상거래
- JavaScript가 카드 데이터를 처리함
- 약 180문항
SAQ D(요구사항 가장 많음)
- 카드 데이터를 저장, 처리 또는 전송함
- 전체 PCI DSS 요구사항 적용
- 약 300문항
준수 체크리스트
PCI_COMPLIANCE_CHECKLIST = {
'network_security': [
'Firewall configured and maintained',
'No vendor default passwords',
'Network segmentation implemented'
],
'data_protection': [
'No storage of CVV, track data, or PIN',
'PAN encrypted when stored',
'PAN masked when displayed',
'Encryption keys properly managed'
],
'vulnerability_management': [
'Anti-virus installed and updated',
'Secure development practices',
'Regular security patches',
'Vulnerability scanning performed'
],
'access_control': [
'Access restricted by role',
'Unique IDs for all users',
'Multi-factor authentication',
'Physical security measures'
],
'monitoring': [
'Audit logs enabled',
'Log review process',
'File integrity monitoring',
'Regular security testing'
],
'policy': [
'Security policy documented',
'Risk assessment performed',
'Security awareness training',
'Incident response plan'
]
}