GDPR Art.32 Technical and Organisational Security Measures: Developer Implementation Guide (2026)
GDPR Article 32 is the Regulation's core security mandate. It requires every data controller and every data processor to implement appropriate technical and organisational measures to ensure a level of security appropriate to the risk — but it deliberately avoids prescribing a fixed control list. This risk-based design gives engineers flexibility; it also creates compliance ambiguity that organisations routinely exploit to under-invest in security.
This guide cuts through that ambiguity. It maps the four explicit technical measures named in Art.32(1), explains the risk-proportionality test from Art.32(2), covers the state-of-the-art and cost factors, and provides a production-grade implementation pattern. If your organisation also falls under NIS2 Art.21 (critical sector operators), we cover the dual-compliance overlap at the end.
1. What Art.32 Actually Requires
Art.32(1) opens with the principle: "appropriate technical and organisational measures to ensure a level of security appropriate to the risk." It then lists four non-exhaustive examples:
(a) Pseudonymisation and Encryption
"the pseudonymisation and encryption of personal data"
Pseudonymisation (Art.4(5)): replace direct identifiers with a pseudonym — a key, token, or hash — such that re-identification requires separately held information. The pseudonymisation key must be stored separately with strict access control.
Encryption: personal data must be encrypted at rest and in transit. "Encryption" in 2026 means:
- At rest: AES-256 GCM or ChaCha20-Poly1305 with hardware security module (HSM) or KMS-managed keys
- In transit: TLS 1.2 minimum, TLS 1.3 preferred; no self-signed certificates in production
- Key rotation: 90-day maximum for symmetric keys, 1-year for asymmetric (DPA guidance varies)
Encryption alone does not satisfy Art.32 — it is one measure among several. EDPB Guidelines 4/2019 on pseudonymisation make clear that encryption of a database column is not equivalent to pseudonymisation if the decryption key is co-located with the data.
(b) Confidentiality, Integrity, Availability, and Resilience
"the ability to ensure the ongoing confidentiality, integrity, availability and resilience of processing systems and services"
This maps directly to the classic CIA triad plus resilience:
Confidentiality: only authorised subjects access personal data. Technically: RBAC/ABAC with least-privilege, network segmentation, secrets management (no hardcoded credentials), audit logging of all data access.
Integrity: data is accurate and cannot be altered without authorisation. Technically: database-level integrity constraints, write-ahead logging, cryptographic signatures for audit trails, immutable audit logs (append-only, log integrity checks).
Availability: processing systems must remain available for lawful processing. Technically: redundancy (multi-AZ deployments), auto-scaling, circuit breakers for downstream dependencies, health checks with alerting.
Resilience: systems must continue functioning under adversarial conditions — not just hardware failure but DDoS, ransomware, configuration errors. Resilience goes beyond availability; it requires graceful degradation design (fail-safe defaults, bulkheads, chaos engineering).
(c) Restoration Capability
"the ability to restore the availability and access to personal data in a timely manner in the event of a physical or technical incident"
This is a backup and recovery mandate. "Timely" is not defined — it is proportional to risk. For healthcare or payment data, hours-to-days; for low-risk data, days-to-weeks. Key engineering requirements:
- Automated daily backups with integrity verification (hash the backup, restore-test monthly)
- Geographically separated backup storage (not same availability zone as primary)
- Recovery Time Objective (RTO) and Recovery Point Objective (RPO) documented and tested
- Ransomware-resistant backups: immutable object storage (S3 Object Lock, Azure Immutable Blob), air-gapped copies for critical data
- Personal data in backups must remain encrypted; key management must survive the incident
(d) Regular Testing and Evaluation
"a process for regularly testing, assessing and evaluating the effectiveness of technical and organisational measures for ensuring the security of the processing"
This is the control most routinely neglected. "Regularly" is not defined — EDPB guidance suggests at minimum annually; for high-risk processing, quarterly or after material system changes.
Testing must cover:
- Penetration testing: external and internal, including social engineering assessment
- Vulnerability scanning: automated, integrated into CI/CD (SAST, DAST, SCA)
- Access review: quarterly review of who has access to what personal data
- Incident response drills: tabletop exercises; test the restoration capability from (c)
- DPIA review: re-assess Data Protection Impact Assessments when processing changes
2. The Risk-Proportionality Test (Art.32(2))
Art.32(2) lists four risk factors that calibrate what is "appropriate":
-
State of the art: what encryption, authentication, and security controls are standard practice in your industry today. In 2026, passwordless authentication, FIDO2/WebAuthn, and zero-trust network architecture are increasingly "state of the art" for cloud services.
-
Implementation costs: cost is a legitimate factor — but not a licence to skip controls. Regulators have rejected "too expensive" defences where the cost of the breach (fines + remediation + notification) exceeds the cost of prevention by orders of magnitude.
-
Nature, scope, context, and purposes of processing: special category data (Art.9) — health, biometric, political opinion, sexual orientation — requires systematically higher security than, say, a newsletter subscriber list.
-
Risk to rights and freedoms: the likelihood and severity of harm to data subjects (discrimination, identity theft, financial loss, physical harm). High-risk processing requires correspondingly more robust technical controls.
Practical application: before selecting controls, perform a risk assessment that maps each data processing activity to risk level (low/medium/high/very high) and documents control choices with rationale. This documentation is not optional — it demonstrates compliance under Art.5(2) accountability.
3. Art.32 × Art.28: Processor Obligations
Art.32 applies equally to data processors (cloud providers, SaaS vendors, infrastructure operators processing personal data on behalf of a controller). Your Data Processing Agreement (DPA/DPA under GDPR) must:
- Specify the technical and organisational measures the processor implements
- Require the processor to notify the controller within 72 hours of a personal data breach (to give the controller time to meet its own Art.33 notification deadline)
- Allow the controller to audit or review the processor's security measures
If you are a cloud provider or SaaS vendor processing customer data: your SOC 2 Type II report or ISO 27001 certificate is not a substitute for Art.32 compliance — but it provides strong evidence. DPAs routinely accept these certifications as evidence of Art.32 conformity; treat them as the floor, not the ceiling.
4. The Art.32 × NIS2 Art.21 Overlap
For operators in NIS2-covered sectors (cloud providers, managed service providers, health operators, banks, energy companies), both Art.32 and NIS2 Art.21 apply. The overlap is substantial but not identical.
| Measure | GDPR Art.32 | NIS2 Art.21 |
|---|---|---|
| Encryption | Named example | Part of Art.21(2)(h) "encryption and access control" |
| Incident handling | Implied by Art.32(1)(b)–(c) | Explicit: Art.21(2)(b) "incident handling" |
| Business continuity | Art.32(1)(c) restoration | Art.21(2)(c) "business continuity" |
| Supply chain security | Via Art.28 processor requirements | Explicit: Art.21(2)(d) "supply chain security" |
| Access control | Not named, but implied | Art.21(2)(i) "access control policies" |
| MFA | Not named | Art.21(2)(j) "use of multi-factor authentication" |
| Regular testing | Art.32(1)(d) | Art.21(2)(e) "security in network/IS acquisition, development, maintenance" |
| Vulnerability handling | Part of testing | Art.21(2)(e) and ENISA guidance |
Key difference: NIS2 Art.21 specifies minimum measures for the organisation's network and information systems — focused on operational continuity. GDPR Art.32 focuses on personal data security and is risk-proportional to data sensitivity, not sector classification.
Dual compliance strategy: implement NIS2 Art.21 as your security baseline (it is more prescriptive), and layer GDPR Art.32 personal-data-specific controls (encryption of personal data columns, pseudonymisation, personal-data-specific access logs) on top. A single integrated security programme covering both is more efficient than parallel compliance silos.
For incident reporting under dual coverage, see the companion guide on NIS2 + GDPR Dual Reporting.
5. Python Implementation: GDPR32ComplianceChecker
"""
GDPR Article 32 Technical Security Measures Compliance Checker
Validates four Art.32(1) measures for a given processing system.
"""
import hashlib
import hmac
import os
import json
import datetime
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class RiskLevel(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
VERY_HIGH = "very_high"
class ComplianceStatus(Enum):
COMPLIANT = "compliant"
PARTIAL = "partial"
NON_COMPLIANT = "non_compliant"
NOT_ASSESSED = "not_assessed"
@dataclass
class Art32Measure:
"""Represents one of the four Art.32(1) measures."""
article_ref: str
measure_name: str
status: ComplianceStatus
evidence: list[str] = field(default_factory=list)
gaps: list[str] = field(default_factory=list)
last_tested: Optional[str] = None
@dataclass
class ProcessingSystem:
"""Describes a data processing system for Art.32 assessment."""
system_name: str
risk_level: RiskLevel
processes_special_category: bool
estimated_data_subjects: int
# Art.32(1)(a) - Pseudonymisation and encryption
encryption_at_rest: bool = False
encryption_in_transit: bool = False
pseudonymisation_implemented: bool = False
key_rotation_days: Optional[int] = None
# Art.32(1)(b) - CIA + Resilience
rbac_implemented: bool = False
mfa_enforced: bool = False
audit_logging: bool = False
multi_az_deployment: bool = False
circuit_breakers: bool = False
# Art.32(1)(c) - Restoration
automated_backups: bool = False
backup_tested_days_ago: Optional[int] = None
geo_separated_backups: bool = False
rto_documented: bool = False
rpo_documented: bool = False
# Art.32(1)(d) - Regular testing
last_pentest_days_ago: Optional[int] = None
vulnerability_scanning_in_cicd: bool = False
access_review_days_ago: Optional[int] = None
incident_drills_conducted: bool = False
class GDPR32ComplianceChecker:
"""
Evaluates Art.32 compliance for a given processing system.
Applies risk-proportional thresholds per Art.32(2).
"""
# Risk-proportional thresholds (days)
PENTEST_THRESHOLDS = {
RiskLevel.LOW: 365,
RiskLevel.MEDIUM: 180,
RiskLevel.HIGH: 90,
RiskLevel.VERY_HIGH: 90,
}
ACCESS_REVIEW_THRESHOLDS = {
RiskLevel.LOW: 365,
RiskLevel.MEDIUM: 180,
RiskLevel.HIGH: 90,
RiskLevel.VERY_HIGH: 60,
}
BACKUP_TEST_THRESHOLDS = {
RiskLevel.LOW: 365,
RiskLevel.MEDIUM: 180,
RiskLevel.HIGH: 90,
RiskLevel.VERY_HIGH: 30,
}
def assess(self, system: ProcessingSystem) -> dict:
measures = []
# Art.32(1)(a): Pseudonymisation and encryption
enc_measure = self._assess_encryption(system)
measures.append(enc_measure)
# Art.32(1)(b): CIA + Resilience
cia_measure = self._assess_cia_resilience(system)
measures.append(cia_measure)
# Art.32(1)(c): Restoration capability
restore_measure = self._assess_restoration(system)
measures.append(restore_measure)
# Art.32(1)(d): Regular testing
testing_measure = self._assess_testing(system)
measures.append(testing_measure)
overall = self._calculate_overall(measures, system.risk_level)
return {
"system": system.system_name,
"risk_level": system.risk_level.value,
"assessment_date": datetime.date.today().isoformat(),
"overall_status": overall.value,
"measures": [
{
"ref": m.article_ref,
"name": m.measure_name,
"status": m.status.value,
"evidence": m.evidence,
"gaps": m.gaps,
}
for m in measures
],
"priority_gaps": self._priority_gaps(measures, system.risk_level),
}
def _assess_encryption(self, s: ProcessingSystem) -> Art32Measure:
evidence, gaps = [], []
if s.encryption_at_rest:
evidence.append("Encryption at rest: implemented")
else:
gaps.append("CRITICAL: No encryption at rest for personal data")
if s.encryption_in_transit:
evidence.append("Encryption in transit: TLS enforced")
else:
gaps.append("CRITICAL: Personal data transmitted without encryption")
if s.pseudonymisation_implemented:
evidence.append("Pseudonymisation: implemented")
elif s.processes_special_category:
gaps.append("HIGH: Special category data without pseudonymisation")
else:
gaps.append("MEDIUM: Pseudonymisation not implemented (recommended for Art.32)")
if s.key_rotation_days is not None:
if s.key_rotation_days <= 90:
evidence.append(f"Key rotation: every {s.key_rotation_days} days")
else:
gaps.append(f"MEDIUM: Key rotation {s.key_rotation_days}d exceeds 90-day best practice")
else:
gaps.append("MEDIUM: No key rotation schedule documented")
if not s.encryption_at_rest or not s.encryption_in_transit:
status = ComplianceStatus.NON_COMPLIANT
elif gaps:
status = ComplianceStatus.PARTIAL
else:
status = ComplianceStatus.COMPLIANT
return Art32Measure(
article_ref="Art.32(1)(a)",
measure_name="Pseudonymisation and Encryption",
status=status,
evidence=evidence,
gaps=gaps,
)
def _assess_cia_resilience(self, s: ProcessingSystem) -> Art32Measure:
evidence, gaps = [], []
critical_gaps = 0
if s.rbac_implemented:
evidence.append("RBAC/ABAC: access control implemented")
else:
gaps.append("CRITICAL: No role-based access control for personal data")
critical_gaps += 1
if s.mfa_enforced:
evidence.append("MFA: enforced for data access")
elif s.risk_level in (RiskLevel.HIGH, RiskLevel.VERY_HIGH):
gaps.append("HIGH: MFA not enforced for high-risk processing system")
else:
gaps.append("MEDIUM: MFA not enforced")
if s.audit_logging:
evidence.append("Audit logging: personal data access logged")
else:
gaps.append("HIGH: No audit log for personal data access events")
if s.multi_az_deployment:
evidence.append("Availability: multi-AZ deployment")
else:
gaps.append("MEDIUM: Single-AZ deployment — availability risk")
if s.circuit_breakers:
evidence.append("Resilience: circuit breakers implemented")
else:
gaps.append("LOW: No circuit breakers — resilience gap")
if critical_gaps > 0:
status = ComplianceStatus.NON_COMPLIANT
elif gaps:
status = ComplianceStatus.PARTIAL
else:
status = ComplianceStatus.COMPLIANT
return Art32Measure(
article_ref="Art.32(1)(b)",
measure_name="Confidentiality, Integrity, Availability, Resilience",
status=status,
evidence=evidence,
gaps=gaps,
)
def _assess_restoration(self, s: ProcessingSystem) -> Art32Measure:
evidence, gaps = [], []
threshold = self.BACKUP_TEST_THRESHOLDS[s.risk_level]
if s.automated_backups:
evidence.append("Backups: automated backup process in place")
else:
gaps.append("CRITICAL: No automated backups for personal data")
if s.backup_tested_days_ago is not None:
if s.backup_tested_days_ago <= threshold:
evidence.append(f"Backup testing: tested {s.backup_tested_days_ago}d ago (threshold {threshold}d)")
else:
gaps.append(f"HIGH: Backup last tested {s.backup_tested_days_ago}d ago (threshold {threshold}d for {s.risk_level.value} risk)")
else:
gaps.append("HIGH: No backup restoration test ever conducted")
if s.geo_separated_backups:
evidence.append("Geo-separation: backups stored in separate region")
else:
gaps.append("MEDIUM: Backups not geographically separated — ransomware risk")
if s.rto_documented and s.rpo_documented:
evidence.append("RTO/RPO: documented and tested")
else:
gaps.append("MEDIUM: RTO/RPO not documented (required for Art.32(2) risk assessment)")
critical = not s.automated_backups
status = (
ComplianceStatus.NON_COMPLIANT if critical
else ComplianceStatus.PARTIAL if gaps
else ComplianceStatus.COMPLIANT
)
return Art32Measure(
article_ref="Art.32(1)(c)",
measure_name="Restoration Capability",
status=status,
evidence=evidence,
gaps=gaps,
)
def _assess_testing(self, s: ProcessingSystem) -> Art32Measure:
evidence, gaps = [], []
pentest_threshold = self.PENTEST_THRESHOLDS[s.risk_level]
access_threshold = self.ACCESS_REVIEW_THRESHOLDS[s.risk_level]
if s.last_pentest_days_ago is not None:
if s.last_pentest_days_ago <= pentest_threshold:
evidence.append(f"Pentest: conducted {s.last_pentest_days_ago}d ago")
else:
gaps.append(f"HIGH: Pentest {s.last_pentest_days_ago}d ago (threshold {pentest_threshold}d)")
else:
gaps.append("HIGH: No penetration test ever conducted")
if s.vulnerability_scanning_in_cicd:
evidence.append("Vulnerability scanning: integrated in CI/CD pipeline")
else:
gaps.append("HIGH: No automated vulnerability scanning in CI/CD")
if s.access_review_days_ago is not None:
if s.access_review_days_ago <= access_threshold:
evidence.append(f"Access review: conducted {s.access_review_days_ago}d ago")
else:
gaps.append(f"MEDIUM: Access review {s.access_review_days_ago}d ago (threshold {access_threshold}d)")
else:
gaps.append("MEDIUM: No access review conducted")
if s.incident_drills_conducted:
evidence.append("Incident drills: response exercises conducted")
else:
gaps.append("LOW: No incident response drills — Art.32(1)(d) evaluation gap")
status = (
ComplianceStatus.PARTIAL if gaps
else ComplianceStatus.COMPLIANT
)
return Art32Measure(
article_ref="Art.32(1)(d)",
measure_name="Regular Testing and Evaluation",
status=status,
evidence=evidence,
gaps=gaps,
)
def _calculate_overall(
self, measures: list[Art32Measure], risk: RiskLevel
) -> ComplianceStatus:
statuses = {m.status for m in measures}
if ComplianceStatus.NON_COMPLIANT in statuses:
return ComplianceStatus.NON_COMPLIANT
if ComplianceStatus.PARTIAL in statuses:
return ComplianceStatus.PARTIAL
return ComplianceStatus.COMPLIANT
def _priority_gaps(
self, measures: list[Art32Measure], risk: RiskLevel
) -> list[str]:
all_gaps = []
for m in measures:
all_gaps.extend(m.gaps)
critical = [g for g in all_gaps if g.startswith("CRITICAL")]
high = [g for g in all_gaps if g.startswith("HIGH")]
return critical + high[:3] # Top critical + top 3 high
# Example: SaaS platform processing health data
checker = GDPR32ComplianceChecker()
health_platform = ProcessingSystem(
system_name="HealthTrack SaaS — EU Production",
risk_level=RiskLevel.HIGH,
processes_special_category=True,
estimated_data_subjects=250_000,
encryption_at_rest=True,
encryption_in_transit=True,
pseudonymisation_implemented=True,
key_rotation_days=90,
rbac_implemented=True,
mfa_enforced=True,
audit_logging=True,
multi_az_deployment=True,
circuit_breakers=False,
automated_backups=True,
backup_tested_days_ago=45,
geo_separated_backups=True,
rto_documented=True,
rpo_documented=True,
last_pentest_days_ago=75,
vulnerability_scanning_in_cicd=True,
access_review_days_ago=60,
incident_drills_conducted=True,
)
result = checker.assess(health_platform)
print(json.dumps(result, indent=2))
Sample output for the health platform:
{
"system": "HealthTrack SaaS — EU Production",
"risk_level": "high",
"assessment_date": "2026-04-16",
"overall_status": "partial",
"measures": [
{
"ref": "Art.32(1)(a)",
"name": "Pseudonymisation and Encryption",
"status": "compliant",
"evidence": ["Encryption at rest: implemented", "Encryption in transit: TLS enforced",
"Pseudonymisation: implemented", "Key rotation: every 90 days"],
"gaps": []
},
{
"ref": "Art.32(1)(b)",
"name": "Confidentiality, Integrity, Availability, Resilience",
"status": "partial",
"evidence": ["RBAC/ABAC: access control implemented", "MFA: enforced", "Audit logging: implemented", "Availability: multi-AZ"],
"gaps": ["LOW: No circuit breakers — resilience gap"]
},
...
],
"priority_gaps": []
}
6. Art.32 in the Context of a DPIA
When a processing activity requires a Data Protection Impact Assessment (DPIA) under Art.35, the Art.32 security analysis feeds directly into the DPIA's risk treatment section. The DPIA must:
- Describe the processing operation and its purposes (Art.35(7)(a))
- Assess the necessity and proportionality (Art.35(7)(b))
- Assess the risks to data subjects (Art.35(7)(c))
- Describe the measures envisaged to address those risks, including Art.32 technical measures (Art.35(7)(d))
Use the GDPR32ComplianceChecker output as the technical annex to your DPIA. Each measure's status (compliant/partial/non-compliant) maps to residual risk level, which must be acceptable before high-risk processing begins.
For combined DPIA + FRIA documentation, see the DPIA and FRIA Combined Developer Guide.
7. Supervisory Authority Guidance (2026)
Key DPA guidance interpreting Art.32 as of 2026:
EDPB Guidelines 4/2019 (pseudonymisation): distinguishes pseudonymisation from anonymisation; confirms that pseudonymised data remains personal data. Encryption of the pseudonymisation key is required to prevent re-identification.
ICO (UK): "security of personal data" guidance applies post-Brexit UK GDPR with nearly identical Art.32 text. UK ICO enforcement has consistently found that failure to encrypt personal data laptops, databases, or email attachments is an Art.32 violation.
CNIL (France): publishes minimum security requirements (recommendations 2024) including: TLS 1.2+ mandatory, database-at-rest encryption mandatory for special category data, annual penetration testing recommended.
BSI (Germany): C5 cloud security catalogue aligns with NIS2 Art.21 and serves as evidence of Art.32 compliance for cloud processors in Germany.
ENISA Guidelines on Security Measures: ENISA periodically publishes sector-specific guidelines (telecom, smart grids, health) that translate Art.32 into sector-appropriate controls. These are non-binding but demonstrate "state of the art."
8. Art.32 Violation Consequences
Art.32 violations are subject to Tier 2 fines under Art.83(4): up to €10 million or 2% of global annual turnover, whichever is higher. In practice, supervisory authorities have issued significant fines specifically for Art.32 failures:
- British Airways (2020): £20M for inadequate security controls leading to 500,000 customer record breach
- H&M (2020): €35.3M (Germany) — including Art.32 failure for internal employee surveillance data
- Marriott International: £18.4M for inadequate technical measures protecting 7M EU data subjects
- Romanian DPA / Raiffeisen Bank (2022): failure to encrypt personal data on internal systems
Common Art.32 enforcement triggers:
- Personal data breach that reveals inadequate encryption (automatically triggers DPA investigation)
- Employee accessing data beyond their role (RBAC/access control failure)
- No data backup → inability to restore after ransomware (Art.32(1)(c))
- Failed penetration test report that was not acted upon
9. Art.32 Developer Checklist (25 Items)
Art.32(1)(a) — Pseudonymisation and Encryption
- Personal data encrypted at rest (AES-256 GCM minimum)
- Personal data encrypted in transit (TLS 1.3 preferred, TLS 1.2 minimum)
- No cleartext personal data in application logs
- Encryption keys stored separately from data (KMS / HSM)
- Key rotation schedule: ≤90 days symmetric, ≤1 year asymmetric
- Special category data pseudonymised (re-identification key held separately)
Art.32(1)(b) — CIA + Resilience
- RBAC/ABAC implemented: least-privilege access to personal data
- MFA enforced for admin access to personal data stores
- Audit log records every access to personal data (who, when, what)
- Audit logs are immutable (append-only, integrity-protected)
- Multi-AZ or equivalent redundancy for processing systems
- Network segmentation: personal data stores not directly reachable from internet
- No personal data in CI/CD pipelines, test environments, or developer logs
Art.32(1)(c) — Restoration
- Automated daily backups of personal data
- Backup integrity verified (hash or restore test)
- Backups geographically separated from primary (different region/AZ)
- Backup restore tested within last 90 days (high risk) / 180 days (medium risk)
- RTO and RPO documented and tested
- Backups ransomware-resistant (immutable object storage or air-gapped)
Art.32(1)(d) — Regular Testing
- Penetration test conducted within last 90 days (high risk) / 365 days (low risk)
- Automated vulnerability scanning in CI/CD (SAST + DAST + SCA)
- Quarterly access review: confirm who has access to personal data stores
- Annual DPIA review for high-risk processing activities
- Incident response plan tested (tabletop exercise minimum)
- Art.32 risk assessment documented and reviewed after material system changes
See Also
- NIS2 Art.23 + GDPR Art.33 Dual Reporting: Simultaneous Incident Notification
- NIS2 Essential Entity vs Important Entity Classification
- DORA Art.19 Major ICT Incident Reporting: 4h/24h/5-Day Developer Guide
- GDPR DPIA + EU AI Act FRIA Combined Developer Guide
- NIS2 Art.23 Incident Reporting: 24h/72h/1-Month Developer Guide