🔒 보안

HIPAA

Health Insurance Portability and Accountability Act (의료정보 이동성 및 책임법)

HIPAA는 1996년 제정된 미국 연방법으로, 의료 정보(PHI: Protected Health Information)의 프라이버시와 보안을 보호합니다. 의료 서비스 제공자, 보험사, 의료 데이터를 다루는 IT 기업은 반드시 HIPAA 규정을 준수해야 합니다.

📖 상세 설명

HIPAA(Health Insurance Portability and Accountability Act)는 미국 의료 산업의 근간을 이루는 연방법입니다. 1996년 빌 클린턴 대통령 시절 제정되었으며, 초기에는 보험 이동성(Portability)에 초점을 맞췄지만, 이후 2003년 Privacy Rule, 2005년 Security Rule이 추가되면서 의료 정보 보호의 핵심 프레임워크로 발전했습니다. 디지털 의료 시대에 HIPAA는 헬스케어 IT 시스템 설계의 필수 고려사항이 되었습니다.

HIPAA의 핵심은 PHI(Protected Health Information) 보호입니다. PHI는 환자를 식별할 수 있는 18가지 유형의 데이터를 포함합니다: 이름, 생년월일, 주소, 사회보장번호, 의료기록번호, 건강보험 ID, 사진, 이메일 등. ePHI(Electronic PHI)는 디지털 형태로 저장, 전송되는 PHI로, 더 엄격한 Security Rule의 적용을 받습니다. 단일 PHI 유출도 심각한 법적 책임과 평판 손상을 초래할 수 있습니다.

HIPAA는 세 가지 핵심 규칙으로 구성됩니다. Privacy Rule은 PHI의 사용과 공개에 대한 기준을 설정합니다(환자 동의, 최소 필요 원칙). Security Rule은 ePHI의 기술적, 물리적, 관리적 보호조치를 요구합니다(암호화, 접근 제어, 감사 로그). Breach Notification Rule은 PHI 유출 시 72시간 이내 환자와 HHS(보건복지부)에 통보할 의무를 규정합니다. 500명 이상 유출 시 언론 공지도 필수입니다.

HIPAA 적용 대상은 Covered Entity(의료 서비스 제공자, 보험사, 의료 정보 교환소)와 Business Associate(CE를 대신해 PHI를 다루는 제3자)로 나뉩니다. 클라우드 서비스, SaaS, AI 의료 솔루션 제공업체는 대부분 BA에 해당합니다. BA는 CE와 BAA(Business Associate Agreement)를 체결해야 하며, HIPAA 위반 시 직접 책임을 집니다. 위반 벌금은 건당 최대 $1.5M(연간 $15M), 형사 처벌도 가능합니다.

💻 코드 예제

# HIPAA 준수를 위한 PHI 데이터 보호 예제 - Python
import hashlib
import logging
from datetime import datetime
from typing import Dict, Any, Optional
from cryptography.fernet import Fernet
from functools import wraps

