Migrating from python-xmlsec¶
Why migrate?¶
Zero C dependencies (no libxmlsec1, libxml2, OpenSSL)
No lxml version mismatch crashes
Single wheel, works on all platforms
Post-quantum algorithm support (ML-DSA, SLH-DSA)
Anti-XSW strict verification mode
Complete PEP 484 type stubs
Key differences¶
pybergshamra works with XML strings, not lxml
ElementnodesTemplates are built with pyuppsala’s
XmlWriter(or as raw XML strings)Constants use
Algorithm.RSA_SHA256instead ofxmlsec.Transform.RSA_SHA256Exceptions are typed (
CryptoError,KeyLoadError, etc.) instead of genericVerification returns a
VerifyResultobject instead of raising on failure
Key loading¶
xmlsec:
import xmlsec
# From file
key = xmlsec.Key.from_file("rsakey.pem", xmlsec.KeyFormat.PEM)
# From memory
key = xmlsec.Key.from_memory(pem_data, xmlsec.KeyFormat.PEM)
# With password
key = xmlsec.Key.from_file("key.pem", xmlsec.KeyFormat.PEM, password="secret")
# Load certificate onto key
key.load_cert("rsacert.pem", xmlsec.KeyFormat.CERT_PEM)
# PKCS#12
key = xmlsec.Key.from_file("key.p12", xmlsec.KeyFormat.PKCS12, password="pass")
# HMAC from file
key = xmlsec.Key.from_binary_file("hmackey.bin")
pybergshamra:
import pybergshamra
# From file (auto-detect format)
key = pybergshamra.load_key_file("rsakey.pem")
# From memory (explicit type)
key = pybergshamra.load_rsa_private_pem(pem_data)
# Auto-detect PEM type from memory
key = pybergshamra.load_pem_auto(pem_data, password="secret")
# With password
key = pybergshamra.load_key_file_with_password("key.pem", "secret")
# X.509 certificate
key = pybergshamra.load_x509_cert_pem(cert_pem_data)
# PKCS#12
key = pybergshamra.load_pkcs12(p12_data, "pass")
# HMAC from bytes
key = pybergshamra.load_hmac_key(hmac_bytes)
# AES key
key = pybergshamra.load_aes_key(os.urandom(32)) # replaces Key.generate()
KeysManager¶
xmlsec:
import xmlsec
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_file("rsakey.pem", xmlsec.KeyFormat.PEM)
manager.add_key(key)
pybergshamra:
import pybergshamra
manager = pybergshamra.KeysManager()
key = pybergshamra.load_key_file("rsakey.pem")
manager.add_key(key)
# pybergshamra adds typed lookups
rsa_key = manager.find_rsa()
ec_key = manager.find_ec_p256()
named = manager.find_by_name("my-key")
Certificate management¶
xmlsec:
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_file("cert.pem", xmlsec.KeyFormat.CERT_PEM)
manager.add_key(key)
pybergshamra:
manager = pybergshamra.KeysManager()
cert_der = open("cert.der", "rb").read()
manager.add_trusted_cert(cert_der)
# Or load as key for signing context
key = pybergshamra.load_x509_cert_pem(open("cert.pem", "rb").read())
manager.add_key(key)
Signature verification¶
xmlsec:
from lxml import etree
import xmlsec
doc = etree.parse("signed.xml")
root = doc.getroot()
signature_node = xmlsec.tree.find_node(root, xmlsec.Node.SIGNATURE)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_file("pubkey.pem", xmlsec.KeyFormat.PEM)
ctx.key = key
ctx.verify(signature_node) # raises on failure
pybergshamra:
import pybergshamra
xml = open("signed.xml").read()
manager = pybergshamra.KeysManager()
key = pybergshamra.load_rsa_public_pem(open("pubkey.pem", "rb").read())
manager.add_key(key)
ctx = pybergshamra.DsigContext(manager)
result = pybergshamra.verify(ctx, xml)
if result:
print("Valid!", result.key_info.algorithm)
else:
print("Invalid:", result.reason)
Signature verification with ID registration¶
xmlsec:
from lxml import etree
import xmlsec
doc = etree.fromstring(saml_xml)
signature_node = xmlsec.tree.find_node(doc, xmlsec.Node.SIGNATURE)
ctx = xmlsec.SignatureContext()
ctx.register_id(doc, "ID") # register on specific node
ctx.key = xmlsec.Key.from_file("idp-cert.pem", xmlsec.KeyFormat.CERT_PEM)
ctx.verify(signature_node)
pybergshamra:
import pybergshamra
manager = pybergshamra.KeysManager()
key = pybergshamra.load_x509_cert_pem(open("idp-cert.pem", "rb").read())
manager.add_key(key)
ctx = pybergshamra.DsigContext(manager)
ctx.add_id_attr("ID") # register attribute name globally
result = pybergshamra.verify(ctx, saml_xml)
Signing an XML document¶
xmlsec:
from lxml import etree
import xmlsec
# Parse template
root = etree.parse("document.xml").getroot()
# Build signature template programmatically
signature_node = xmlsec.template.create(
root, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA256
)
root.append(signature_node)
ref = xmlsec.template.add_reference(signature_node, xmlsec.Transform.SHA256)
xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
key_info = xmlsec.template.ensure_key_info(signature_node)
xmlsec.template.add_x509_data(key_info)
# Sign
ctx = xmlsec.SignatureContext()
ctx.key = xmlsec.Key.from_file("rsakey.pem", xmlsec.KeyFormat.PEM)
ctx.sign(signature_node)
print(etree.tostring(root, encoding="unicode"))
pybergshamra (with pyuppsala XmlWriter for template):
import pybergshamra
from pyuppsala import XmlWriter
# Build the template using pyuppsala's XmlWriter
document_xml = open("document.xml").read()
# ... (build template XML string with XmlWriter or as a raw string)
# Sign
manager = pybergshamra.KeysManager()
key = pybergshamra.load_rsa_private_pem(open("rsakey.pem", "rb").read())
manager.add_key(key)
ctx = pybergshamra.DsigContext(manager)
signed_xml = pybergshamra.sign(ctx, template_xml)
pybergshamra (with raw template string):
import pybergshamra
# Pre-built template (e.g. from a file or embedded string)
template = open("sign-template.xml").read()
manager = pybergshamra.KeysManager()
key = pybergshamra.load_rsa_private_pem(open("rsakey.pem", "rb").read())
manager.add_key(key)
ctx = pybergshamra.DsigContext(manager)
signed_xml = pybergshamra.sign(ctx, template)
XML Encryption¶
xmlsec:
from lxml import etree
import xmlsec
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_file("rsakey.pem", xmlsec.KeyFormat.PEM)
manager.add_key(key)
enc_ctx = xmlsec.EncryptionContext(manager)
enc_data = xmlsec.tree.find_child(root, "EncryptedData", xmlsec.constants.EncNs)
decrypted = enc_ctx.decrypt(enc_data)
pybergshamra:
import pybergshamra
manager = pybergshamra.KeysManager()
key = pybergshamra.load_rsa_private_pem(open("rsakey.pem", "rb").read())
manager.add_key(key)
ctx = pybergshamra.EncContext(manager)
decrypted_xml = pybergshamra.decrypt(ctx, encrypted_xml_string)
# Or get raw bytes (for non-UTF-8 content)
decrypted_bytes = pybergshamra.decrypt_to_bytes(ctx, encrypted_xml_string)
Encryption with template¶
xmlsec:
from lxml import etree
import xmlsec
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_file("rsacert.pem", xmlsec.KeyFormat.CERT_PEM)
manager.add_key(key)
# Build encryption template with xmlsec.template helpers
enc_data = xmlsec.template.encrypted_data_create(
root, xmlsec.Transform.AES128_CBC, type=xmlsec.constants.TypeEncContent
)
xmlsec.template.encrypted_data_ensure_cipher_value(enc_data)
key_info = xmlsec.template.encrypted_data_ensure_key_info(enc_data)
enc_key = xmlsec.template.add_encrypted_key(key_info, xmlsec.Transform.RSA_OAEP)
enc_ctx = xmlsec.EncryptionContext(manager)
enc_ctx.encrypt_xml(enc_data, element)
pybergshamra:
import pybergshamra
manager = pybergshamra.KeysManager()
key = pybergshamra.load_x509_cert_pem(open("rsacert.pem", "rb").read())
manager.add_key(key)
# Template built with pyuppsala XmlWriter or as raw XML string
template_xml = open("enc-template.xml").read()
ctx = pybergshamra.EncContext(manager)
encrypted_xml = pybergshamra.encrypt(ctx, template_xml, plaintext_bytes)
Canonicalization¶
xmlsec (via lxml):
from lxml import etree
# lxml provides C14N directly
root = etree.fromstring(xml_bytes)
result = etree.tostring(root, method="c14n2")
pybergshamra:
import pybergshamra
from pybergshamra import C14nMode
# Full document canonicalization
result = pybergshamra.canonicalize(xml_string, C14nMode.Exclusive)
# With inclusive namespace prefixes (common in SAML)
result = pybergshamra.canonicalize(
xml_string, C14nMode.Exclusive, inclusive_prefixes=["ds", "saml"]
)
# Subtree canonicalization by element ID
result = pybergshamra.canonicalize_subtree(
xml_string, "element-id", C14nMode.Exclusive
)
Algorithm constants¶
xmlsec:
import xmlsec
xmlsec.Transform.RSA_SHA256
xmlsec.Transform.EXCL_C14N
xmlsec.Transform.SHA256
xmlsec.Transform.ENVELOPED
pybergshamra:
from pybergshamra import Algorithm
Algorithm.RSA_SHA256
Algorithm.EXC_C14N
Algorithm.SHA256
Algorithm.ENVELOPED_SIGNATURE
# pybergshamra also has algorithms xmlsec lacks:
Algorithm.EDDSA_ED25519 # Ed25519
Algorithm.RSA_PSS_SHA256 # RSA-PSS
Algorithm.ML_DSA_65 # Post-quantum
Algorithm.AES256_GCM # AES-GCM
Algorithm.ECDH_ES # Key agreement
Symmetric key generation¶
xmlsec:
import xmlsec
key = xmlsec.Key.generate(xmlsec.KeyData.AES, 256)
pybergshamra:
import os
import pybergshamra
key = pybergshamra.load_aes_key(os.urandom(32)) # 256-bit AES
Error handling¶
xmlsec:
import xmlsec
try:
ctx.verify(sig_node)
except xmlsec.Error as e:
print(f"Verification failed: {e}")
pybergshamra:
import pybergshamra
from pybergshamra import CryptoError, KeyLoadError, XmlError
# Verification returns a result object (no exception on invalid signature)
result = pybergshamra.verify(ctx, xml)
if not result:
print(f"Invalid: {result.reason}")
# Typed exceptions for actual errors
try:
key = pybergshamra.load_rsa_private_pem(b"not a pem")
except KeyLoadError as e:
print(f"Key load failed: {e}")
try:
pybergshamra.verify(ctx, "<not valid xml")
except XmlError as e:
print(f"XML parse error: {e}")
Features only in pybergshamra¶
These features have no xmlsec equivalent:
import pybergshamra
from pybergshamra import Algorithm
# Post-quantum signatures (ML-DSA, SLH-DSA)
Algorithm.ML_DSA_44
Algorithm.ML_DSA_65
Algorithm.ML_DSA_87
Algorithm.SLH_DSA_SHA2_128F
# Ed25519 signing
key = pybergshamra.load_ed25519_private_pkcs8_der(der_bytes)
# X25519 key agreement
key = pybergshamra.load_x25519_private_raw(raw_32_bytes)
# Key derivation functions
derived = pybergshamra.pbkdf2_derive(password, salt, 100000, 32, Algorithm.HMAC_SHA256)
derived = pybergshamra.hkdf_derive(shared_secret, 32, salt=salt, info=info)
derived = pybergshamra.concat_kdf(shared_secret, 32)
# Standalone digest
h = pybergshamra.digest(Algorithm.SHA256, data)
# Certificate chain validation
pybergshamra.validate_cert_chain(
leaf_der, trusted_certs=[ca_der], skip_time_checks=True
)
# Anti-XSW strict verification
ctx = pybergshamra.DsigContext(manager)
ctx.strict_verification = True
# Key export
spki = key.to_spki_der()
raw = key.symmetric_key_bytes()
xml_fragment = key.to_key_value_xml()
# X509 KeyInfo builder
xml = pybergshamra.build_x509_key_info(["base64cert..."])