Scala'da Dijital İmza ve Belge Kimlik Doğrulaması
2024 yılında küresel elektronik imza pazarı 5 milyar doları aştı, 2030 yılına kadar yıllık %30'un üzerinde büyüme bekleniyor. Ancak piyasa rakamlarının ötesinde, dijital imzaların kitlesel olarak benimsenmesi somut teknik zorluklar doğuruyor: milyonların nasıl yönetileceği Birden fazla yargı bölgesinde yasal geçerliliği korurken günlük imza sayısı nedir? Nasıl uygulanır? Avrupa'da eIDAS 2.0 ve dünyanın geri kalanındaki eşdeğer düzenlemelerle uyumlu ölçeklenebilir PKI sistemi?
Bu makalede kurumsal düzeyde bir dijital imza sistemi oluşturuyoruz: PKI sertifikalarının oluşturulması ve yönetimi, imza iş akışlarının uygulanması çok adımlı, RFC 3161'e kadar uyumlu zaman damgası ve uzun vadeli arşivleme Avrupa standartlarına göre. Kod, entegrasyon örnekleriyle birlikte Python ve TypeScript'tedir Açısal uygulamalar için.
Ne Öğreneceksiniz
- eIDAS 2.0'a göre elektronik imza türleri (SES, AES, QES)
- PKI mimarisi: CA, RA, X.509 sertifikaları, CRL ve OCSP
- PyHanko (Python) ile PDF imza uygulaması
- Varlık kanıtı için RFC 3161 uyumlu zaman damgası
- Makine durumuna sahip çok taraflı imza iş akışı
- DocuSign/Adobe Sign API ile açısal entegrasyon
- Uzun vadeli arşivleme (LTV - Uzun Süreli Doğrulama)
eIDAS 2.0'a Göre Elektronik İmza Seviyeleri
20 Mayıs 2024'te yürürlüğe giren eIDAS Yönetmeliği (AB) 2024/1183, Farklı güvenlik gereksinimlerine ve yasal değere sahip üç düzeyde elektronik imza:
| Tip | Kısaltma | Gereksinimler | Yasal değer | Kullanım örneği |
|---|---|---|---|---|
| Basit Elektronik İmza | SES | İmzalayanla ilişkili her türlü elektronik veri | Bas | Tıklama sarma, e-posta onayı |
| Gelişmiş Elektronik İmza | AES | İmza sahibine benzersiz bir şekilde atıfta bulunulabilir, onun kontrolü altındaki verilerle oluşturulmuştur | Orta | Ticari sözleşmeler, İK |
| Nitelikli Elektronik İmza | QES | Nitelikli Sertifika + QSCD (Nitelikli İmza Oluşturma Cihazı) | Eşdeğer el yazısı imza | Noter tasdik işlemleri, gayrimenkul sözleşmeleri |
eIDAS 2.0 ve Dijital Kimlik Cüzdanı
Aralık 2026'dan itibaren 27 AB üye devletinin tümü vatandaşlarına AB Dijital Kimlik Cüzdanı (EUDI Cüzdanı) QES imzalarına izin veren mobil cihazda. Bu, kullanıcının katılımını temelden değiştirecek dijital imza sistemleri için: USB donanım belirteçlerine elveda, akıllı telefonlara merhaba.
Dijital İmza için PKI Mimarisi
Açık Anahtar Altyapısı (PKI) ve bunun güvence altına aldığı şifreleme altyapısı Dijital imzaların özgünlüğü ve bütünlüğü. Temel bileşenler şunlardır:
- Kök CA (Sertifika Yetkilisi): hiyerarşinin güvenilir dayanağı. Ara sertifikalar verir. Maksimum güvenlik için çevrimdışı (hava boşluklu) olmalıdır.
- Orta düzey CA: Son kullanıcılara sertifikalar veren operasyonel CA.
- RA (Kayıt Yetkilisi): başvuranın kimliğini doğrular Sertifikanın verilmesine izin vermeden önce.
- OCSP Yanıtlayıcıları: Bir sertifikanın olup olmadığını doğrulamak için gerçek zamanlı hizmet ve iptal edildi (CRL'ye alternatif).
- TSA (Zaman Damgası Yetkilisi): RFC 3161 nitelikli zaman damgalarının çıktısını alır.
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
import datetime
import uuid
class PKIManager:
"""
Gestore PKI per la creazione di certificati X.509 self-signed e firmati da CA.
Per uso in sviluppo/test. In produzione usare una CA qualificata eIDAS.
"""
def generate_key_pair(self, key_size: int = 4096):
"""Genera coppia di chiavi RSA."""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=key_size,
backend=default_backend()
)
return private_key, private_key.public_key()
def create_root_ca(self, subject_name: str, validity_years: int = 20):
"""
Crea un certificato Root CA self-signed.
Normalmente questa operazione viene eseguita offline.
"""
private_key, public_key = self.generate_key_pair()
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "IT"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, subject_name),
x509.NameAttribute(NameOID.COMMON_NAME, f"{subject_name} Root CA"),
])
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365 * validity_years)
)
.add_extension(
x509.BasicConstraints(ca=True, path_length=1),
critical=True
)
.add_extension(
x509.KeyUsage(
digital_signature=True, key_cert_sign=True,
crl_sign=True, content_commitment=False,
key_encipherment=False, data_encipherment=False,
key_agreement=False, encipher_only=False, decipher_only=False
),
critical=True
)
.sign(private_key, hashes.SHA256(), default_backend())
)
return cert, private_key
def create_end_entity_certificate(
self,
subject_cn: str,
subject_email: str,
ca_cert,
ca_private_key,
validity_days: int = 365
):
"""
Crea un certificato end-entity firmato dalla CA.
Usato per la firma digitale dei documenti.
"""
user_private_key, user_public_key = self.generate_key_pair(key_size=2048)
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "IT"),
x509.NameAttribute(NameOID.COMMON_NAME, subject_cn),
x509.NameAttribute(NameOID.EMAIL_ADDRESS, subject_email),
])
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(ca_cert.subject)
.public_key(user_public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=validity_days)
)
.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True
)
.add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.EMAIL_PROTECTION,
# OID per firma documenti: 1.2.840.113549.1.9.15 (non standard)
]),
critical=False
)
.add_extension(
x509.SubjectAlternativeName([
x509.RFC822Name(subject_email),
]),
critical=False
)
.sign(ca_private_key, hashes.SHA256(), default_backend())
)
return cert, user_private_key
PDF'yi PyHanko ile imzalayın
PyHanko, PDF belgelerini dijital olarak imzalamak için referans Python kütüphanesidir PDF/A ve PAdES (PDF Gelişmiş Elektronik İmza) standartlarına göre. Destek görünmez, görünür imzalar, etkileşimli imza alanları ve entegre zaman damgası.
from pyhanko.sign import signers, fields
from pyhanko.sign.fields import MDPPerm
from pyhanko import stamp
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign.timestamps import HTTPTimeStamper
from pyhanko.sign.validation import validate_pdf_signature
from pyhanko.pdf_utils.reader import PdfFileReader
import hashlib
from io import BytesIO
class PDFSigningService:
"""
Servizio di firma PDF con supporto PAdES e timestamping RFC 3161.
"""
def __init__(
self,
cert_pem_path: str,
key_pem_path: str,
ca_chain_pem_path: str,
tsa_url: str = "http://timestamp.digicert.com"
):
# Carica certificato e chiave privata
self.signer = signers.SimpleSigner.load(
cert_file=cert_pem_path,
key_file=key_pem_path,
ca_chain_files=[ca_chain_pem_path]
)
self.timestamper = HTTPTimeStamper(tsa_url)
def sign_document(
self,
input_pdf_bytes: bytes,
reason: str = "Approvazione contratto",
location: str = "Milano, Italia",
visible: bool = True,
page: int = 0,
add_timestamp: bool = True
) -> bytes:
"""
Firma un documento PDF e opzionalmente aggiunge un timestamp qualificato.
Restituisce il PDF firmato come bytes.
"""
writer = IncrementalPdfFileWriter(BytesIO(input_pdf_bytes))
if visible:
# Crea campo firma visibile nell'angolo in basso a destra dell'ultima pagina
fields.append_signature_field(
writer,
sig_field_spec=fields.SigFieldSpec(
sig_field_name="Signature1",
on_page=page,
box=(400, 50, 560, 110) # (x1, y1, x2, y2) in punti
)
)
# Configurazione del digest e firma
meta = signers.PdfSignatureMetadata(
field_name="Signature1",
reason=reason,
location=location,
certify=True,
certify_perm=MDPPerm.NO_CHANGES # impedisce modifiche post-firma
)
sign_result = signers.sign_pdf(
writer,
signature_meta=meta,
signer=self.signer,
timestamper=self.timestamper if add_timestamp else None,
in_place=False
)
return sign_result.getvalue()
def validate_signature(self, signed_pdf_bytes: bytes) -> dict:
"""
Valida tutte le firme in un documento PDF.
Restituisce un report strutturato per ogni firma.
"""
reader = PdfFileReader(BytesIO(signed_pdf_bytes))
validation_results = []
for sig_obj in reader.embedded_signatures:
val_status = validate_pdf_signature(sig_obj)
validation_results.append({
'field_name': sig_obj.field_name,
'signer_name': str(val_status.signing_cert.subject),
'signing_time': str(val_status.signer_reported_dt),
'timestamp_valid': val_status.timestamp_validity.valid if val_status.timestamp_validity else None,
'cert_valid': val_status.signing_cert_validity.valid,
'modification_on_unchanged': val_status.modification_level.name,
'intact': val_status.bottom_line # True = firma integra e valida
})
return {
'total_signatures': len(validation_results),
'all_valid': all(r['intact'] for r in validation_results),
'signatures': validation_results,
'document_hash_sha256': hashlib.sha256(signed_pdf_bytes).hexdigest()
}
Durum Makinesi ile Çok Taraflı İmza İş Akışı
Gerçek dünya senaryolarında, bir sözleşme genellikle birden fazla tarafın tek bir siparişte imza atmasını gerektirir kesin: önce iç hukuk müdürü, sonra müşteri, son olarak noter. Bu iş akışını sağlam bir şekilde yürütmek için bir durum makinesi uyguluyoruz.
from enum import Enum, auto
from dataclasses import dataclass, field
from typing import List, Optional, Callable
from datetime import datetime
import uuid
class SignatureStatus(Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
REJECTED = "rejected"
EXPIRED = "expired"
@dataclass
class SignatureRequest:
"""Una richiesta di firma per un singolo firmatario."""
request_id: str
signer_email: str
signer_name: str
order: int # ordine di firma (1, 2, 3...)
status: SignatureStatus = SignatureStatus.PENDING
signed_at: Optional[datetime] = None
rejection_reason: Optional[str] = None
@dataclass
class SigningWorkflow:
"""
Workflow di firma multi-party con ordinamento sequenziale.
"""
workflow_id: str
document_id: str
document_name: str
created_at: datetime
expires_at: datetime
signers: List[SignatureRequest]
current_signer_order: int = 1
status: SignatureStatus = SignatureStatus.IN_PROGRESS
audit_log: List[dict] = field(default_factory=list)
def get_current_signer(self) -> Optional[SignatureRequest]:
"""Restituisce il firmatario corrente."""
for signer in self.signers:
if signer.order == self.current_signer_order:
return signer
return None
def record_signature(
self,
signer_email: str,
signed_pdf_bytes: bytes,
validation_report: dict
) -> bool:
"""
Registra una firma e avanza il workflow al prossimo firmatario.
Returns True se il workflow e completato.
"""
current = self.get_current_signer()
if not current or current.signer_email != signer_email:
raise ValueError(f"Non e il turno di {signer_email} di firmare")
if not validation_report.get('all_valid'):
raise ValueError("Firma non valida secondo il report di validazione")
# Aggiorna stato del firmatario
current.status = SignatureStatus.COMPLETED
current.signed_at = datetime.utcnow()
# Log immutabile dell'evento
self.audit_log.append({
'event': 'signature_recorded',
'signer': signer_email,
'order': self.current_signer_order,
'timestamp': datetime.utcnow().isoformat(),
'doc_hash': validation_report.get('document_hash_sha256')
})
# Avanza al prossimo firmatario
self.current_signer_order += 1
next_signer = self.get_current_signer()
if next_signer is None:
# Tutti hanno firmato: workflow completato
self.status = SignatureStatus.COMPLETED
return True
# Notifica il prossimo firmatario
next_signer.status = SignatureStatus.IN_PROGRESS
return False
def reject(self, signer_email: str, reason: str):
"""Il firmatario corrente rifiuta di firmare."""
current = self.get_current_signer()
if current and current.signer_email == signer_email:
current.status = SignatureStatus.REJECTED
current.rejection_reason = reason
self.status = SignatureStatus.REJECTED
self.audit_log.append({
'event': 'signature_rejected',
'signer': signer_email,
'reason': reason,
'timestamp': datetime.utcnow().isoformat()
})
# Factory per creare workflow
def create_signing_workflow(
document_id: str,
document_name: str,
signers_ordered: List[dict],
validity_days: int = 30
) -> SigningWorkflow:
"""
Crea un workflow di firma dal documento e dalla lista di firmatari.
signers_ordered: [{'email': '...', 'name': '...'}, ...] in ordine di firma
"""
requests = [
SignatureRequest(
request_id=str(uuid.uuid4()),
signer_email=s['email'],
signer_name=s['name'],
order=idx + 1
)
for idx, s in enumerate(signers_ordered)
]
requests[0].status = SignatureStatus.IN_PROGRESS # Primo firmatario attivo
return SigningWorkflow(
workflow_id=str(uuid.uuid4()),
document_id=document_id,
document_name=document_name,
created_at=datetime.utcnow(),
expires_at=datetime.utcnow().replace(
day=datetime.utcnow().day + validity_days
),
signers=requests
)
Signature API ile açısal entegrasyon
Angular ön uç tarafından imza hizmetiyle entegrasyonun ele alınması gerekiyor yönlendirme akışı (kullanıcı imzalama platformuna getirilir ve ardından geri döner), veya iframe/SDK aracılığıyla doğrudan yerleştirme.
// signature.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface SigningSession {
sessionId: string;
signingUrl: string; // URL redirect alla piattaforma di firma
expiresAt: string;
documentId: string;
}
export interface SignatureStatus {
workflowId: string;
status: 'pending' | 'in_progress' | 'completed' | 'rejected';
completedSigners: number;
totalSigners: number;
nextSigner?: string;
completedAt?: string;
}
@Injectable({ providedIn: 'root' })
export class SignatureService {
private readonly http = inject(HttpClient);
private readonly apiBase = '/api/v1/signatures';
initiateSignature(documentId: string, signerEmail: string): Observable<SigningSession> {
return this.http.post<SigningSession>(
`{this.apiBase}/sessions`,
{ documentId, signerEmail }
);
}
getWorkflowStatus(workflowId: string): Observable<SignatureStatus> {
return this.http.get<SignatureStatus>(
`{this.apiBase}/workflows/{workflowId}/status`
);
}
downloadSignedDocument(workflowId: string): Observable<Blob> {
return this.http.get(
`{this.apiBase}/workflows/{workflowId}/document`,
{ responseType: 'blob' }
);
}
}
// document-signing.component.ts
import { Component, input, inject, signal } from '@angular/core';
import { SignatureService, SignatureStatus } from './signature.service';
@Component({
selector: 'app-document-signing',
template: `
<div class="signing-container">
@if (status() === 'idle') {
<button (click)="startSigning()">Firma il Documento</button>
}
@if (status() === 'loading') {
<div class="spinner">Preparazione firma in corso...</div>
}
@if (status() === 'completed') {
<div class="success">
<p>Documento firmato con successo!</p>
<button (click)="downloadSigned()">Scarica PDF firmato</button>
</div>
}
</div>
`
})
export class DocumentSigningComponent {
documentId = input.required<string>();
workflowId = input.required<string>();
private sigService = inject(SignatureService);
status = signal<'idle' | 'loading' | 'redirect' | 'completed' | 'error'>('idle');
startSigning(): void {
this.status.set('loading');
this.sigService.initiateSignature(this.documentId(), 'user@example.com').subscribe({
next: (session) => {
// Redirect alla piattaforma di firma (DocuSign, YouSign, etc.)
window.location.href = session.signingUrl;
},
error: () => this.status.set('error')
});
}
downloadSigned(): void {
this.sigService.downloadSignedDocument(this.workflowId()).subscribe(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'documento-firmato.pdf';
a.click();
URL.revokeObjectURL(url);
});
}
}
Uzun Vadeli Doğrulama (LTV) ve Arşivleme
Dijital imzanın yalnızca bugün değil, 10 ya da 20 yıl sonra da doğrulanabilir olması gerekiyor. orijinal sertifikaların süresi dolduğunda veya şifreleme algoritması modası geçmiş. Orada Uzun Vadeli Doğrulama (LTV) bu sorunu çözer Doğrulama için gerekli tüm bilgilerin imzalı belgeye dahil edilmesi gelecek: sertifika zinciri, OCSP yanıtları ve zaman damgaları.
Uzun Vadeli Arşivleme Standartları
- PAdES-LTV: Sertifika yerleştirme ve OCSP yanıtıyla PDF imzası
- XAdES-A: Periyodik arşiv zaman damgalarına sahip XML imzası
- CAdES-A: Arşiv zaman damgalarına sahip CMS imzası
- ASiC-E: İmza + belge + meta veriler içeren ZIP kapsayıcısı
Geçerliliği 10 yıldan fazla olan yasal belgeler için yeniden zaman damgası uygulanması önerilir SHA-256 geçerli hale gelmeden önce kriptografik gücü güncellemek için periyodik olarak (her 5 yılda bir) modası geçmiş.
Güvenlik Hususları
Kritik Güvenlik Noktaları
- Özel anahtar koruması: asla özel anahtarlar oluşturmayın veya saklamayın uygulama kodunda. HSM (Donanım Güvenliği Modülü) veya bulut KMS (AWS KMS, Azure Key Vault, Google Cloud KMS).
- Sertifikaların iptali: önlemek için OCSP zımbalamayı uygulayın Her imzada OCSP'yi gerçek zamanlı olarak kontrol edin; performans açısından kritik öneme sahiptir.
- Değişmez denetim günlüğü: her iş akışı olayı (imza, ret, süre sonu) Kurcalamanın tespit edilmesi için karma zinciriyle salt eklemeli bir günlüğe yazılmalıdır.
- İmza öncesi belge doğrulama: PDF'nin içermediğini kontrol edin Görüntülenen içeriği değiştirebilecek yerleşik JavaScript veya etkileşimli formlar.
Sonuçlar
Ölçeklenebilir, yasal olarak geçerli bir dijital imza sisteminin uygulanması çok şey gerektirir PDF'ye "imza eklemekten" daha fazlası. Yaşam döngüsü yönetimi PKI, eIDAS 2.0 uyumluluğu, çok taraflı iş akışları, zaman damgası ve uzun vadeli Doğrulama, baştan itibaren birlikte tasarlanması gereken bileşenlerdir.
Bu makaledeki kod, bir sistem oluşturmak için sağlam bir temel sağlar üretim. Kritik uygulamalar için (noterlik işlemleri, gayrimenkul sözleşmeleri, belgeler) şirketler), nitelikli TSP'lerle (Güven Hizmet Sağlayıcıları) entegrasyonu değerlendirin. Düzenleme karmaşıklığını yöneten Namirial, InfoCert, Aruba veya DocuSign gibi eIDAS 2.0'ın sizin için.
LegalTech ve AI serisi
- Sözleşme Analizi için NLP: OCR'den Anlamaya
- e-Keşif Platformu Mimarisi
- Dinamik Kural Motoru ile Uyumluluk Otomasyonu
- Yasal Anlaşmalar için Akıllı Sözleşme: Sağlamlık ve Vyper
- Üretken Yapay Zeka ile Yasal Belgelerin Özetlenmesi
- Arama Motoru Yasası: Vektör Yerleştirmeleri
- Scala'da Dijital İmza ve Belge Kimlik Doğrulaması (bu makale)
- Veri Gizliliği ve GDPR Uyumluluk Sistemleri
- Yasal Yapay Zeka Asistanı Oluşturma (Yasal Yardımcı Pilot)
- LegalTech Veri Entegrasyon Modeli







