Log Decryption¶
Native Python decryption for encrypted Silo log entries. Requires pip install -e ".[decrypt]" (from SDK directory) for Standard encryption or .[legacy-decrypt] for seccure/secp256r1 support.
Ciphertext Layout (Standard)¶
Bytes 0–11 : 12-byte GCM nonce (IV)
Bytes 12–102 : 91-byte DER-encoded ephemeral EC public key (P-256 SubjectPublicKeyInfo)
Bytes 103–118 : 16-byte GCM authentication tag
Bytes 119+ : encrypted payload
Key File Format¶
Private keys are stored in pvtkey.txt as name=value pairs:
Manage keys using scripts/key_manager.py.
Functions¶
Native decryption module for encrypted Silo platform logs.
Provides decryption of log entries encrypted by the Silo platform using
either the Standard (EC + HKDF-SHA384 + AES-256-GCM) or Legacy
(seccure library, secp256r1) encryption schemes.
Dependencies¶
cryptography>=43.0.0— required for Standard decryption (install viapip install silo-sdk[decrypt])seccure>=0.5.0— required for Legacy decryption (install viapip install silo-sdk[legacy-decrypt])
Key file format¶
Private keys are stored in a simple text file (pvtkey.txt) with one
key per line in key-name=key-value format. For Standard encryption
the value is the PEM-encoded private key (or a path to a .pem file).
Example::
mykey=-----BEGIN EC PRIVATE KEY-----\nMHQCA...\n-----END EC PRIVATE KEY-----
legacy_key=raw_passphrase_value
Video decryption¶
Encrypted video files can be decrypted with :func:decrypt_video_file, which
uses chunked I/O (32 MB chunks by default) to avoid loading the entire file
into memory and an atomic write pattern (temp file → move on success).
Decryption failures¶
A tampered/corrupted ciphertext or any other decryption error raises
:class:LogDecryptionError — it is never silently reported as a successful
decryption. See :func:decrypt_log_entry and :func:decrypt_logs.
LogDecryptionError ¶
Bases: ValueError
Raised when a log entry's ciphertext fails to decrypt or authenticate.
This covers AES-GCM authentication failures (InvalidTag — a
tampered or corrupted ciphertext, or the wrong key), malformed
ciphertext, and any other error raised while decrypting or
JSON-parsing a log entry's plaintext.
Decryption failures are never silently treated as successful: they
are never merged into :func:decrypt_log_entry's return value or
appended to :func:decrypt_logs's decrypted-entry list. Subclasses
ValueError so existing callers that already catch ValueError
around decryption calls continue to observe failures.
Source code in silo_sdk/logging/decrypt.py
load_private_keys ¶
Parse a pvtkey.txt-format key file.
Each non-empty line should be key-name=key-value. When load_pem
is True and a value ends with .pem, the value is replaced by the
contents of that PEM file.
Values that were base64-encoded by :func:add_key_to_store (detected
by successfully base64-decoding to UTF-8 text containing a PEM header)
are automatically decoded back to raw PEM text. This makes the
generate_key_pair -> add_key_to_store -> load_private_keys
-> decrypt workflow work without a manual decoding step. Values that
are not base64-encoded PEM (Legacy passphrases, .pem file paths,
hand-written raw PEM) are returned unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key_file
|
str
|
Path to the key file. |
required |
load_pem
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Mapping of key name to key data. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If key_file does not exist. |
Source code in silo_sdk/logging/decrypt.py
decrypt_log_entry ¶
decrypt_log_entry(entry: dict[str, Any], keys: dict[str, str], show_enc_block: bool = False) -> dict[str, Any] | None
Decrypt a single encrypted log entry.
The entry must contain an enc field (base64-encoded ciphertext),
a key_name field, and optionally an encryption_type field
("Standard" or "Legacy"; defaults to "Legacy").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entry
|
dict[str, Any]
|
A log entry dict from the API. |
required |
keys
|
dict[str, str]
|
Mapping of key name → key data (PEM string or passphrase). |
required |
show_enc_block
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
The decrypted entry with |
dict[str, Any] | None
|
|
dict[str, Any] | None
|
entry, or not present in keys). |
Raises:
| Type | Description |
|---|---|
LogDecryptionError
|
If decryption or authentication fails for any
reason — including a tampered/corrupted ciphertext failing
its AES-GCM auth tag ( |
Source code in silo_sdk/logging/decrypt.py
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 | |
decrypt_logs ¶
decrypt_logs(logs: list[dict[str, Any]], keys: dict[str, str], show_enc_block: bool = False, track_missing: bool = False, track_failed: bool = False) -> list[dict[str, Any]] | tuple[list[dict[str, Any]], list[dict[str, Any]]] | tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]
Decrypt a list of encrypted log entries.
Entries whose keys are missing are silently skipped (see track_missing).
Entries that fail decryption or authentication — e.g. a tampered or
corrupted ciphertext that fails its AES-GCM auth tag — are never
treated as successfully decrypted and are never appended to the
decrypted-entry list. By default this function raises
:class:LogDecryptionError as soon as such an entry is encountered
(stopping the batch); pass track_failed=True to instead collect
failed entries into a separate list and keep processing the rest of
the batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
logs
|
list[dict[str, Any]]
|
List of log entry dicts (each with |
required |
keys
|
dict[str, str]
|
Mapping of key name → key data. |
required |
show_enc_block
|
bool
|
Whether to preserve the |
False
|
track_missing
|
bool
|
If |
False
|
track_failed
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]] | tuple[list[dict[str, Any]], list[dict[str, Any]]] | tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]
|
|
list[dict[str, Any]] | tuple[list[dict[str, Any]], list[dict[str, Any]]] | tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]
|
|
list[dict[str, Any]] | tuple[list[dict[str, Any]], list[dict[str, Any]]] | tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]
|
|
list[dict[str, Any]] | tuple[list[dict[str, Any]], list[dict[str, Any]]] | tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]
|
|
Raises:
| Type | Description |
|---|---|
LogDecryptionError
|
If any entry fails decryption or
authentication and track_failed is |
Source code in silo_sdk/logging/decrypt.py
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 | |
standard_decrypt ¶
Decrypt ciphertext produced by Standard (EC-based) encryption.
The ciphertext layout is:
- Bytes 0–11 : 12-byte IV (GCM nonce)
- Bytes 12–102 : DER-encoded ephemeral EC public key (91 bytes)
- Bytes 103–118: 16-byte GCM authentication tag
- Bytes 119+ : encrypted payload
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ciphertext
|
bytes
|
Raw ciphertext bytes (not base64-encoded). |
required |
private_key
|
Any
|
A |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
Decrypted plaintext bytes. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
Source code in silo_sdk/logging/decrypt.py
standard_decrypt_chunked ¶
standard_decrypt_chunked(input_file: IO[bytes], output_file: IO[bytes], private_key: Any, chunk_size: int = 32 * 1024 * 1024) -> None
Decrypt a Standard-encrypted file using chunked I/O to reduce memory usage.
Reads the 119-byte header (IV + ephemeral key + auth tag) first, then processes the remaining ciphertext in chunk_size chunks.
.. warning::
AES-GCM authentication is only verified when :py:meth:finalize is
called, after every plaintext chunk has already been written to
output_file — decryptor.update() returns and writes plaintext
immediately, it does not buffer until the auth tag is checked. If
the ciphertext has been tampered with or corrupted,
:py:meth:finalize raises
cryptography.exceptions.InvalidTag, but the unauthenticated
plaintext chunks already written to output_file are not
automatically removed from it — this function only makes a
best-effort attempt to truncate output_file back to its
original position when output_file supports seek/truncate
(see the except clause below). Callers writing directly to a
persistent destination (a real path, not an in-memory buffer)
should still write to a temporary file and move it into place only
after this function returns successfully — see
:func:decrypt_video_file for the reference implementation of
that pattern — or must otherwise treat output_file's contents as
untrusted and discard them if this function raises.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_file
|
IO[bytes]
|
Readable binary file object positioned at the start of the encrypted payload. |
required |
output_file
|
IO[bytes]
|
Writable binary file object for the plaintext output. |
required |
private_key
|
Any
|
A |
required |
chunk_size
|
int
|
Bytes to read per iteration (default 32 MB). |
32 * 1024 * 1024
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If the file header is too short. |
InvalidTag
|
If the ciphertext fails AES-GCM authentication (tampered/corrupted data, or the wrong key). Any bytes already written to output_file before this is raised are unauthenticated and must not be trusted. |
Source code in silo_sdk/logging/decrypt.py
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
decrypt_video_file ¶
decrypt_video_file(video_path: str, output_path: str, key_name: str, key_file: str | None = None, keys: dict[str, str] | None = None) -> bool
Decrypt an encrypted video file using Standard encryption with chunked I/O.
Uses an atomic write pattern: decrypts to a temporary file in the same directory as output_path, then moves it into place on success. Partial files are cleaned up on any failure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
video_path
|
str
|
Path to the encrypted video file. |
required |
output_path
|
str
|
Destination path for the decrypted output. |
required |
key_name
|
str
|
Name of the key to use (must exist in the key source). |
required |
key_file
|
str | None
|
Path to a |
None
|
keys
|
dict[str, str] | None
|
Pre-loaded key dictionary (alternative to key_file). |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither key_file nor keys is provided, or if decryption fails (wrong key or corrupt file). |
KeyError
|
If key_name is not found in the key source. |
FileNotFoundError
|
If video_path does not exist. |
ImportError
|
If |
Source code in silo_sdk/logging/decrypt.py
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | |
legacy_decrypt ¶
Decrypt ciphertext produced by Legacy (seccure-based) encryption.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ciphertext
|
bytes
|
Raw ciphertext bytes (not base64-encoded). |
required |
passphrase
|
bytes
|
The passphrase / private key bytes. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
Decrypted plaintext bytes. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If |