关于 consent-receipt-spec
面向隐私工程与合规实施者的同意收据规范技能,介绍 Kantara v1.1 的机器可读格式及其与 ISO/IEC 27560:2023 的对应关系。内容涵盖收据字段、JWT 验证和生命周期管理。
主要功能
- 01说明 Kantara 收据核心字段
- 02描述控制者、服务与处理目的结构
- 03提供 JSON 机器可读收据格式
- 04涵盖基于 JWT 的签名验证
- 05管理收据更新、撤回与过期
适用场景
- 01实现标准化的同意记录
- 02生成供个人追踪的机器可读收据
- 03验证已签发收据的真实性
- 04管理同意收据的状态与更新
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mukul975/ privacy-data-protection-skills --skill consent-receipt-spec安装后在 Claude Code 中输入/consent-receipt-spec确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/template.mdreferences/standards.mdworkflows.mdscripts/process.py详细介绍
Kantara Initiative 同意回执规范
概述
同意回执是提供给个人(数据主体)的同意交易记录,用作已给予同意的凭证。Kantara Initiative 同意回执规范 v1.1 定义了一种标准的机器可读格式,使个人能够跨多个组织跟踪和管理自己的同意。本技能涵盖符合 Kantara 规范和 ISO/IEC 27560:2023 的同意回执实现。
同意回执结构
核心字段(Kantara v1.1)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| version | String | 是 | 规范版本(例如,“KI-CR-v1.1.0”) |
| jurisdiction | String | 是 | 法律管辖区(ISO 3166-1 alpha-2) |
| consentTimestamp | DateTime | 是 | 授予同意的 UTC 时间戳 |
| collectionMethod | String | 是 | 收集同意的方式(Web 表单、口头、纸质) |
| consentReceiptID | UUID | 是 | 此回执的唯一标识符 |
| publicKey | String | 否 | 用于验证回执的公钥 |
| language | String | 是 | 同意交互所用语言(BCP 47) |
| piiPrincipalId | String | 是 | 数据主体的标识符 |
| piiControllers | Array | 是 | 数据控制者列表 |
| policyUrl | URL | 是 | 隐私政策链接 |
| services | Array | 是 | 已获得同意的服务 |
| sensitive | Boolean | 是 | 是否涉及特殊类别数据 |
| spiCat | Array | 否 | 已处理数据的特殊类别 |
PII 控制者对象
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| piiController | String | 是 | 控制者组织的名称 |
| onBehalf | Boolean | 否 | 是否代表另一控制者行事 |
| contact | String | 是 | 控制者的联系信息 |
| address | Object | 是 | 控制者的实际地址 |
| String | 是 | 联系邮箱地址 | |
| phone | String | 否 | 联系电话号码 |
| piiControllerUrl | URL | 否 | 控制者网站的网址 |
服务对象
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| service | String | 是 | 服务名称 |
| purposes | Array | 是 | 处理目的列表 |
目的对象
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| purpose | String | 是 | 处理目的说明 |
| purposeCategory | Array | 是 | 目的类别代码 |
| consentType | String | 是 | “explicit”或“implicit” |
| piiCategory | Array | 是 | 已处理 PII 的类别 |
| primaryPurpose | Boolean | 是 | 是否为主要目的 |
| termination | String | 是 | 撤回同意的方式 |
| thirdPartyDisclosure | Boolean | 是 | 是否与第三方共享数据 |
| thirdPartyName | String | 否 | 第三方名称(如适用) |
机器可读的回执格式
JSON 同意回执
{
"version": "KI-CR-v1.1.0",
"jurisdiction": "EU",
"consentTimestamp": "2026-03-14T10:30:00.000Z",
"collectionMethod": "web_form",
"consentReceiptID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"language": "en",
"piiPrincipalId": "user-98765",
"piiControllers": [
{
"piiController": "Cipher Engineering Labs",
"onBehalf": false,
"contact": "Data Protection Officer",
"address": {
"streetAddress": "100 Technology Drive",
"locality": "London",
"region": "Greater London",
"postalCode": "EC2A 1NT",
"country": "GB"
},
"email": "[email protected]",
"phone": "+44-20-7946-0958",
"piiControllerUrl": "https://www.cipherengineeringlabs.com"
}
],
"policyUrl": "https://www.cipherengineeringlabs.com/privacy-policy",
"services": [
{
"service": "Privacy Analytics Platform",
"purposes": [
{
"purpose": "Provide personalized privacy compliance recommendations",
"purposeCategory": ["core_service"],
"consentType": "explicit",
"piiCategory": ["contact_information", "professional_information"],
"primaryPurpose": true,
"termination": "Account settings > Privacy > Withdraw consent",
"thirdPartyDisclosure": false
},
{
"purpose": "Send product updates and feature announcements",
"purposeCategory": ["marketing"],
"consentType": "explicit",
"piiCategory": ["contact_information"],
"primaryPurpose": false,
"termination": "Unsubscribe link in email or Account settings",
"thirdPartyDisclosure": false
},
{
"purpose": "Aggregate usage analytics to improve platform features",
"purposeCategory": ["analytics"],
"consentType": "explicit",
"piiCategory": ["usage_data", "device_information"],
"primaryPurpose": false,
"termination": "Account settings > Privacy > Analytics opt-out",
"thirdPartyDisclosure": true,
"thirdPartyName": "Cipher Analytics Processing Ltd"
}
]
}
],
"sensitive": false,
"spiCat": []
}
基于 JWT 的验证机制
签名同意回执实现
"""
Consent receipt generation and verification using JWT (JSON Web Tokens).
Implements Kantara Initiative Consent Receipt Specification v1.1
with cryptographic verification.
"""
import json
import uuid
from datetime import datetime, timezone
from typing import Optional
import jwt # PyJWT
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
class ConsentReceiptIssuer:
"""
Issue and sign consent receipts as JWTs.
Uses RS256 (RSA with SHA-256) for signing.
"""
def __init__(self, private_key_pem: bytes, issuer_name: str):
"""
Args:
private_key_pem: PEM-encoded RSA private key
issuer_name: Name of the issuing organization
"""
self.private_key = serialization.load_pem_private_key(
private_key_pem, password=None, backend=default_backend()
)
self.public_key = self.private_key.public_key()
self.issuer_name = issuer_name
def issue_receipt(
self,
principal_id: str,
services: list[dict],
jurisdiction: str,
collection_method: str,
policy_url: str,
sensitive: bool = False,
language: str = "en",
controller_info: dict = None
) -> tuple[str, str]:
"""
Generate a signed consent receipt.
Args:
principal_id: Data subject identifier
services: List of service/purpose objects
jurisdiction: Legal jurisdiction code
collection_method: How consent was collected
policy_url: URL of the privacy policy
sensitive: Whether special categories are involved
language: Language of consent interaction
controller_info: PII controller details
Returns:
Tuple of (receipt_id, signed_jwt_string)
"""
receipt_id = str(uuid.uuid4())
if controller_info is None:
controller_info = {
"piiController": self.issuer_name,
"onBehalf": False,
"contact": "Data Protection Officer",
"email": f"dpo@{self.issuer_name.lower().replace(' ', '')}.com"
}
receipt_payload = {
"version": "KI-CR-v1.1.0",
"jurisdiction": jurisdiction,
"consentTimestamp": datetime.now(timezone.utc).isoformat(),
"collectionMethod": collection_method,
"consentReceiptID": receipt_id,
"language": language,
"piiPrincipalId": principal_id,
"piiControllers": [controller_info],
"policyUrl": policy_url,
"services": services,
"sensitive": sensitive,
"spiCat": [],
# JWT standard claims
"iss": self.issuer_name,
"sub": principal_id,
"iat": int(datetime.now(timezone.utc).timestamp()),
"jti": receipt_id,
}
# Sign the receipt as a JWT
signed_token = jwt.encode(
receipt_payload,
self.private_key,
algorithm="RS256",
headers={"kid": f"{self.issuer_name}-consent-key-1"}
)
return receipt_id, signed_token
def get_public_key_pem(self) -> str:
"""Export the public key for verification by data subjects."""
return self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode("utf-8")
class ConsentReceiptVerifier:
"""
Verify signed consent receipts.
Used by data subjects or auditors to confirm receipt authenticity.
"""
def __init__(self):
self.trusted_keys: dict[str, bytes] = {}
def register_issuer_key(self, issuer_name: str, public_key_pem: str):
"""Register a trusted issuer's public key."""
self.trusted_keys[issuer_name] = public_key_pem.encode("utf-8")
def verify_receipt(self, token: str) -> tuple[bool, Optional[dict], Optional[str]]:
"""
Verify a signed consent receipt JWT.
Returns:
Tuple of (is_valid, decoded_payload_or_None, error_message_or_None)
"""
try:
# Decode without verification first to get the issuer
unverified = jwt.decode(token, options={"verify_signature": False})
issuer = unverified.get("iss")
if issuer not in self.trusted_keys:
return (False, None, f"Unknown issuer: {issuer}")
public_key = serialization.load_pem_public_key(
self.trusted_keys[issuer],
backend=default_backend()
)
# Verify the signature
decoded = jwt.decode(
token,
public_key,
algorithms=["RS256"],
issuer=issuer
)
# Validate required Kantara fields
required_fields = [
"version", "jurisdiction", "consentTimestamp",
"collectionMethod", "consentReceiptID", "piiPrincipalId",
"piiControllers", "policyUrl", "services", "sensitive"
]
missing = [f for f in required_fields if f not in decoded]
if missing:
return (False, decoded, f"Missing required fields: {missing}")
return (True, decoded, None)
except jwt.ExpiredSignatureError:
return (False, None, "Receipt JWT has expired")
except jwt.InvalidSignatureError:
return (False, None, "Invalid signature — receipt may have been tampered with")
except jwt.DecodeError as e:
return (False, None, f"Failed to decode JWT: {str(e)}")
回执生命周期管理
生命周期状态
ISSUED --> ACTIVE --> WITHDRAWN
| | |
| v v
| UPDATED ARCHIVED
| |
v v
EXPIRED ACTIVE (new version)
生命周期管理器
"""
Manage the lifecycle of consent receipts including
updates, withdrawals, and expiration tracking.
"""
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
class ReceiptStatus(Enum):
ISSUED = "issued"
ACTIVE = "active"
UPDATED = "updated"
WITHDRAWN = "withdrawn"
EXPIRED = "expired"
ARCHIVED = "archived"
@dataclass
class ReceiptRecord:
receipt_id: str
principal_id: str
status: ReceiptStatus
issued_at: datetime
last_updated: datetime
withdrawn_at: datetime | None
jwt_token: str
version: int
superseded_by: str | None
purposes: list[str]
class ConsentReceiptLifecycle:
"""
Manage consent receipt state transitions and history.
"""
def __init__(self, receipt_store, issuer: ConsentReceiptIssuer):
self.store = receipt_store
self.issuer = issuer
def activate_receipt(self, receipt_id: str) -> bool:
"""Transition a receipt from ISSUED to ACTIVE."""
record = self.store.get(receipt_id)
if record and record.status == ReceiptStatus.ISSUED:
record.status = ReceiptStatus.ACTIVE
record.last_updated = datetime.now(timezone.utc)
self.store.update(record)
return True
return False
def update_receipt(
self,
receipt_id: str,
updated_services: list[dict],
jurisdiction: str,
policy_url: str
) -> str | None:
"""
Update an existing receipt by issuing a new version.
The old receipt is marked as UPDATED and linked to the new one.
Returns the new receipt ID or None if update failed.
"""
old_record = self.store.get(receipt_id)
if not old_record or old_record.status not in [
ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
]:
return None
# Issue new receipt
new_receipt_id, new_jwt = self.issuer.issue_receipt(
principal_id=old_record.principal_id,
services=updated_services,
jurisdiction=jurisdiction,
collection_method="consent_update",
policy_url=policy_url
)
# Create new record
new_record = ReceiptRecord(
receipt_id=new_receipt_id,
principal_id=old_record.principal_id,
status=ReceiptStatus.ACTIVE,
issued_at=datetime.now(timezone.utc),
last_updated=datetime.now(timezone.utc),
withdrawn_at=None,
jwt_token=new_jwt,
version=old_record.version + 1,
superseded_by=None,
purposes=[p["purpose"] for s in updated_services for p in s.get("purposes", [])]
)
self.store.save(new_record)
# Mark old receipt as updated
old_record.status = ReceiptStatus.UPDATED
old_record.superseded_by = new_receipt_id
old_record.last_updated = datetime.now(timezone.utc)
self.store.update(old_record)
return new_receipt_id
def withdraw_consent(self, receipt_id: str, reason: str = "") -> bool:
"""
Withdraw consent and mark the receipt accordingly.
Returns True if withdrawal was successful.
"""
record = self.store.get(receipt_id)
if not record or record.status not in [
ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
]:
return False
record.status = ReceiptStatus.WITHDRAWN
record.withdrawn_at = datetime.now(timezone.utc)
record.last_updated = datetime.now(timezone.utc)
self.store.update(record)
return True
def get_active_receipts(self, principal_id: str) -> list[ReceiptRecord]:
"""Get all active consent receipts for a data subject."""
return self.store.find_by_principal(
principal_id, status=ReceiptStatus.ACTIVE
)
def get_receipt_history(self, principal_id: str) -> list[ReceiptRecord]:
"""Get the complete consent receipt history for a data subject."""
return self.store.find_by_principal(principal_id)
ISO/IEC 27560:2023 对齐
| Kantara CR 字段 | ISO 27560 对应项 | 备注 |
|---|---|---|
| consentReceiptID | 同意记录标识符 | 记录的唯一标识符 |
| consentTimestamp | 同意日期和时间 | 收集同意的时间 |
| piiPrincipalId | PII 主体标识符 | 数据主体标识符 |
| piiControllers | PII 控制者 | 处理数据的组织 |
| services.purposes | 目的 | 处理目的 |
| services.purposes.piiCategory | PII 类别 | 个人数据类型 |
| sensitive | 敏感 PII 指示符 | 特殊类别标志 |
| jurisdiction | 司法管辖区 | 适用的法律框架 |
| collectionMethod | 同意机制 | 获取同意的方式 |
| policyUrl | 隐私声明引用 | 隐私声明链接 |
参考资料
- Kantara Initiative《同意收据规范》v1.1.0(2018)
- ISO/IEC 27560:2023 — 隐私技术 — 同意记录信息结构
- ISO/IEC 29184:2020 — 在线隐私声明和同意指南
- RFC 7519 — JSON Web Token(JWT)
- W3C 数据隐私词汇和控制社区组
- IEEE P7012 — 机器可读个人隐私条款标准
评论
支持 Markdown