Implementing 21 CFR Part 11 Electronic Signatures

When a quality reviewer releases a batch, dispositions an excursion, or approves a corrective action, the record they sign must carry a signature that a regulator will treat as equivalent to a handwritten one. Under 21 CFR Part 11 that equivalence is not automatic; it is earned by a specific set of controls that bind an authenticated identity and a declared meaning to the exact record being signed, in a way the signer cannot later disown. This section builds those controls in production Python: it authenticates the signer, captures why they are signing, and cryptographically ties both to the record’s hash so the resulting signature is a permanent, non-repudiable manifestation. An electronic signature that omits any of these elements is a checkbox, not a Part 11 signature, and a checkbox is the first thing an inspector discredits.

Problem Statement: A Login Is Not a Signature

Cold chain platforms routinely conflate authentication with signing. A user logs in, clicks “Approve,” and a row is stamped with their username — but that stamp proves only that a session existed, not that a specific person deliberately signed a specific record for a specific reason. Part 11 draws a sharp line here. §11.50 requires that a signed electronic record display the signer’s printed name, the date and time of signing, and the meaning of the signature. §11.70 requires that the signature be linked to its record so it cannot be excised and transplanted onto another. §11.100 through §11.300 govern the identity controls behind the signature: uniqueness, identity verification, and, for non-biometric signatures, at least two distinct authentication components.

Three concrete gaps recur when these clauses are treated loosely:

  • The meaning is missing. A signature with no declared meaning cannot distinguish authorship from review from approval, so the same click means “I wrote this” and “I release this batch.” §11.50(a) requires the meaning to be part of the signed record.
  • The link is severable. A signature stored in a separate table keyed only by record ID can be copied, deleted, or re-pointed without breaking anything, violating the §11.70 requirement that the signature be bound so it cannot be excised, copied, or transferred.
  • The identity is not verifiably the signer’s. A shared service account or an unverified credential means the signature is attributable to a role, not a person, defeating §11.100’s uniqueness requirement and the two-component authentication expected under §11.200.

Closing these gaps is what this section does. The signature it produces binds to the record hash the audit stack already maintains, so a signed excursion disposition or batch release is anchored to the same tamper-evident chain that the audit trail export for regulatory submissions later packages for inspection. Downstream, that signature gates the quality actions handled by CAPA routing automation for temperature excursions and by automated batch invalidation and quarantine workflows, because no batch state should change without an attributable, meaningful signature behind it.

Concept & Specification: The Signed Manifestation

A Part 11 electronic signature is a structured object, not a scalar. It captures who signed, when, why, and against what, and it seals all four together with a keyed cryptographic operation so the bundle is both attributable and non-repudiable. The critical field is the one that binds the signature to its record: the signature is computed over a canonical body that includes the record’s hash, so a signature can never be verified against a different record than the one it was made for.

The persisted signature carries the following fields. The Regulatory anchor column states why each field is mandatory rather than decorative.

Field Type Constraint Regulatory anchor
record_hash string (sha256) Digest of the exact record being signed §11.70 signature/record linking
signer_id string Unique, verified individual identifier §11.100 uniqueness of identity
signer_printed_name string Human-readable name shown on the record §11.50(a)(1) printed name manifestation
signing_meaning enum AUTHORSHIP / REVIEW / APPROVAL / RESPONSIBILITY §11.50(a)(2) meaning of the signature
signed_at string (ISO 8601, UTC) Timezone-aware UTC, from a traceable clock §11.50(a)(3) date and time
auth_components integer ≥ 2 for non-biometric signatures §11.200(a)(1) two-component authentication
signature_value string (hmac-sha256) Keyed digest over the canonical body §11.70 non-severable binding
key_version string Signing-key identifier for rotation §11.300 credential control
prev_signature string (sha256) Links into the append-only signature trail §11.10(e) tamper-evident record

Two properties make the object defensible. First, signing_meaning is a required enumeration, not free text: an inspector can read, unambiguously, whether a signer claimed authorship, review, or release authority, satisfying §11.50(a)(2). Second, because signature_value is an HMAC over a canonical body that embeds record_hash, signer_id, signing_meaning, and signed_at together, none of those can be altered after signing without invalidating the signature — the §11.70 guarantee that the manifestation cannot be excised or transplanted.

Architecture Diagram: Identity to Bound Signature

The signing flow has three gates before any signature is produced. The signer’s identity is authenticated with two distinct components; the record to be signed is reduced to a canonical hash; and the signer’s declared meaning is captured. Only then is a keyed signature computed over the combined body and appended to the signature trail. Failing any gate stops the flow — an unauthenticated identity, an ambiguous meaning, or a missing record hash never yields a signature.

Binding an authenticated identity and signing meaning to a record hash Two authentication components establish a unique signer identity; the record is hashed and a signing meaning is declared; identity, hash, and meaning are combined and sealed with a keyed HMAC, then appended to a tamper-evident signature trail. SIGNATURE BINDING · §11.50 / §11.70 / §11.200 Identity token component 1 · §11.200 Knowledge factor component 2 · §11.200 Verified signer unique id · §11.100 signer_id Record hash SHA-256 of record Signing meaning approval · §11.50 Combine & seal canonical body HMAC-SHA256 §11.70 non-severable Signature trail append-only prev_signature §11.10(e)

