데이터 프라이버시 및 보안에 대한 DuckDB 이해
데이터 프라이버시 및 보안은 전 세계 모든 조직에 있어 매우 중요해졌습니다. 조직은 데이터 유용성을 유지하면서 데이터 세트에서 민감한 정보를 식별하고, 마스킹하거나 제거해야 할 필요가 종종 있습니다. 이 기사는 민감한 데이터 수정에 효과적으로 DuckDB라는 인프로세스 분석 데이터베이스를 활용하는 방법을 탐구합니다.
왜 DuckDB인가? (그리고 왜 신경 써야 할까요?)
DuckDB를 SQLite의 분석적으로 재능 있는 사촌이라고 생각해 보세요. 그것은 당신의 프로세스 내에서 바로 실행되는 임베디드 데이터베이스이지만, 분석 작업 부하를 처리하도록 특별히 설계되었습니다. 데이터 수정에 적합한 이유는 무엇일까요? 복잡한 데이터베이스 서버를 설정하지 않고도 번개처럼 빠른 속도로 대규모 데이터 세트를 처리할 수 있다고 상상해 보세요. 좋지 않나요?
DuckDB가 우리의 사용 사례에 특히 멋진 이유는 다음과 같습니다:
- 컬럼 지향 저장소 덕분에 매우 빠릅니다.
- 기존의 Python 환경에서 바로 실행할 수 있습니다.
- 다양한 파일 형식을 문제 없이 처리합니다.
- 클라우드 스토리지와도 잘 호환됩니다(자세한 내용은 뒤에서 다루겠습니다).
이 가이드에서는 Python과 DuckDB를 사용할 것입니다. DuckDB는 그들의 문서에 언급된 대로 다른 언어도 지원합니다.
데이터 프라이버시를 위한 DuckDB 시작하기
전제 조건
- Python 3.9 이상 설치됨
- Python 프로젝트 및 가상 환경 또는 Conda 환경 설정에 대한 사전 지식
다음 명령어를 실행하여 가상 환경 내에 DuckDB를 설치하세요:
pip install duckdb --upgrade
DuckDB를 설치했으니 이제 DuckDB 연결을 생성해 보겠습니다:
import duckdb
import pandas as pd
# Create a DuckDB connection - it's this simple!
conn = duckdb.connect(database=':memory:')
고급 PII 데이터 마스킹 기술
강력한 PII(개인 식별 정보) 마스킹을 구현하는 방법은 다음과 같습니다:
고객 정보로 가득 찬 데이터 세트가 정리되어야 한다고 가정해 보겠습니다. 일반적인 시나리오를 처리하는 방법은 다음과 같습니다.
샘플 데이터를 생성해 보겠습니다:
CREATE TABLE customer_data AS
SELECT
'John Doe' as name,
'123-45-6789' as ssn,
'[email protected]' as email,
'123-456-7890' as phone;
- 이것은 샘플 민감 데이터 한 행으로 구성된
customer_data
라는 테이블을 생성합니다. - 데이터에는 이름, SSN, 이메일 및 전화번호가 포함됩니다.
두 번째 부분은 regexp_replace
를 사용하여 마스킹 패턴을 포함합니다:
-- Implement PII masking patterns
CREATE TABLE masked_data AS
SELECT
regexp_replace(name, '[a-zA-Z]', 'X') as masked_name,
regexp_replace(ssn, '[0-9]', '*') as masked_ssn,
regexp_replace(email, '(^[^@]+)(@.*$)', '****$2') as masked_email,
regexp_replace(phone, '[0-9]', '#') as masked_phone
FROM customer_data;
위의 SQL 코드가 수행하는 작업에 대해 설명하겠습니다.
regexp_replace(name, '[a-zA-Z]', 'X')
- 모든 알파벳(대문자 및 소문자)을
'X'
로 대체합니다. - 예:
"John Doe"
는"XXXX XXX"
가 됩니다.
- 모든 알파벳(대문자 및 소문자)을
regexp_replace(ssn, '[0-9]', '*') as masked_ssn
- 모든 숫자를
'*'
로 대체합니다. - 예:
"123-45-6789"
는"--***"
가 됩니다.
- 모든 숫자를
regexp_replace(email, '(^[^@]+)(@.*$)', '****$2') as masked_email:
(^[^@]+)
는@
기호 이전의 모든 것을 캡처합니다.(@.*$)
는@
와 그 이후의 모든 것을 캡처합니다.- 첫 번째 부분을
'****'
로 대체하고 도메인 부분은 유지합니다. - 예:
""
는"****@email.com"
이 됩니다.
regexp_replace(phone, '[0-9]', '#') as masked_phone
:- 모든 숫자를
'#'
로 바꿉니다 - 예:
"123-456-7890"
는"###-###-####"
로 변환됩니다
- 모든 숫자를
따라서 귀하의 데이터는 아래와 같이 변환됩니다:
- 원본 데이터:
name: John Doe
ssn: 123-45-6789
email: [email protected]
phone: 123-456-7890
- 마스킹된 데이터:
masked_name: XXXX XXX
masked_ssn: ***-**-****
masked_email: ****@email.com
masked_phone: ###-###-####
파이썬 구현
import duckdb
import pandas as pd
def mask_pii_data():
# Create a DuckDB connection in memory
conn = duckdb.connect(database=':memory:')
try:
# Create and populate sample data
conn.execute("""
CREATE TABLE customer_data AS
SELECT
'John Doe' as name,
'123-45-6789' as ssn,
'[email protected]' as email,
'123-456-7890' as phone
""")
# Implement PII masking
conn.execute("""
CREATE TABLE masked_data AS
SELECT
regexp_replace(name, '[a-zA-Z]', 'X') as masked_name,
regexp_replace(ssn, '[0-9]', '*') as masked_ssn,
regexp_replace(email, '(^[^@]+)(@.*$)', '****$2') as masked_email,
regexp_replace(phone, '[0-9]', '#') as masked_phone
FROM customer_data
""")
# Fetch and display original data
print("Original Data:")
original_data = conn.execute("SELECT * FROM customer_data").fetchdf()
print(original_data)
print("\n")
# Fetch and display masked data
print("Masked Data:")
masked_data = conn.execute("SELECT * FROM masked_data").fetchdf()
print(masked_data)
return original_data, masked_data
except Exception as e:
print(f"An error occurred: {str(e)}")
return None, None
finally:
# Close the connection
conn.close()
규칙에 따른 데이터 마스킹
기술적인 부분에 들어가기 전에 데이터 마스킹을 간단히 설명하겠습니다.
데이터 마스킹은 문서나 데이터베이스에서 민감한 정보를 숨기거나 제거하는 과정으로, 전체 구조와 비민감한 내용을 유지합니다. 인쇄된 문서에서 기밀 정보를 숨기기 위해 검은색 마커를 사용하는 것과 비슷하지만 디지털 형식입니다.
이제 DuckDB와 Python을 사용하여 데이터 마스킹을 구현해 보겠습니다. 이 코드 조각에는 주석이 추가되어 있어 쉽게 따라할 수 있습니다.
import duckdb
import pandas as pd
def demonstrate_data_redaction():
# Create a connection
conn = duckdb.connect(':memory:')
# Create sample data with various sensitive information
conn.execute("""
CREATE TABLE sensitive_info AS SELECT * FROM (
VALUES
('John Doe', '[email protected]', 'CC: 4532-1234-5678-9012', 'Normal text'),
('Jane Smith', '[email protected]', 'SSN: 123-45-6789', 'Some notes'),
('Bob Wilson', '[email protected]', 'Password: SecretPass123!', 'Regular info'),
('Alice Brown', '[email protected]', 'API_KEY=abc123xyz', 'Basic text')
) AS t(name, email, sensitive_field, normal_text);
""")
# Define redaction rules
redaction_rules = {
'email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', # Email pattern
'sensitive_field': r'(CC:\s*\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}|SSN:\s*\d{3}-\d{2}-\d{4}|Password:\s*\S+|API_KEY=\S+)', # Various sensitive patterns
'name': r'[A-Z][a-z]+ [A-Z][a-z]+' # Full name pattern
}
# Show original data
print("Original Data:")
print(conn.execute("SELECT * FROM sensitive_info").fetchdf())
# Apply redaction
redact_sensitive_data(conn, 'sensitive_info', redaction_rules)
# Show redacted data
print("\nRedacted Data:")
print(conn.execute("SELECT * FROM redacted_data").fetchdf())
return conn
def redact_sensitive_data(conn, table_name, rules):
"""
Redact sensitive data based on specified patterns.
Parameters:
- conn: DuckDB connection
- table_name: Name of the table containing sensitive data
- rules: Dictionary of column names and their corresponding regex patterns to match sensitive data
"""
redaction_cases = []
# This creates a CASE statement for each column
# If the pattern matches, the value is redacted
# If not, the original value is kept
for column, pattern in rules.items():
redaction_cases.append(f"""
CASE
WHEN regexp_matches({column}, '{pattern}')
THEN '(REDACTED)'
ELSE {column}
END as {column}
""")
query = f"""
CREATE TABLE redacted_data AS
SELECT
{', '.join(redaction_cases)}
FROM {table_name};
"""
conn.execute(query)
# Example with custom redaction patterns
def demonstrate_custom_redaction():
conn = duckdb.connect(':memory:')
# Create sample data
conn.execute("""
CREATE TABLE customer_data AS SELECT * FROM (
VALUES
('John Doe', '123-45-6789', 'ACC#12345', '$5000'),
('Jane Smith', '987-65-4321', 'ACC#67890', '$3000'),
('Bob Wilson', '456-78-9012', 'ACC#11111', '$7500')
) AS t(name, ssn, account, balance);
""")
# Define custom redaction rules with different patterns
custom_rules = {
'name': {
'pattern': r'[A-Z][a-z]+ [A-Z][a-z]+',
'replacement': lambda match: f"{match[0][0]}*** {match[0].split()[1][0]}***"
},
'ssn': {
'pattern': r'\d{3}-\d{2}-\d{4}',
'replacement': 'XXX-XX-XXXX'
},
'account': {
'pattern': r'ACC#\d{5}',
'replacement': 'ACC#*****'
}
}
def apply_custom_redaction(conn, table_name, rules):
redaction_cases = []
for column, rule in rules.items():
redaction_cases.append(f"""
CASE
WHEN regexp_matches({column}, '{rule['pattern']}')
THEN '{rule['replacement']}'
ELSE {column}
END as {column}
""")
query = f"""
CREATE TABLE custom_redacted AS
SELECT
{', '.join(redaction_cases)},
balance -- Keep this column unchanged
FROM {table_name};
"""
conn.execute(query)
# Show original data
print("\nOriginal Customer Data:")
print(conn.execute("SELECT * FROM customer_data").fetchdf())
# Apply custom redaction
apply_custom_redaction(conn, 'customer_data', custom_rules)
# Show results
print("\nCustom Redacted Data:")
print(conn.execute("SELECT * FROM custom_redacted").fetchdf())
# Run demonstrations
print("=== Basic Redaction Demo ===")
demonstrate_data_redaction()
print("\n=== Custom Redaction Demo ===")
demonstrate_custom_redaction()
샘플 결과
마스킹 전:
name email sensitive_field
John Doe [email protected] CC: 4532-1234-5678-9012
마스킹 후:
name email sensitive_field
(REDACTED) (REDACTED) (REDACTEd)
결론
DuckDB는 민감한 데이터 수정에 도움이 되는 간단하면서도 강력한 인메모리 데이터베이스입니다.
항상 기억하세요:
- 마스킹된 데이터를 검증하세요.
- 대용량 데이터셋에 대해 병렬 처리를 사용하세요.
- 클라우드 데이터에 대해 DuckDB의 S3 통합을 활용하세요.
- 대용량 파일을 처리할 때 메모리 사용량을 주의하세요.
Source:
https://dzone.com/articles/developers-guide-handling-sensitive-data-with-duckdb