2026-04-18·15 min read·

GDPR Art.32: Security of Processing — Technical & Organizational Measures, Encryption & Developer Checklist (2026)

Post #444 in the sota.io EU Cyber Compliance Series

Article 32 is the GDPR's security mandate for every data controller and processor. Where Article 25 (Privacy by Design) is about building privacy in from the start, Article 32 is about maintaining security during ongoing operations. It applies to every organization — from a two-person SaaS to a hyperscaler — that processes personal data of EU residents, and it underpins supervisory authority fines across every GDPR enforcement action where a breach occurred.

The article does not prescribe a fixed list of controls. Instead it establishes a risk-based proportionality framework: the measures you implement must be appropriate to the risk. Cheap measures are not automatically adequate; expensive ones are not automatically required. The test is whether the chosen TOMs appropriately reduce the likelihood and severity of harm to data subjects.


Art.32 at a Glance

ParagraphRuleApplies To
Art.32(1)Implement appropriate TOMs considering state of the art, cost, nature, scope, context, purposes, and riskController + Processor
Art.32(1)(a)–(d)Four specific TOM categories (see below)Controller + Processor
Art.32(2)Assess risks: accidental/unlawful destruction, loss, alteration, unauthorised disclosure or accessController + Processor
Art.32(3)Adherence to Art.40 Codes of Conduct or Art.42 Certification may demonstrate complianceController + Processor
Art.32(4)Persons acting under controller/processor authority only process data on instructionController + Processor

The Four TOM Categories (Art.32(1)(a)–(d))

GDPR Art.32(1) enumerates four non-exhaustive TOM categories. These are not a closed list — they illustrate the minimum floor of expectation:

(a) Pseudonymisation and Encryption of Personal Data

Pseudonymisation (Art.4(5)): replacing direct identifiers with pseudonyms so data can no longer be attributed to a specific data subject without the use of additional information, which is kept separately. Encryption: rendering data unreadable without the correct key.

Developer obligations:

(b) Ongoing Confidentiality, Integrity, Availability, and Resilience

The CIA triad extended with resilience — the ability of systems to withstand and recover from failures, attacks, and disruptions.

Developer obligations:

(c) Ability to Restore Availability and Access in Timely Manner After Incident

This is the GDPR's business continuity obligation for personal data. After an incident — ransomware, hardware failure, accidental deletion — you must be able to restore access to personal data within a timeframe appropriate to the risk.

Developer obligations:

(d) Process for Regularly Testing, Assessing, and Evaluating Effectiveness of TOMs

Security is not a one-time configuration — it is an ongoing process. Art.32(1)(d) requires a documented, recurring review cycle.

Developer obligations:


Art.32(2): Risks to Account For

Art.32(2) specifies the risk categories controllers and processors must consider when calibrating their TOMs:

"In assessing the appropriate level of security account shall be taken in particular of the risks that are presented by processing, in particular from accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to, personal data transmitted, stored or otherwise processed."

Risk CategoryExamplesTOM Mitigation
Accidental destructionServer hardware failure, fire, floodGeo-redundant backups, disaster recovery plan
Unlawful destructionInsider threat, ransomwareImmutable backups, access logging, MFA
Accidental lossDeveloper deletes production tableBackup verification, change-control process
AlterationData corruption, malicious modificationIntegrity checks, audit trail, signed commits
Unauthorised disclosureData breach, misconfigured S3 bucketEncryption at rest, access control, DAST
Unauthorised accessCredential theft, privilege escalationMFA, RBAC, session management, zero-trust

Art.32(3): Codes of Conduct and Certification as Compliance Proof

Adherence to an approved Art.40 Code of Conduct or possession of an Art.42 certification may be used as an element to demonstrate compliance with Art.32 requirements. This is a compliance shortcut, not a safe harbour — supervisory authorities can still find inadequate security even with a certification if the specific risk was not addressed.

Relevant certifications and standards:

For small SaaS operators not yet certified, ENISA's Guidelines on Minimum Security Measures for Digital Service Providers and ENISA's Cloud Security Guide for SMEs provide a practical TOM baseline referenced by DPAs in enforcement.


