Universally unique lexicographically sortable identifier with cryptographical offline integrity check and meta appendix support
Project description
RigID
Cryptographically secured ULIDs with built-in integrity verification
Rigid generates universally unique lexicographically sortable identifiers (ULIDs) enhanced with HMAC signatures for tamper detection. Perfect for applications that expose database IDs publicly while maintaining security and preventing enumeration attacks.
Features
- 🔒 HMAC-signed ULIDs - Every ID includes a cryptographic signature for integrity verification
- 🛡️ Tamper detection - Instantly detect if an ID has been modified or forged
- 📊 Tamper-proof metadata - Optionally bind additional context (user ID, resource type) protected by the same HMAC signature
- ⏰ Timestamp extraction - Extract creation timestamps from verified IDs
- 🚀 Minimal dependencies - Only requires
python-ulid - 🔐 Timing-attack resistant - Uses constant-time comparison for security
- 📈 Sortable & time-based - Maintains all ULID benefits (lexicographic ordering)
Use Cases
- Public API endpoints - Safely expose database IDs without enumeration risks
- URL parameters - Secure resource identifiers in web applications
- Distributed systems - Tamper-evident IDs across service boundaries
- Audit trails - Verify ID authenticity in logs and records
- Multi-tenant applications - Bind tenant context to prevent cross-tenant access
Quick Start
from rigid import Rigid
# Initialize with a secret key
rigid = Rigid(secret_key=b"your-secret-key")
# Generate a secure ULID
secure_id = rigid.generate()
print(secure_id)
# Output: 01HQZX9T4R8K2M3N7P5Q6S8V9W-A7B3F8K1
# Verify the ID integrity
is_valid, ulid_str, metadata = rigid.verify(secure_id)
print(f"Valid: {is_valid}, ULID: {ulid_str}")
# Output: Valid: True, ULID: 01HQZX9T4R8K2M3N7P5Q6S8V9W
Usage Examples
Basic ID Generation and Verification
from rigid import Rigid
# Initialize Rigid with your secret key
rigid = Rigid(secret_key=b"my-application-secret-key")
# Generate a secure ULID
secure_ulid = rigid.generate()
print(f"Generated: {secure_ulid}")
# Verify the integrity
is_valid, original_ulid, metadata = rigid.verify(secure_ulid)
if is_valid:
print(f"✅ Valid ID: {original_ulid}")
else:
print("❌ Invalid or tampered ID")
Binding Metadata to IDs
Bind additional context like user IDs, resource types, or tenant information. The metadata is cryptographically protected by the same HMAC signature, making it tamper-proof:
# Generate IDs with metadata
user_id = rigid.generate(metadata="user_12345")
order_id = rigid.generate(metadata="order:premium")
tenant_resource = rigid.generate(metadata="tenant_abc:document")
print(f"User ID: {user_id}")
# Output: 01HQZX9T4R8K2M3N7P5Q6S8V9W-A7B3F8K1-user_12345
# Verify and extract metadata
is_valid, ulid_str, extracted_metadata = rigid.verify(user_id)
if is_valid:
print(f"ULID: {ulid_str}")
print(f"Metadata: {extracted_metadata}")
# Output: ULID: 01HQZX9T4R8K2M3N7P5Q6S8V9W
# Metadata: user_12345
Timestamp Extraction
import time
# Generate an ID
secure_id = rigid.generate()
# Extract timestamp (Unix timestamp in seconds)
timestamp = rigid.extract_timestamp(secure_id)
if timestamp:
creation_time = time.ctime(timestamp)
print(f"ID created at: {creation_time}")
# Extract full ULID object
ulid_obj = rigid.extract_ulid(secure_id)
if ulid_obj:
print(f"ULID object: {ulid_obj}")
print(f"Timestamp: {ulid_obj.timestamp}")
print(f"Randomness: {ulid_obj.randomness}")
Custom Signature Length
# Use shorter signatures for space efficiency (less secure)
compact_rigid = Rigid(secret_key=b"secret", signature_length=4)
# Use longer signatures for higher security
secure_rigid = Rigid(secret_key=b"secret", signature_length=16)
compact_id = compact_rigid.generate()
secure_id = secure_rigid.generate()
print(f"Compact: {compact_id}") # Shorter signature
print(f"Secure: {secure_id}") # Longer signature
Web Application Example
from rigid import Rigid
from flask import Flask, jsonify, request
app = Flask(__name__)
rigid = Rigid(secret_key=b"your-web-app-secret")
@app.route('/users/<user_id>')
def get_user(user_id):
# Verify the user ID integrity
is_valid, ulid_str, metadata = rigid.verify(user_id)
if not is_valid:
return jsonify({"error": "Invalid user ID"}), 400
# Safe to use the verified ULID for database lookup
user = database.get_user_by_ulid(ulid_str)
return jsonify(user.to_dict())
@app.route('/users', methods=['POST'])
def create_user():
# Create user in database
user = create_new_user(request.json)
# Generate secure public ID
public_id = rigid.generate(metadata=f"user_{user.internal_id}")
return jsonify({
"public_id": public_id,
"user": user.to_dict()
})
Multi-Instance Compatibility
# Same secret key = compatible instances
rigid1 = Rigid(secret_key=b"shared-secret")
rigid2 = Rigid(secret_key=b"shared-secret")
# Generate with first instance
secure_id = rigid1.generate(metadata="shared_resource")
# Verify with second instance
is_valid, ulid_str, metadata = rigid2.verify(secure_id)
print(f"Cross-instance verification: {is_valid}")
# Output: Cross-instance verification: True
Error Handling
# Handle various error cases
test_cases = [
"invalid-format", # Malformed
"01HQZX9T4R8K2M3N7P5Q6S8V9W-WRONG", # Wrong signature
"INVALID_ULID-A7B3F8K1", # Invalid ULID
"01HQZX9T4R-TOO-MANY-PARTS-HERE", # Too many parts
]
for test_id in test_cases:
is_valid, ulid_str, metadata = rigid.verify(test_id)
print(f"ID: {test_id[:20]}... → Valid: {is_valid}")
# All outputs: Valid: False
Security Considerations
- Secret Key Management: Use a strong, unique secret key per application
- Key Rotation: Consider implementing key rotation for long-lived applications
- Signature Length: Balance between security (longer) and efficiency (shorter)
- Timing Attacks: Built-in constant-time verification prevents timing-based attacks
- Metadata Privacy: Be cautious about sensitive information in metadata
API Reference
Rigid(secret_key, signature_length=8)
Initialize a Rigid instance.
secret_key(bytes): Secret key for HMAC generationsignature_length(int): Number of signature bytes (default: 8)
generate(metadata=None) -> str
Generate a secure ULID with HMAC signature.
metadata(str, optional): Additional data to bind to the ID- Returns: Secure ULID string in format
ULID-SIGNATUREorULID-SIGNATURE-METADATA
verify(secure_ulid) -> tuple[bool, str | None, str | None]
Verify the integrity of a secure ULID.
secure_ulid(str): The secure ULID to verify- Returns: Tuple of (is_valid, ulid_str, metadata)
extract_ulid(secure_ulid) -> ULID | None
Extract the ULID object if valid.
secure_ulid(str): The secure ULID string- Returns: ULID object if valid, None otherwise
extract_timestamp(secure_ulid) -> float | None
Extract the timestamp from a secure ULID.
secure_ulid(str): The secure ULID string- Returns: Unix timestamp if valid, None otherwise
Requirements
- Python 3.11+
python-ulid>= 3.0.0
License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_rigid-0.1.1.tar.gz.
File metadata
- Download URL: python_rigid-0.1.1.tar.gz
- Upload date:
- Size: 18.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a217518bad9a0a934692fc86f0ad97004c73a0ca68409f1620a8a6d7f7bd404
|
|
| MD5 |
f612163ddb9e0d32dc3e83f2de9d28c2
|
|
| BLAKE2b-256 |
6334b05d367d81fc13c02df091418feb7e371e056e5903068c5fcbedc01b1db8
|
File details
Details for the file python_rigid-0.1.1-py3-none-any.whl.
File metadata
- Download URL: python_rigid-0.1.1-py3-none-any.whl
- Upload date:
- Size: 4.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8de82c37090e5345688131d377c362ffc86bf41dee3b19947ad2a2bc5f28b52
|
|
| MD5 |
a6513a3014b0a51bca31fae3340f6a14
|
|
| BLAKE2b-256 |
8b622a730488b79f5ae141a2d2ff4175698adc7e9e60cc2380d23b79dbd5e6d0
|