name: pci-compliance description: 实施PCI DSS合规要求,用于安全处理支付卡数据和支付系统。在确保支付处理安全、实现PCI合规或实施支付卡安全措施时使用。
PCI合规性
掌握PCI DSS(支付卡行业数据安全标准)合规性,用于安全支付处理和处理持卡人数据。
何时使用此技能
- 构建支付处理系统
- 处理信用卡信息
- 实施安全支付流程
- 进行PCI合规审计
- 减少PCI合规范围
- 实施令牌化和加密
- 准备PCI DSS评估
PCI DSS要求(12项核心要求)
建立和维护安全网络
- 安装和维护防火墙配置
- 不使用供应商提供的默认密码
保护持卡人数据
- 保护存储的持卡人数据
- 在公共网络上加密传输持卡人数据
维护漏洞管理
- 保护系统免受恶意软件侵害
- 开发和维护安全系统和应用
实施强访问控制
- 按业务需知限制对持卡人数据的访问
- 识别和验证对系统组件的访问
- 限制对持卡人数据的物理访问
监控和测试网络
- 跟踪和监控所有对网络资源和持卡人数据的访问
- 定期测试安全系统和流程
维护信息安全政策
- 维护解决信息安全的政策
合规级别
级别1:> 600万笔交易/年(需要年度ROC) 级别2:100万-600万笔交易/年(年度SAQ) 级别3:20,000-100万笔电子商务交易/年 级别4:< 20,000笔电子商务或< 100万笔总交易
数据最小化(永不存储)
# 永不存储这些
PROHIBITED_DATA = {
'full_track_data': '磁条数据',
'cvv': '卡片验证码/值',
'pin': 'PIN或PIN块'
}
# 可以存储(如果加密)
ALLOWED_DATA = {
'pan': '主账号(卡号)',
'cardholder_name': '卡片持有者姓名',
'expiration_date': '卡片过期日期',
'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"尝试存储禁止字段:{field}")
令牌化
使用支付处理器令牌
import stripe
class TokenizedPayment:
"""使用令牌处理支付(服务器上无卡数据)。"""
@staticmethod
def create_payment_method_token(card_details):
"""从卡详情创建令牌(仅客户端)。"""
# 这应仅使用STRIPE.JS在客户端完成
# 永远不要将卡详情发送到服务器
"""
// 前端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="支付"
)
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 = {} # 在生产中:使用加密数据库
def tokenize(self, card_data):
"""将卡数据转换为令牌。"""
# 生成安全随机令牌
token = secrets.token_urlsafe(32)
# 加密卡数据
encrypted = self.cipher.encrypt(json.dumps(card_data).encode())
# 存储令牌 -> 加密数据映射
self.vault[token] = encrypted
return token
def detokenize(self, token):
"""从令牌检索卡数据。"""
encrypted = self.vault.get(token)
if not encrypted:
raise ValueError("未找到令牌")
# 解密
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
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': '未经授权访问持卡人数据'}, 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')
# 配置为写入安全、仅追加日志
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):
"""净化用户输入以防止注入。"""
# 移除特殊字符
# 根据预期格式验证
# 为数据库查询转义
pass
PCI DSS SAQ(自我评估问卷)
SAQ A(最少要求)
- 使用托管支付页面的电子商务
- 您的系统上无卡数据
- ~20个问题
SAQ A-EP
- 使用嵌入式支付表单的电子商务
- 使用JavaScript处理卡数据
- ~180个问题
SAQ D(最多要求)
- 存储、处理或传输卡数据
- 完整PCI DSS要求
- ~300个问题
合规检查清单
PCI_COMPLIANCE_CHECKLIST = {
'network_security': [
'防火墙配置和维护',
'无供应商默认密码',
'网络分段实施'
],
'data_protection': [
'不存储CVV、磁条数据或PIN',
'PAN存储时加密',
'PAN显示时掩码',
'加密密钥妥善管理'
],
'vulnerability_management': [
'安装和更新反病毒软件',
'安全开发实践',
'定期安全补丁',
'执行漏洞扫描'
],
'access_control': [
'按角色限制访问',
'所有用户唯一ID',
'多因素认证',
'物理安全措施'
],
'monitoring': [
'启用审计日志',
'日志审查流程',
'文件完整性监控',
'定期安全测试'
],
'policy': [
'文档化安全政策',
'执行风险评估',
'安全意识培训',
'事件响应计划'
]
}
资源
- references/data-minimization.md:永不存储禁止数据
- references/tokenization.md:令牌化策略
- references/encryption.md:加密要求
- references/access-control.md:基于角色的访问
- references/audit-logging.md:全面日志
- assets/pci-compliance-checklist.md:完整检查清单
- assets/encrypted-storage.py:加密工具
- scripts/audit-payment-system.sh:合规审计脚本
常见违规
- 存储CVV:永不存储卡片验证码
- 未加密PAN:卡号存储时必须加密
- 弱加密:使用AES-256或等效
- 无访问控制:限制谁可以访问持卡人数据
- 缺少审计日志:必须记录所有对支付数据的访问
- 不安全传输:始终使用TLS 1.2+
- 默认密码:更改所有默认凭证
- 无安全测试:需要定期渗透测试
减少PCI范围
- 使用托管支付:Stripe Checkout、PayPal等
- 令牌化:用令牌替换卡数据
- 网络分段:隔离持卡人数据环境
- 外包:使用PCI合规支付处理器
- 不存储:永不存储完整卡详情
通过最小化接触卡数据的系统,显著降低合规负担。