Relationship to Other GDPR Articles

Art.32 does not operate in isolation:

Related ArticleRelationship
Art.24 — Controller ResponsibilityArt.24 is the overarching controller obligation to implement appropriate measures; Art.32 specifies the security subset
Art.25 — Privacy by Design/DefaultArt.25 requires TOMs at design time; Art.32 requires them at all times during processing. Overlapping but distinct obligations.
Art.28 — Processor AgreementsArt.28(3)(c) requires the DPA to impose Art.32 obligations on the processor. The controller must verify the processor's TOMs contractually.
Art.35 — DPIAA DPIA identifies risks; Art.32 requires mitigating them with TOMs. High-risk DPIA findings must be reflected in Art.32 measures.
Art.33 — Breach Notification (72h)Art.32(1)(c) restore obligation + Art.33 notification are the two operational responses to a security incident.
Art.34 — Communication to Data SubjectsRequired when a breach is likely to result in high risk — directly triggered when Art.32 TOMs were inadequate to prevent harm.
Art.83(4) — Administrative FinesArt.32 violations: up to €10 million or 2% of global annual turnover (whichever is higher).

ENISA TOM Recommendations (2026 Baseline)

ENISA's Recommendations on Security Measures for Article 32 of the GDPR (last updated 2024, referenced in 2026 enforcement actions) identify the following minimum baseline for SaaS and cloud processors:

Identity and Access:

Network Security:

Logging and Monitoring:

Vulnerability Management:

Data Minimisation and Masking:


EU-Hosted PaaS and Art.32

For developers choosing a PaaS provider to host applications processing personal data, Art.32 creates direct obligations on the processor (the PaaS). Under Art.28(3)(c), the PaaS must implement TOMs at least equivalent to those the controller has specified in the DPA.

EU-native PaaS providers (such as sota.io) offer a structural advantage: data sovereignty guarantees that personal data never leaves the EU, eliminating the class of Art.32 risks arising from cross-border transfer to jurisdictions with weaker security enforcement frameworks. GDPR enforcement in 2026 has increasingly cited processor TOM inadequacy in cross-border transfer scenarios as a compounding factor in breach fines — DPAs are looking not just at what happened but at whether the controller took adequate steps to ensure the processor's TOMs were sufficient.

Key questions to ask your PaaS provider for Art.32 compliance:


Enforcement Landscape (2026)

Art.32 violations are among the most frequently cited articles in DPA fines, often in conjunction with Art.33 (breach notification) and Art.5(1)(f) (integrity and confidentiality principle):

CaseDPAFineArt.32 Violation
Meta Ireland (2023)Irish DPA€91MPasswords stored in plaintext (no encryption at rest)
British Airways (UK, 2020)ICO£20MInadequate security controls; SQL injection vector
Marriott International (UK, 2020)ICO£18.4MAcquired vulnerable infrastructure; no due-diligence TOM review
CNIL vs. e-commerce operator (FR, 2022)CNIL€150,000No TLS 1.2 minimum; personal data in HTTP URLs
German hospital (DE, 2023)BfDI€105,000No MFA on administrative access; inadequate logging
Dutch telecom (NL, 2021)AP€475,000Password reset via knowledge-based authentication only; no MFA

Pattern: encryption, access controls, and logging are the three most common Art.32 deficiencies cited in fines.


Python Implementation: SecurityAuditRecord

from dataclasses import dataclass, field
from datetime import date, datetime
from enum import Enum
from typing import Optional
import hashlib
import json

class EncryptionStatus(Enum):
    COMPLIANT = "compliant"           # AES-256 at rest + TLS 1.3 in transit
    PARTIAL = "partial"               # Encrypted in transit only
    NON_COMPLIANT = "non_compliant"   # Plaintext storage detected

class MFAStatus(Enum):
    ALL_PRIVILEGED = "all_privileged"  # MFA on all admin/DB/deploy accounts
    PARTIAL = "partial"               # MFA on some privileged accounts
    ABSENT = "absent"                 # No MFA