# HIPAA 감사 로그 설정 (Security Rule 요구사항)
logging.basicConfig(
    filename='hipaa_audit.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
audit_logger = logging.getLogger('HIPAA_AUDIT')

# PHI 식별자 18가지 유형
PHI_IDENTIFIERS = [
    'name', 'address', 'dates', 'phone', 'fax', 'email',
    'ssn', 'mrn', 'health_plan_id', 'account_number',
    'certificate_number', 'vehicle_id', 'device_id', 'url',
    'ip_address', 'biometric', 'photo', 'any_unique_id'
]

class HIPAACompliantStorage:
    """HIPAA 준수 PHI 저장소"""

    def __init__(self, encryption_key: bytes):
        self.cipher = Fernet(encryption_key)
        self._phi_access_log = []

    def encrypt_phi(self, phi_data: Dict[str, Any]) -> bytes:
        """PHI 암호화 (Security Rule - 기술적 보호조치)"""
        import json
        json_data = json.dumps(phi_data)
        encrypted = self.cipher.encrypt(json_data.encode())

        # 감사 로그 기록
        audit_logger.info(f"PHI encrypted: patient_id={phi_data.get('patient_id', 'unknown')}")
        return encrypted

    def decrypt_phi(self, encrypted_data: bytes, user_id: str, purpose: str) -> Dict[str, Any]:
        """PHI 복호화 (접근 기록 필수)"""
        import json

        # 접근 기록 (Minimum Necessary 원칙 확인용)
        access_record = {
            'timestamp': datetime.utcnow().isoformat(),
            'user_id': user_id,
            'purpose': purpose,
            'action': 'decrypt'
        }
        self._phi_access_log.append(access_record)
        audit_logger.info(f"PHI accessed: user={user_id}, purpose={purpose}")

        decrypted = self.cipher.decrypt(encrypted_data)
        return json.loads(decrypted.decode())


def hipaa_audit_required(action: str):
    """HIPAA 감사 로그 데코레이터"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user_id = kwargs.get('user_id', 'system')
            patient_id = kwargs.get('patient_id', 'unknown')

            # 접근 전 로그
            audit_logger.info(
                f"PHI_ACCESS_START: action={action}, user={user_id}, patient={patient_id}"
            )

            try:
                result = func(*args, **kwargs)
                audit_logger.info(
                    f"PHI_ACCESS_SUCCESS: action={action}, user={user_id}"
                )
                return result
            except Exception as e:
                audit_logger.error(
                    f"PHI_ACCESS_FAILED: action={action}, user={user_id}, error={str(e)}"
                )
                raise
        return wrapper
    return decorator


def de_identify_phi(phi_record: Dict[str, Any]) -> Dict[str, Any]:
    """PHI 비식별화 (Safe Harbor 방식)

    HIPAA에서 허용하는 18가지 식별자 제거/변환
    """
    de_identified = phi_record.copy()

    # 직접 식별자 제거
    for identifier in PHI_IDENTIFIERS:
        if identifier in de_identified:
            if identifier in ['name', 'ssn', 'email', 'phone']:
                de_identified[identifier] = '[REDACTED]'
            elif identifier == 'dates':
                # 연도만 유지 (Safe Harbor)
                de_identified[identifier] = de_identified[identifier][:4] + '-XX-XX'
            elif identifier == 'address':
                # 주(State)만 유지, 상세 주소 제거
                de_identified[identifier] = de_identified.get('state', '[REDACTED]')

    return de_identified


# 사용 예시
if __name__ == "__main__":
    # 암호화 키 생성 (실제로는 안전한 키 관리 시스템 사용)
    key = Fernet.generate_key()
    storage = HIPAACompliantStorage(key)

    # PHI 데이터
    patient_phi = {
        'patient_id': 'P12345',
        'name': 'John Doe',
        'ssn': '123-45-6789',
        'dates': '1985-03-15',
        'diagnosis': 'Type 2 Diabetes',
        'medication': 'Metformin 500mg'
    }

    # 암호화 저장
    encrypted = storage.encrypt_phi(patient_phi)

    # 권한 있는 접근
    decrypted = storage.decrypt_phi(
        encrypted,
        user_id='dr_smith',
        purpose='Treatment'
    )

    # 연구용 비식별화
    de_identified = de_identify_phi(patient_phi)
    print(f"De-identified: {de_identified}")
// HIPAA 준수 ePHI 암호화 및 접근 제어 - Node.js
const crypto = require('crypto');
const { createLogger, format, transports } = require('winston');

// HIPAA 감사 로그 설정
const auditLogger = createLogger({
    level: 'info',
    format: format.combine(
        format.timestamp(),
        format.json()
    ),
    transports: [
        new transports.File({ filename: 'hipaa_audit.log' }),
        new transports.File({ filename: 'hipaa_error.log', level: 'error' })
    ]
});

class HIPAAEncryption {
    constructor() {
        // AES-256-GCM (HIPAA Security Rule 권장)
        this.algorithm = 'aes-256-gcm';
        this.keyLength = 32;
        this.ivLength = 16;
        this.tagLength = 16;
    }

    /**
     * ePHI 암호화 (전송 중 & 저장 시 암호화)
     */
    encryptPHI(plaintext, encryptionKey) {
        const iv = crypto.randomBytes(this.ivLength);
        const cipher = crypto.createCipheriv(
            this.algorithm,
            Buffer.from(encryptionKey, 'hex'),
            iv
        );

        let encrypted = cipher.update(plaintext, 'utf8', 'hex');
        encrypted += cipher.final('hex');
        const authTag = cipher.getAuthTag();

        // IV + AuthTag + Encrypted Data
        return {
            iv: iv.toString('hex'),
            authTag: authTag.toString('hex'),
            encryptedData: encrypted
        };
    }

    /**
     * ePHI 복호화
     */
    decryptPHI(encryptedObj, encryptionKey) {
        const decipher = crypto.createDecipheriv(
            this.algorithm,
            Buffer.from(encryptionKey, 'hex'),
            Buffer.from(encryptedObj.iv, 'hex')
        );

        decipher.setAuthTag(Buffer.from(encryptedObj.authTag, 'hex'));

        let decrypted = decipher.update(encryptedObj.encryptedData, 'hex', 'utf8');
        decrypted += decipher.final('utf8');

        return decrypted;
    }
}

/**
 * HIPAA 접근 제어 미들웨어
 */
class HIPAAAccessControl {
    constructor() {
        this.allowedRoles = {
            'view_phi': ['physician', 'nurse', 'admin'],
            'edit_phi': ['physician', 'admin'],
            'export_phi': ['admin', 'compliance_officer']
        };
    }

    /**
     * Minimum Necessary 원칙 적용
     */
    checkMinimumNecessary(userRole, requestedFields, purpose) {
        const allowedFields = {
            'physician': ['*'], // 모든 필드
            'nurse': ['name', 'vitals', 'medication', 'allergies'],
            'billing': ['name', 'insurance_id', 'procedure_codes'],
            'researcher': ['age_range', 'diagnosis', 'anonymized_id']
        };

        const userAllowed = allowedFields[userRole] || [];

        if (userAllowed.includes('*')) return requestedFields;

        return requestedFields.filter(field => userAllowed.includes(field));
    }

    /**
     * 접근 권한 검증 미들웨어
     */
    authorize(requiredPermission) {
        return (req, res, next) => {
            const userRole = req.user?.role;
            const userId = req.user?.id;

            // 감사 로그 기록
            auditLogger.info({
                event: 'PHI_ACCESS_ATTEMPT',
                userId,
                userRole,
                permission: requiredPermission,
                patientId: req.params.patientId,
                ipAddress: req.ip,
                timestamp: new Date().toISOString()
            });

            if (!this.allowedRoles[requiredPermission]?.includes(userRole)) {
                auditLogger.warn({
                    event: 'PHI_ACCESS_DENIED',
                    userId,
                    userRole,
                    permission: requiredPermission,
                    reason: 'Insufficient permissions'
                });

                return res.status(403).json({
                    error: 'Access Denied',
                    message: 'You do not have permission to access this PHI'
                });
            }

            next();
        };
    }
}

/**
 * Breach Notification 헬퍼
 */
async function notifyBreach(breachDetails) {
    const { affectedCount, breachDate, discoveryDate, phiTypes } = breachDetails;

    auditLogger.error({
        event: 'PHI_BREACH_DETECTED',
        ...breachDetails,
        timestamp: new Date().toISOString()
    });

    // 72시간 이내 통보 필요
    const notificationDeadline = new Date(discoveryDate);
    notificationDeadline.setHours(notificationDeadline.getHours() + 72);

    // 500명 이상 영향 시 HHS 즉시 보고 + 미디어 통보
    if (affectedCount >= 500) {
        console.log('CRITICAL: Major breach - HHS and media notification required');
        // await notifyHHS(breachDetails);
        // await notifyMedia(breachDetails);
    }

    // 영향받은 개인에게 통보
    // await notifyAffectedIndividuals(breachDetails);
}

module.exports = { HIPAAEncryption, HIPAAAccessControl, notifyBreach };
# HIPAA 준수 AWS 인프라 구성 - Terraform
# Security Rule 요구사항 충족을 위한 기술적 보호조치

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# HIPAA 적격 서비스만 사용하는 VPC
resource "aws_vpc" "hipaa_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "hipaa-compliant-vpc"
    Compliance  = "HIPAA"
    Environment = "production"
  }
}

# PHI 저장용 암호화된 S3 버킷
resource "aws_s3_bucket" "phi_storage" {
  bucket = "hipaa-phi-storage-${random_id.bucket_suffix.hex}"

  tags = {
    Name       = "PHI Storage"
    Compliance = "HIPAA"
    DataClass  = "PHI"
  }
}

# S3 버킷 암호화 (저장 시 암호화 - Security Rule)
resource "aws_s3_bucket_server_side_encryption_configuration" "phi_encryption" {
  bucket = aws_s3_bucket.phi_storage.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.phi_key.arn
      sse_algorithm     = "aws:kms"
    }
    bucket_key_enabled = true
  }
}

# S3 버킷 버전 관리 (무결성 보장)
resource "aws_s3_bucket_versioning" "phi_versioning" {
  bucket = aws_s3_bucket.phi_storage.id
  versioning_configuration {
    status = "Enabled"
  }
}

# S3 퍼블릭 액세스 차단
resource "aws_s3_bucket_public_access_block" "phi_public_access" {
  bucket = aws_s3_bucket.phi_storage.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# PHI 암호화용 KMS 키 (HIPAA 요구 AES-256)
resource "aws_kms_key" "phi_key" {
  description             = "KMS key for PHI encryption"
  deletion_window_in_days = 30
  enable_key_rotation     = true

  # 키 정책 - 최소 권한 원칙
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "Enable IAM User Permissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "Allow PHI Access Role"
        Effect = "Allow"
        Principal = {
          AWS = aws_iam_role.phi_access_role.arn
        }
        Action = [
          "kms:Decrypt",
          "kms:GenerateDataKey"
        ]
        Resource = "*"
      }
    ]
  })

  tags = {
    Name       = "PHI Encryption Key"
    Compliance = "HIPAA"
  }
}

# PHI 데이터베이스 (RDS with 암호화)
resource "aws_db_instance" "phi_database" {
  identifier     = "hipaa-phi-db"
  engine         = "postgres"
  engine_version = "15.4"
  instance_class = "db.r6g.large"

  allocated_storage     = 100
  max_allocated_storage = 1000
  storage_type          = "gp3"
  storage_encrypted     = true  # 저장 시 암호화
  kms_key_id           = aws_kms_key.phi_key.arn

  db_name  = "phi_records"
  username = "phi_admin"
  password = var.db_password  # Secrets Manager 사용 권장

  # 네트워크 격리
  db_subnet_group_name   = aws_db_subnet_group.phi_subnet.name
  vpc_security_group_ids = [aws_security_group.phi_db_sg.id]
  publicly_accessible    = false

  # 백업 및 복구 (Security Rule)
  backup_retention_period = 35
  backup_window          = "03:00-04:00"
  maintenance_window     = "Mon:04:00-Mon:05:00"

  # 감사 로그 (CloudWatch로 전송)
  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]

  # 삭제 보호
  deletion_protection = true
  skip_final_snapshot = false
  final_snapshot_identifier = "phi-db-final-snapshot"

  tags = {
    Name       = "PHI Database"
    Compliance = "HIPAA"
    DataClass  = "PHI"
  }
}

# CloudTrail (감사 로그 - Security Rule)
resource "aws_cloudtrail" "hipaa_trail" {
  name                          = "hipaa-audit-trail"
  s3_bucket_name               = aws_s3_bucket.audit_logs.id
  include_global_service_events = true
  is_multi_region_trail        = true
  enable_log_file_validation   = true
  kms_key_id                   = aws_kms_key.phi_key.arn

  event_selector {
    read_write_type           = "All"
    include_management_events = true

    data_resource {
      type   = "AWS::S3::Object"
      values = ["${aws_s3_bucket.phi_storage.arn}/"]
    }
  }

  tags = {
    Name       = "HIPAA Audit Trail"
    Compliance = "HIPAA"
  }
}

# 감사 로그 보존 (6년 - HIPAA 요구사항)
resource "aws_s3_bucket_lifecycle_configuration" "audit_retention" {
  bucket = aws_s3_bucket.audit_logs.id

  rule {
    id     = "hipaa-retention"
    status = "Enabled"

    transition {
      days          = 90
      storage_class = "GLACIER"
    }

    expiration {
      days = 2190  # 6년
    }
  }
}

output "phi_storage_bucket" {
  value       = aws_s3_bucket.phi_storage.bucket
  description = "HIPAA-compliant PHI storage bucket"
}

🗣️ 실무에서 이렇게 말하세요

💬 헬스케어 SaaS 아키텍처 회의에서
"우리 서비스가 PHI를 다루기 때문에 HIPAA Business Associate에 해당합니다. 병원과 BAA를 체결해야 하고, AWS에서도 BAA 서명이 필요해요. 데이터는 AES-256으로 암호화하고, 모든 PHI 접근은 감사 로그에 6년간 보관해야 합니다. 또한 Minimum Necessary 원칙에 따라 역할별로 접근 가능한 PHI 필드를 제한해야 해요."
💬 보안 감사 대응에서
"Security Rule의 기술적 보호조치를 모두 구현했습니다. 저장 시 암호화(KMS), 전송 중 암호화(TLS 1.3), 고유 사용자 ID, 자동 로그오프, 감사 로그를 적용했고요. 물리적 보호조치는 AWS의 SOC 2 보고서로 증빙합니다. 관리적 보호조치로는 연 1회 HIPAA 교육, 위험 분석, 재해 복구 계획이 있습니다."
💬 데이터 유출 사고 대응에서
"Breach Notification Rule에 따라 움직입니다. PHI 유출이 확인되면 72시간 이내에 영향받은 환자에게 통보해야 해요. 500명 이상이면 HHS에 즉시 보고하고 미디어에도 알려야 합니다. 지금 영향 범위를 파악하고, 법무팀과 협의해서 통보 문구를 준비합시다. 감사 로그로 누가 어떤 데이터에 접근했는지 추적이 가능합니다."

⚠️ 주의사항 & 베스트 프랙티스

암호화 없이 PHI 전송/저장

이메일, 문자, 암호화되지 않은 클라우드 스토리지로 PHI를 전송하면 HIPAA 위반입니다. 반드시 암호화된 채널(TLS)과 저장소(AES-256)를 사용하세요. S3 버킷 퍼블릭 설정 실수로 PHI가 노출되는 사고가 빈번합니다.

BAA 없이 제3자 서비스 사용

AWS, Google Cloud, Slack 등 PHI가 경유하는 모든 서비스와 BAA를 체결해야 합니다. BAA 없이 사용하면 CE와 BA 모두 위반 책임을 집니다. 특히 무료 버전, 개인용 서비스는 대부분 HIPAA 미지원입니다.

감사 로그 미비

누가, 언제, 어떤 PHI에 접근했는지 추적이 불가능하면 Security Rule 위반입니다. 로그는 최소 6년간 보관해야 합니다. 로그 없이는 유출 사고 시 영향 범위 파악도, 법적 방어도 불가능합니다.

HIPAA 컴플라이언스 베스트 프랙티스

PHI 최소화(필요한 데이터만 수집), 역할 기반 접근 제어(RBAC), 정기 위험 분석(연 1회 이상), 직원 교육(입사 시 + 연간), 인시던트 대응 계획 수립, BAA 체계적 관리를 실행하세요. 가능하면 HITRUST CSF 인증도 고려하세요.

🔗 관련 용어

📚 더 배우기