파생된 아이덴티티, AES‑GCM 및 Ed25519 서명을 활용한 안전한 디지털 영수증 프로토콜 (DRP) 설계

발행: (2025년 12월 4일 오후 02:43 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

Introduction

오늘날 디지털 영수증은 파편화 문제를 안고 있습니다. 가맹점, 발행자, 인수인, 카드 네트워크가 서로 다른 스키마, 포맷, 프라이버시 모델을 사용하고 있기 때문입니다.
디지털 영수증 프로토콜(DRP)은 통합된, 동의 기반의, 종단 간 암호화된, 검증 가능하고 사용자 제어가 가능한 라인 아이템 영수증 이식성을 목표로 합니다.

이 글에서는 다음을 포함하는 DRP 캡슐 아키텍처를 제안합니다.

  • 파생된 아이덴티티
  • AES‑GCM 암호화
  • Ed25519 무결성 보호
  • NIST SP 800‑171 Rev.3 및 PCI DSS v4.0과의 강력한 정렬
  • 전체 Python 레퍼런스 구현

Architectural Principles

  • 사용자는 장기적인 루트 시크릿을 보유합니다.
  • 각 가맹점마다 가명 아이덴티티가 생성됩니다.
  • 고유한 대칭키가 각 영수증을 암호화합니다.

이러한 조치는 연결 불가능성, 프라이버시, 그리고 암호학적 격리를 보장합니다.

DRP Capsule Structure

DRP 캡슐은 다음으로 구성됩니다.

  • Header – 공개 메타데이터이며, 민감한 데이터가 없고 무결성 보호됩니다.
  • Ciphertext – AES‑256‑GCM으로 암호화된 청구 항목.
  • Signature – 결정적 다이제스트에 대한 Ed25519 발행자 서명.

Regulatory Alignment

구현에는 암호화 작업을 다음과 매핑하는 주석이 포함됩니다.

  • NIST 800‑171 Rev.3 controls

    • SC‑13 (Cryptographic Protection)
    • SC‑28 (Data at Rest)
    • IA‑5 (Authenticator/Secret Management)
    • AU‑2 / AU‑3 (Auditability)
  • PCI DSS v4.0 controls

    • Req.3 (Protect Stored PAN Data)
    • Req.4 (Encrypt Transmission)
    • Req.6.4.3 (Secure Crypto Design)

Full Python Implementation

"""
This implementation is for EDUCATIONAL PURPOSES ONLY.
Compliance-oriented inline notes reference:
NIST SP 800-171 Rev.3 (SC-13, SC-28, IA-5, AU-2, AU-3)
PCI DSS v4.0 (Req.3, Req.4, Req.6.4.3)
Goal:
Derived Identities
AES-256-GCM
Ed25519 issuer signatures
"""
from dataclasses import dataclass, asdict
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes, serialization

@dataclass
class DerivedIdentityWallet:
    def __init__(self, root_secret: bytes):
        self._root_secret = root_secret

    def derive_user_key(self, merchant_context: str) -> bytes:
        """AES-256 key derivation using HKDF."""
        hkdf = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=merchant_context.encode(),
        )
        return hkdf.derive(self._root_secret)

    def derive_user_pseudonym(self, merchant_context: str) -> str:
        """Privacy-preserving pseudonymous ID."""
        hkdf = HKDF(
            algorithm=hashes.SHA256(),
            length=32,
            salt=None,
            info=(merchant_context + "|pseudonym").encode(),
        )
        raw = hkdf.derive(self._root_secret)

        digest = hashes.Hash(hashes.SHA256())
        digest.update(raw)
        return base64.urlsafe_b64encode(digest.finalize()).decode()

def encrypt_receipt_claims(claims: ReceiptClaims, key: bytes):
    ...

def generate_issuer_keypair() -> Ed25519PrivateKey:
    ...

def export_public_key_pem(pk: Ed25519PublicKey) -> str:
    ...

def sign_capsule_digest(priv: Ed25519PrivateKey, header, nonce_b64, ciphertext_b64):
    h = hashes.Hash(hashes.SHA256())
    h.update(capsule)
    digest = h.finalize()
    return priv.sign(digest)

def verify_drp_capsule(capsule: DRPCapsule, pub: Ed25519PublicKey):
    h = hashes.Hash(hashes.SHA256())
    h.update(data)
    digest = h.finalize()
    sig = base64.b64decode(capsule.issuer_signature)
    try:
        pub.verify(sig, digest)
        return True
    except Exception:
        return False

def decrypt_drp_capsule(capsule: DRPCapsule, key: bytes) -> ReceiptClaims:
    items = [LineItem(**it) for it in raw["line_items"]]
    return ReceiptClaims(**{**raw, "line_items": items})

def issue_drp_capsule(claims, wallet, issuer_priv):
    nonce, ciphertext = encrypt_receipt_claims(claims, key)

    nonce_b64 = base64.b64encode(nonce).decode()
    ct_b64 = base64.b64encode(ciphertext).decode()

    header = DRPHeader(
        drp_version="1.0.0",
        capsule_id=base64.b32encode(os.urandom(10)).decode().rstrip("="),
        issuer_id=claims.merchant_id,
        schema_id="DRP-RECEIPT-V1",
        country_code=claims.country_code,
    )

    sig = sign_capsule_digest(issuer_priv, header, nonce_b64, ct_b64)

    return DRPCapsule(
        header=header,
        ciphertext=ct_b64,
        nonce=nonce_b64,
        issuer_signature=base64.b64encode(sig).decode(),
    )

if __name__ == "__main__":
    issuer_priv = generate_issuer_keypair()
    issuer_pub = issuer_priv.public_key()

    items = [
        LineItem("SKU1", "Coffee 1kg", 1, 15.99, 0.07),
        LineItem("SKU2", "Cup", 2, 8.50, 0.07),
    ]

    subtotal = sum(i.unit_price * i.quantity for i in items)
    tax = sum(i.unit_price * i.quantity * i.tax_rate for i in items)

    claims = ReceiptClaims(
        merchant_id="MRC-001",
        terminal_id="POS-01",
        country_code="US",
        currency="USD",
        total_amount=round(subtotal + tax, 2),
        tax_amount=round(tax, 2),
        timestamp_utc=int(time.time()),
        card_network="VISA",
        network_profile="VISA-US-2025",
        pan_token="tok_visa_4242",
        auth_code="AUTH123",
        line_items=items,
    )

    capsule = issue_drp_capsule(claims, wallet, issuer_priv)

    print("VALID SIGNATURE:", verify_drp_capsule(capsule, issuer_pub))

    key = wallet.derive_user_key("MRC-001|VISA-US-2025")
    recovered = decrypt_drp_capsule(capsule, key)

    print(json.dumps(asdict(recovered), indent=2))

Diagrams

Diagram 1 – DRP Capsule Architecture

Integrity-protected via Ed25519 signature

Confidentiality + Integrity (AEAD)

Issuer authentication & non-repudiation

Diagram 2 – Derived Identity Wallet (Key Hierarchy)

Root Secret (32 bytes)
(Never leaves secure enclave)


HKDF (merchant_context = "merchant_id | network_profile")

├─ Derived User Key
├─ Derived Pseudonym
└─ Derived Identifier (optional)

Diagram 3 – Issuance Flow (Merchant → User Wallet)
(illustrative flow, not shown in code)

Diagram 4 – Verification & Decryption Flow

Wallet Derives Key via HKDF


AES‑GCM Decryption


Reconstruct Receipt Claims


Decrypted Receipt (Plain)
Back to Blog

관련 글

더 보기 »