@dataclass
class TOMAssessment:
    """Art.32(1)(a)–(d) four-category TOM assessment."""
    encryption_at_rest: EncryptionStatus
    encryption_in_transit: EncryptionStatus
    pseudonymisation_in_use: bool
    mfa_status: MFAStatus
    rbac_implemented: bool
    backup_tested_rto_hours: Optional[int]    # None = not tested
    backup_offsite: bool
    pen_test_date: Optional[date]             # None = never tested
    vuln_scanning_in_ci: bool
    audit_logging_enabled: bool
    log_retention_days: int
    incident_response_plan: bool

    def art_32_1a_score(self) -> str:
        """Encryption and pseudonymisation (Art.32(1)(a))."""
        if (self.encryption_at_rest == EncryptionStatus.COMPLIANT and
                self.encryption_in_transit == EncryptionStatus.COMPLIANT and
                self.pseudonymisation_in_use):
            return "COMPLIANT"
        if (self.encryption_at_rest == EncryptionStatus.NON_COMPLIANT or
                self.encryption_in_transit == EncryptionStatus.NON_COMPLIANT):
            return "NON_COMPLIANT"
        return "PARTIAL"

    def art_32_1b_score(self) -> str:
        """Confidentiality, integrity, availability, resilience (Art.32(1)(b))."""
        if (self.mfa_status == MFAStatus.ALL_PRIVILEGED and
                self.rbac_implemented and
                self.audit_logging_enabled):
            return "COMPLIANT"
        if self.mfa_status == MFAStatus.ABSENT:
            return "NON_COMPLIANT"
        return "PARTIAL"

    def art_32_1c_score(self) -> str:
        """Restore availability after incident (Art.32(1)(c))."""
        if (self.backup_offsite and
                self.backup_tested_rto_hours is not None and
                self.backup_tested_rto_hours <= 4 and
                self.incident_response_plan):
            return "COMPLIANT"
        if not self.backup_offsite or not self.incident_response_plan:
            return "NON_COMPLIANT"
        return "PARTIAL"

    def art_32_1d_score(self) -> str:
        """Regular testing and evaluation of TOMs (Art.32(1)(d))."""
        pen_test_current = (
            self.pen_test_date is not None and
            (date.today() - self.pen_test_date).days <= 365
        )
        if (pen_test_current and
                self.vuln_scanning_in_ci and
                self.log_retention_days >= 365):
            return "COMPLIANT"
        if not self.vuln_scanning_in_ci and self.pen_test_date is None:
            return "NON_COMPLIANT"
        return "PARTIAL"


@dataclass
class SecurityAuditRecord:
    """Art.32 GDPR security audit record — produce annually or after significant changes."""
    system_name: str
    data_controller: str
    audit_date: date
    tom_assessment: TOMAssessment
    auditor: str
    certifications: list[str] = field(default_factory=list)  # ["ISO 27001", "BSI C5"]
    open_findings: list[str] = field(default_factory=list)
    remediation_deadline: Optional[date] = None

    def overall_status(self) -> str:
        scores = [
            self.tom_assessment.art_32_1a_score(),
            self.tom_assessment.art_32_1b_score(),
            self.tom_assessment.art_32_1c_score(),
            self.tom_assessment.art_32_1d_score(),
        ]
        if all(s == "COMPLIANT" for s in scores):
            return "ART_32_COMPLIANT"
        if any(s == "NON_COMPLIANT" for s in scores):
            return "ART_32_NON_COMPLIANT"
        return "ART_32_PARTIAL"

    def generate_audit_hash(self) -> str:
        """Tamper-evident hash of audit record for audit trail."""
        record_str = json.dumps({
            "system": self.system_name,
            "date": str(self.audit_date),
            "status": self.overall_status(),
            "auditor": self.auditor,
        }, sort_keys=True)
        return hashlib.sha256(record_str.encode()).hexdigest()[:16]

    def to_report(self) -> dict:
        return {
            "audit_id": f"ART32-{self.audit_date.strftime('%Y%m%d')}-{self.generate_audit_hash()}",
            "system": self.system_name,
            "controller": self.data_controller,
            "audit_date": str(self.audit_date),
            "overall": self.overall_status(),
            "tom_scores": {
                "32_1a_encryption_pseudonymisation": self.tom_assessment.art_32_1a_score(),
                "32_1b_cia_resilience": self.tom_assessment.art_32_1b_score(),
                "32_1c_restore_after_incident": self.tom_assessment.art_32_1c_score(),
                "32_1d_regular_testing": self.tom_assessment.art_32_1d_score(),
            },
            "certifications": self.certifications,
            "open_findings": self.open_findings,
            "remediation_deadline": str(self.remediation_deadline) if self.remediation_deadline else None,
        }