Production Python Implementation

The module below issues and verifies Part 11 signatures. It refuses to sign when fewer than two authentication components are present, requires an explicit signing meaning, binds the signature to the record hash, and appends each signature into a tamper-evident trail. Each block cites the clause it discharges.

python
from __future__ import annotations

import hashlib
import hmac
import json
import os
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum

GENESIS = "0" * 64


class SigningMeaning(str, Enum):
    # §11.50(a)(2): the meaning must be explicit and part of the signed record.
    AUTHORSHIP = "AUTHORSHIP"
    REVIEW = "REVIEW"
    APPROVAL = "APPROVAL"
    RESPONSIBILITY = "RESPONSIBILITY"


class SignatureError(RuntimeError):
    """Raised when a signing precondition or verification fails."""


@dataclass(frozen=True)
class SignerIdentity:
    # §11.100(a): the identity must be unique to one individual and verified.
    signer_id: str
    printed_name: str
    auth_components: int  # count of distinct authentication factors presented

    def assert_two_component(self) -> None:
        # §11.200(a)(1): non-biometric signatures require at least two distinct
        # components (e.g. an id token plus a private knowledge factor).
        if self.auth_components < 2:
            raise SignatureError("two-component authentication required (§11.200)")
        if not self.signer_id or not self.printed_name:
            raise SignatureError("signer identity incomplete (§11.100)")


def _canonical(obj: dict) -> bytes:
    # Deterministic serialization so a verifier recomputes identical signed bytes.
    return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8")


def record_hash(record: dict) -> str:
    # §11.70: the signature binds to THIS record's digest, so it cannot later be
    # verified against a substituted record.
    return hashlib.sha256(_canonical(record)).hexdigest()


class SignatureAuthority:
    def __init__(self, signing_key: bytes, key_version: str):
        if len(signing_key) < 32:
            raise SignatureError("signing key must be at least 32 bytes")
        self._key = signing_key
        self._key_version = key_version

    def _signed_body(
        self,
        rhash: str,
        signer: SignerIdentity,
        meaning: SigningMeaning,
        signed_at: str,
    ) -> dict:
        # §11.50 requires printed name, date/time, and meaning to accompany the
        # record; embedding them in the SIGNED body makes them non-severable (§11.70).
        return {
            "record_hash": rhash,
            "signer_id": signer.signer_id,
            "signer_printed_name": signer.printed_name,
            "signing_meaning": meaning.value,
            "signed_at": signed_at,
            "auth_components": signer.auth_components,
            "key_version": self._key_version,
        }

    def sign(
        self,
        record: dict,
        signer: SignerIdentity,
        meaning: SigningMeaning,
        prev_signature: str = GENESIS,
    ) -> dict:
        signer.assert_two_component()  # §11.200 gate: no signature without two factors
        rhash = record_hash(record)
        # §11.50(a)(3): contemporaneous, timezone-aware UTC signing time.
        signed_at = datetime.now(timezone.utc).isoformat()
        body = self._signed_body(rhash, signer, meaning, signed_at)
        # §11.70: keyed HMAC over the whole body binds identity + meaning + record
        # so none can be excised, copied, or transplanted without detection.
        signature_value = hmac.new(
            self._key, _canonical(body), hashlib.sha256
        ).hexdigest()
        sig = dict(body)
        sig["signature_value"] = signature_value
        sig["prev_signature"] = prev_signature
        # §11.10(e): chain each signature to its predecessor for a tamper-evident trail.
        sig["signature_hash"] = hashlib.sha256(_canonical(sig)).hexdigest()
        return sig

    def verify(self, record: dict, sig: dict) -> bool:
        # Recompute the record hash and the keyed signature; both must match.
        try:
            expected_rhash = record_hash(record)
            if sig["record_hash"] != expected_rhash:  # §11.70 record substitution check
                return False
            body = {
                k: sig[k]
                for k in (
                    "record_hash",
                    "signer_id",
                    "signer_printed_name",
                    "signing_meaning",
                    "signed_at",
                    "auth_components",
                    "key_version",
                )
            }
            expected = hmac.new(
                self._key, _canonical(body), hashlib.sha256
            ).hexdigest()
        except KeyError:
            return False
        # Constant-time comparison prevents timing side channels on the signature.
        return hmac.compare_digest(expected, sig["signature_value"])


if __name__ == "__main__":
    # The signing key MUST come from a KMS/HSM or secrets manager — never inline.
    key = os.environb.get(b"PART11_SIGNING_KEY")
    if not key:
        raise SystemExit("PART11_SIGNING_KEY is required")
    authority = SignatureAuthority(key, key_version="v3")
    excursion_disposition = {
        "excursion_id": "EXC-88213",
        "batch_id": "BATCH-2026-0417",
        "disposition": "REJECT",
    }
    identity = SignerIdentity("qa.reviewer.014", "Dana Okafor", auth_components=2)
    signed = authority.sign(excursion_disposition, identity, SigningMeaning.APPROVAL)
    assert authority.verify(excursion_disposition, signed) is True

The signature never travels apart from what it commits to: because the record hash is inside the signed body, a stored signature carries its own proof of which record it belongs to. Applying this to a concrete quality event is covered in binding electronic signatures to excursion records, and the discipline of making the declared meaning enforceable and undeniable is developed in enforcing signature meaning and non-repudiation.

Configuration & Deployment

Manage the signing subsystem as a controlled component of your ICH Q10 pharmaceutical quality system. Identity verification, key custody, and meaning vocabularies are all subject to change control, and any change to them is a validation event.

Variable Example Purpose Regulatory anchor
PART11_SIGNING_KEY HSM-held 32-byte key Keyed signature material §11.300(b) credential control
PART11_KEY_VERSION v3 Rotation identifier stamped on each signature §11.300 periodic credential checks
PART11_MIN_AUTH_COMPONENTS 2 Enforced two-factor threshold §11.200(a)(1) two components
PART11_MEANING_VOCAB controlled enum set Permitted signing meanings §11.50(a)(2) meaning of signature
PART11_TIME_SOURCE NIST-disciplined NTP Trustworthy signed_at §11.50(a)(3) accurate date/time

Hold the signing key in an HSM or KMS and never let it touch application logs or source control. Rotate on a fixed schedule, retain superseded key versions so historical signatures remain verifiable, and record key_version on every signature. Under §11.300, credentials that authenticate signers must be periodically checked, recalled, and revised; wire deactivation of a departing employee’s identity into the same workflow that revokes their system access, so a stale credential can never produce a fresh signature.

Verification & Testing

Signing software governs batch release, so its tests are validation evidence. Exercise the controls an inspector will challenge:

  • Two-component enforcement. Assert sign raises SignatureError when auth_components is 1, proving the §11.200 gate cannot be bypassed.
  • Record-substitution tests. Sign record A, then call verify against record B and assert it returns False, demonstrating the §11.70 binding rejects a transplanted signature.
  • Meaning-required tests. Assert that a signature always carries one of the controlled SigningMeaning values and that free text cannot be substituted, satisfying §11.50(a)(2).
  • Tamper tests. Mutate signing_meaning on a stored signature and assert verify fails, proving the meaning is non-severable from the signature.
  • CSV IQ/OQ/PQ hooks. Load an Operational Qualification vector of records, identities, meanings, and expected signature_hash values so OQ can be executed and signed against documented expected outputs.

Known Failure Modes & Mitigations

Failure mode Symptom Mitigation Regulatory anchor
Shared service account signs Signature attributable to a role, not a person Require per-individual verified identity; block shared credentials §11.100 uniqueness
Signature stored separately from record Manifestation severable and transplantable Bind record hash inside the signed body §11.70 linking
Free-text meaning field Ambiguous authorship vs approval Constrain to a controlled meaning enumeration §11.50(a)(2) meaning
Single-factor login treated as signing Weak, repudiable signature Enforce two-component authentication at signing §11.200(a)(1)
Rotated key breaks old signatures Historical signatures fail verification Retain key versions; stamp key_version on each signature §11.300 credential control

When a signer disputes a signature, the investigation should reconstruct the signed body from the stored fields and recompute the HMAC. If it matches, the signature is non-repudiable evidence that the identity signed that record with that meaning at that time; if it does not, the record has been altered and the discrepancy is itself an integrity finding. Either outcome is defensible precisely because the meaning and identity were sealed with the record rather than recorded beside it.

Compliance Q&A

Is a username-and-password login enough to constitute a 21 CFR Part 11 electronic signature?

Only as one of the required elements, not as the whole signature. §11.200(a)(1) requires non-biometric electronic signatures to use at least two distinct authentication components, and §11.50 requires the signed record to display the signer’s printed name, the signing date and time, and the meaning of the signature. A password login proves a session existed; a Part 11 signature additionally binds a verified, unique identity and a declared meaning to the specific record being signed.

How does a signature satisfy the §11.70 requirement that it be linked to its record?

By computing the signature over a canonical body that includes the record’s SHA-256 hash. Because the digest of the exact record is inside the signed material, the signature can only ever verify against that record; it cannot be excised and re-attached to a different one without invalidating the keyed HMAC. Storing the signature in a separate table keyed only by record ID does not achieve this, since such a reference can be re-pointed without breaking anything.

Why must the signing meaning be a controlled value rather than free text?

Because §11.50(a)(2) requires the meaning associated with the signature to be clear and unambiguous, and free text lets the same action read as authorship, review, or approval depending on wording. A controlled enumeration such as AUTHORSHIP, REVIEW, APPROVAL, or RESPONSIBILITY lets an inspector read a signer’s exact claimed role directly from the record, and sealing that value inside the signed body makes it non-severable under §11.70.

For architectural context, this page sits under QMS integration, audit trails & automated batch disposition.