# Example: SaaS startup processing user personal data
assessment = TOMAssessment(
    encryption_at_rest=EncryptionStatus.COMPLIANT,
    encryption_in_transit=EncryptionStatus.COMPLIANT,
    pseudonymisation_in_use=True,
    mfa_status=MFAStatus.ALL_PRIVILEGED,
    rbac_implemented=True,
    backup_tested_rto_hours=2,
    backup_offsite=True,
    pen_test_date=date(2026, 2, 15),
    vuln_scanning_in_ci=True,
    audit_logging_enabled=True,
    log_retention_days=365,
    incident_response_plan=True,
)

audit = SecurityAuditRecord(
    system_name="UserDataPlatform",
    data_controller="Acme GmbH",
    audit_date=date(2026, 4, 18),
    tom_assessment=assessment,
    auditor="External Security Partner GmbH",
    certifications=["ISO 27001:2022"],
    open_findings=[],
)

import pprint
pprint.pprint(audit.to_report())
# Output: {audit_id: ART32-20260418-..., overall: ART_32_COMPLIANT, ...}

Developer Checklist: GDPR Art.32 Compliance

Use this checklist for each system processing personal data. Revisit annually and after significant architecture changes.

Encryption (Art.32(1)(a))

Access Control (Art.32(1)(b))

Availability and Resilience (Art.32(1)(b)+(c))

Ongoing Testing (Art.32(1)(d))

Documentation


Common Developer Mistakes

1. Encrypting in transit but not at rest. TLS protects data moving across the network; it does nothing for data sitting in a database or backup. Both layers are required under Art.32(1)(a).

2. No MFA on database administrative access. Production database credentials stolen via phishing or credential stuffing is the single most common Art.32 violation. MFA on the infrastructure layer (SSH, cloud console, database management tool) blocks the majority of these attacks.

3. Backups stored in the same account / region as production. Ransomware targeting your primary storage will also target backups in the same AWS account. Offsite means a different account or provider — ideally air-gapped.

4. Never testing the restore procedure. A backup that has never been restored is not a backup — it's an assumption. Art.32(1)(c) requires the ability to restore, which requires evidence that restoration works.

5. Production personal data in development environments. Developers who copy production data to local machines or staging environments to debug issues create an uncontrolled personal data store with no Art.32 controls. Use synthetic or pseudonymised data in all non-production environments.

6. Long-lived API keys as service credentials. Service accounts authenticating with static API keys stored in environment variables or source code are one accidental git commit away from a breach. Use workload identity, OIDC tokens, or secrets managers with short-lived credentials.


Summary

GDPR Art.32 requires every controller and processor to implement appropriate technical and organizational measures — calibrated to risk — across four categories: encryption and pseudonymisation, CIA+resilience, restore capability, and regular testing. The article is risk-based: there is no universal minimum set of controls, but encryption at rest and in transit, MFA on privileged access, offsite tested backups, and documented security testing are the baseline expectations confirmed by ENISA guidance and DPA enforcement patterns.

For developers building on EU-sovereign infrastructure, Art.32 compliance integrates naturally with a PaaS provider that already implements the infrastructure-layer TOMs (encryption, resilience, access controls) and documents them in the Art.28 DPA — leaving the application layer (RBAC, audit logging, security testing) as the developer's responsibility.

Related posts: