PDF 암호화 정책, FIPS 및 CBOM
PDF Oxide는 모든 암호화 및 서명 작업을 플러그형 암호화 제공자 뒤로 추상화하고, 그 위에 실패 시 닫힘(fail-closed) 방식의 런타임 정책을 얹으며, 실제로 사용한 알고리즘을 프로세스 단위 인벤토리로 기록합니다. 이 인벤토리는 **CycloneDX 1.6 암호화 자재 명세서(CBOM)**로 내보낼 수 있는데, 이는 규제 대상 구매자와 감사자가 요구하는 기계 판독 가능한 산출물입니다.
이 페이지에서는 제공자 선택, FIPS 140-3 모드, 런타임 정책 문법, 암호화 인벤토리, CBOM 내보내기를 다룹니다.
바인딩 지원 범위. 전체 암호화 거버넌스 표면은 프로세스 전역(문서 핸들을 받지 않음)이며 Rust, Python, Go, Node.js / TypeScript, **C#**에서 노출됩니다. 메서드 이름은 바인딩마다 다릅니다(각 언어의 관용적 표기). 제공자 선택 부분(
active_provider/fips_available/use_fips)은 issue #236의 FIPS 표면이며, 정책 부분(set_policy/policy/inventory/cbom)은 issue #230의 런타임 거버넌스 표면입니다. FIPS 모드 자체는 네이티브 라이브러리가--features fips로 컴파일된 경우에만 작동합니다. 그렇지 않으면use_fips는 오류를 반환/발생시키고fips_available은 false입니다.
두 가지 제공자
| 제공자 | 빌드 | 알고리즘 | 용도 |
|---|---|---|---|
rust-crypto(기본값) |
항상 포함 | ISO 32000-1 R≤4 문서용 레거시 MD5 + RC4 경로를 포함해 PDF 사양이 참조하는 모든 알고리즘 | 범용 |
aws-lc-rs |
선택 사용, --features fips |
FIPS 140-3 검증 완료; MD5, RC4, SHA-1 서명을 거부 | FIPS 준수 |
기본 제공자는 지연(lazy) 방식으로 설치됩니다. 첫 번째 암호화 작업(암호화된 문서 열기, 서명 검증)이 이를 확정합니다. 그렇기 때문에 프로세스에서 어떤 암호화 작업이 일어나기 전에 FIPS로 전환해야 합니다.
활성 암호화 제공자를 확인하려면?
active_provider는 초기화를 하지 않습니다: 아직 확정된 제공자가 없으면 기본값을 잠그지 않고 "rust-crypto (default, lazy)"를 반환하므로, 이후의 use_fips 호출이 여전히 성공할 수 있습니다. 제공자가 확정되면(명시적이든 지연이든) 실제 이름("rust-crypto" 또는 "aws-lc-rs")을 반환합니다.
import pdf_oxide
# Safe to call first — does not commit a provider.
print(pdf_oxide.crypto_active_provider()) # "rust-crypto (default, lazy)"
print(pdf_oxide.crypto_available_providers()) # ["rust-crypto"] (+ "aws-lc-rs" on a FIPS wheel)
use pdf_oxide::crypto;
// Non-initializing read for display/audit.
let name = if crypto::is_set() {
crypto::active().name().to_string()
} else {
format!("{} (default, lazy)", crypto::RustCryptoProvider.name())
};
println!("active crypto provider: {name}");
package main
import (
"fmt"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
fmt.Println(pdfoxide.ActiveCryptoProvider()) // "rust-crypto"
fmt.Println(pdfoxide.IsFipsCryptoAvailable()) // false unless built with FIPS
}
const pdf_oxide = require("pdf-oxide");
console.log(pdf_oxide.getActiveCryptoProvider()); // "rust-crypto (default, lazy)"
console.log(pdf_oxide.isFipsCryptoAvailable()); // false unless the -fips package
using PdfOxide;
Console.WriteLine(PdfDocument.GetActiveCryptoProvider()); // "rust-crypto"
Console.WriteLine(PdfDocument.IsFipsCryptoAvailable()); // False unless FIPS lib
시그니처
| 작업 | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| 활성 제공자 | crypto::active().name() -> &'static str |
crypto_active_provider() -> str |
ActiveCryptoProvider() -> string |
getActiveCryptoProvider(): string |
GetActiveCryptoProvider() -> string |
| FIPS 사용 가능 여부? | cfg(feature = "fips") |
crypto_available_providers() -> list[str] |
IsFipsCryptoAvailable() -> bool |
isFipsCryptoAvailable(): boolean |
IsFipsCryptoAvailable() -> bool |
이들이 감싸는 C ABI: char *pdf_oxide_crypto_active_provider(void)와 int32_t pdf_oxide_crypto_fips_available(void)(FIPS 제공자가 컴파일에 포함되어 있으면 1, 그렇지 않으면 0을 반환).
PDF에 대해 FIPS 모드를 활성화하려면?
어떤 암호화 작업보다도 먼저 use_fips(또는 Rust에서는 set_provider)를 호출하세요. 이는 한 번만 설정 가능합니다: 두 번째 호출이나, 기본 제공자가 지연 설치된 후의 모든 호출은 실패합니다.
int32_t pdf_oxide_crypto_use_fips(void)의 C ABI 오류 코드:
| 코드 | 의미 |
|---|---|
0 |
성공 |
1 |
FIPS 기능이 컴파일에 포함되지 않음 |
2 |
제공자가 이미 설정됨 |
import pdf_oxide
try:
pdf_oxide.crypto_use_fips() # raises RuntimeError if not compiled in / already set
print("FIPS provider active:", pdf_oxide.crypto_active_provider()) # "aws-lc-rs"
except RuntimeError as e:
print("FIPS unavailable:", e)
# Now every encrypted-PDF open / signature verify routes through aws-lc-rs.
doc = pdf_oxide.PdfDocument("encrypted-r6.pdf")
print(doc.page_count())
use std::sync::Arc;
use pdf_oxide::PdfDocument;
use pdf_oxide::crypto::{set_provider, AwsLcProvider};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Must run before any pdf_oxide operation that uses crypto.
set_provider(Arc::new(AwsLcProvider::new()))
.expect("crypto provider already installed");
// Every PDF open / signature verify now routes through aws-lc-rs.
let doc = PdfDocument::open("encrypted-r6.pdf")?;
println!("pages: {}", doc.page_count());
Ok(())
}
package main
import (
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
// Before any crypto operation.
if err := pdfoxide.UseFipsCryptoProvider(); err != nil {
log.Fatalf("FIPS unavailable: %v", err) // ErrFipsNotCompiled / ErrCryptoProviderAlreadySet
}
log.Println("active:", pdfoxide.ActiveCryptoProvider()) // "aws-lc-rs"
}
const pdf_oxide = require("pdf-oxide");
try {
pdf_oxide.useFipsCryptoProvider();
console.log("active:", pdf_oxide.getActiveCryptoProvider()); // "aws-lc-rs"
} catch (e) {
console.error("FIPS unavailable:", e.message);
}
using PdfOxide;
try
{
PdfDocument.UseFipsCryptoProvider();
Console.WriteLine(PdfDocument.GetActiveCryptoProvider()); // "aws-lc-rs"
}
catch (InvalidOperationException e)
{
Console.WriteLine($"FIPS unavailable: {e.Message}");
}
FIPS는 무엇을 거부하나요?
PDF Standard Security R≤4(PDF 1.4–1.6 비밀번호 암호화, ISO 32000-1 §7.6.3)는 키 유도에 MD5를, 암호로 RC4/AES-128을 사용합니다. FIPS 140-3은 MD5와 RC4를 금지하므로, 그러한 문서를 aws-lc-rs 아래에서 열면 해결 안내 메시지와 함께 실패합니다:
active CryptoProvider 'aws-lc-rs' rejects PDF Standard Security R=4
(R≤4 requires MD5; FIPS 140-3 forbids MD5).
Re-encrypt the document at R=6 (AES-256) or build pdf_oxide
without the 'fips' feature so the default 'rust-crypto'
provider stays active.
해결 방법: FIPS 제한이 없는 환경에서 R=6(PDF 2.0 AES-256, ISO 32000-2 §7.6.4)으로 재암호화하세요. R=6 문서는 aws-lc-rs 아래에서 문제없이 열립니다.
FIPS 변형 설치.
-fips배포판은 단일 제공자 바이너리를 제공합니다(감사자는 보통 이를 요구합니다).pip install pdf_oxide_fips,npm install pdf-oxide-fips,dotnet add package PdfOxide.Fips,go get github.com/yfedoseev/pdf_oxide/go-fips를 사용하거나 Rust를cargo build --features fips로 빌드하세요. 동일한 릴리스 태그의 기본 변형과 FIPS 변형은 모든 비암호화 경로에서 바이트 단위로 동일합니다.
런타임 암호화 정책을 설정하려면?
정책은 어떤 제공자가 활성인지와 무관하게 허용되는 알고리즘을 좁힙니다. 이는 한 번만 설정 가능하며(런타임 다운그레이드는 공격 벡터입니다) 실패 시 닫힘입니다(파싱할 수 없는 명세는 거부되며 어떤 정책도 설치되지 않습니다). 기본값(설정하지 않을 경우)은 "compat"으로, 정책 도입 이전 동작과 바이트 단위로 동일합니다.
문법: mode[;clause]*
mode∈compat|strict|fips-strictclause=(allow|deny):<alg>@<read|write>— 예:"compat;deny:rc4@write;deny:md5@write"또는"fips-strict"
int32_t pdf_oxide_crypto_set_policy(const char *spec)의 C ABI 오류 코드:
| 코드 | 의미 |
|---|---|
0 |
성공 |
1 |
잘못된 인수(null / 비 UTF-8 명세) |
2 |
파싱 오류 — 명세가 거부되고 정책이 설치되지 않음 |
3 |
정책이 이미 설정됨 |
import pdf_oxide
# Deny the legacy write paths while staying compatible for reads.
pdf_oxide.crypto_set_policy("compat;deny:rc4@write;deny:md5@write")
print(pdf_oxide.crypto_policy()) # canonical grammar string
use pdf_oxide::crypto::{self, SecurityPolicy};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse fail-closed, then install set-once.
let policy: SecurityPolicy = "fips-strict".parse()?;
crypto::set_policy(policy).expect("a crypto policy is already set");
println!("active policy: {}", crypto::active_policy());
Ok(())
}
package main
import (
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
// Treat any error as fatal — the policy is not installed on failure.
if err := pdfoxide.SetCryptoPolicy("strict;deny:md5@write"); err != nil {
log.Fatalf("policy rejected: %v", err)
}
log.Println("active policy:", pdfoxide.CryptoPolicy())
}
const pdf_oxide = require("pdf-oxide");
// Throws on an unparseable spec (fail-closed) or if already set.
pdf_oxide.setCryptoPolicy("fips-strict");
console.log(pdf_oxide.cryptoPolicy()); // "fips-strict"
using PdfOxide;
// Throws ArgumentException (rejected) or InvalidOperationException (already set).
PdfDocument.SetCryptoPolicy("compat;deny:rc4@write");
Console.WriteLine(PdfDocument.CryptoPolicy());
시그니처
| 작업 | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| 정책 설정 | crypto::set_policy(SecurityPolicy) -> Result<(), SetPolicyError> |
crypto_set_policy(spec: str) -> None |
SetCryptoPolicy(spec string) -> error |
setCryptoPolicy(spec: string): void |
SetCryptoPolicy(string spec) -> void |
| 정책 읽기 | crypto::active_policy() -> &'static SecurityPolicy |
crypto_policy() -> str |
CryptoPolicy() -> string |
cryptoPolicy(): string |
CryptoPolicy() -> string |
암호화 인벤토리를 읽으려면?
인벤토리는 이 프로세스에서 지금까지 실제로 사용된 알고리즘 토큰의 집합으로, 각 강제 경계에서 기록되는 잠금 없는 원자적 비트셋입니다. "이 실행이 어떤 암호화를 사용했는가?"에 대한 최소한의 보고서입니다. 토큰은 md5, aes256, rc4, sha256과 같은 안정적인 소문자 문자열입니다.
C ABI는 쉼표로 연결된 문자열을 반환합니다(char *pdf_oxide_crypto_inventory(void), 아무것도 사용되지 않은 경우 ""). 고수준 바인딩은 이를 리스트/슬라이스/배열로 분할해 줍니다.
import pdf_oxide
doc = pdf_oxide.PdfDocument("signed.pdf")
doc.verify_signatures() # exercises some algorithms
print(pdf_oxide.crypto_inventory()) # e.g. ["sha256", "aes256"] (list[str])
use pdf_oxide::crypto;
// `inventory()` returns the exercised algorithms in declaration order.
let used: Vec<&'static str> = crypto::inventory()
.into_iter()
.map(|a| a.token())
.collect();
println!("exercised: {used:?}"); // e.g. ["md5", "aes256"]
package main
import (
"fmt"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
inv := pdfoxide.CryptoInventory() // []string, empty when nothing exercised
fmt.Println("exercised:", inv)
}
const pdf_oxide = require("pdf-oxide");
console.log(pdf_oxide.cryptoInventory()); // string[], e.g. ["sha256"]
using PdfOxide;
foreach (var token in PdfDocument.CryptoInventory()) // string[]
Console.WriteLine(token);
PDF용 CBOM(CycloneDX)을 내보내려면?
crypto_cbom은 인벤토리를 CycloneDX 1.6 cryptographic-asset 자재 명세서로 JSON 문자열로 직렬화합니다. 이 문서는 항상 bomFormat: "CycloneDX", specVersion: "1.6", metadata 블록(RFC 3339 타임스탬프 + pdf_oxide 도구 컴포넌트), 그리고 사용된 알고리즘마다 하나의 cryptographic-asset 컴포넌트를 포함합니다. 빈 인벤토리는 컴포넌트가 없는 유효한 BOM을 생성합니다.
import json
import pdf_oxide
# Run your workload first so the inventory reflects what was used.
doc = pdf_oxide.PdfDocument("signed.pdf")
doc.verify_signatures()
cbom = pdf_oxide.crypto_cbom() # JSON string
report = json.loads(cbom)
assert report["bomFormat"] == "CycloneDX"
assert report["specVersion"] == "1.6"
with open("pdf-cbom.json", "w") as f:
f.write(cbom)
use std::fs;
use pdf_oxide::crypto;
fn main() -> std::io::Result<()> {
// ... run the crypto workload first ...
let cbom: String = crypto::cbom_json(); // CycloneDX 1.6 JSON
fs::write("pdf-cbom.json", cbom)?;
Ok(())
}
package main
import (
"os"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
cbom := pdfoxide.CryptoCBOM() // CycloneDX 1.6 JSON string
_ = os.WriteFile("pdf-cbom.json", []byte(cbom), 0o644)
}
const fs = require("fs");
const pdf_oxide = require("pdf-oxide");
const cbom = pdf_oxide.cryptoCbom(); // CycloneDX 1.6 JSON string
fs.writeFileSync("pdf-cbom.json", cbom);
using System.IO;
using PdfOxide;
string cbom = PdfDocument.CryptoCbom(); // CycloneDX 1.6 JSON string
File.WriteAllText("pdf-cbom.json", cbom);
CBOM 구조
사용된 각 알고리즘은 하나의 컴포넌트가 됩니다:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"metadata": {
"timestamp": "2026-06-22T12:00:00+00:00",
"tools": { "components": [{ "type": "application", "name": "pdf_oxide", "version": "0.3.69" }] }
},
"components": [
{
"type": "cryptographic-asset",
"name": "aes256",
"bom-ref": "crypto/algorithm/aes256",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "block-cipher",
"classicalSecurityLevel": 256,
"nistQuantumSecurityLevel": 0,
"certificationLevel": ["fips140-3"]
}
}
}
]
}
primitive는 알고리즘 종류(hash, block-cipher, RC4용 stream-cipher, signature, kdf, drbg)에서 매핑됩니다. certificationLevel은 FIPS 승인 알고리즘의 경우 ["fips140-3"], 그 외에는 ["none"]입니다.
시그니처
| 작업 | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| 인벤토리 | crypto::inventory() -> Vec<AlgorithmId> |
crypto_inventory() -> list[str] |
CryptoInventory() -> []string |
cryptoInventory(): string[] |
CryptoInventory() -> string[] |
| CBOM | crypto::cbom_json() -> String |
crypto_cbom() -> str |
CryptoCBOM() -> string |
cryptoCbom(): string |
CryptoCbom() -> string |
권장 시작 순서
제공자와 정책 모두 한 번만 설정 가능하고 기본 제공자가 지연 설치되므로, 암호화 거버넌스를 프로세스가 가장 먼저 하는 일로 구성하세요:
import pdf_oxide
# 1. (optional) switch to FIPS — must precede any crypto operation.
if "aws-lc-rs" in pdf_oxide.crypto_available_providers():
pdf_oxide.crypto_use_fips()
# 2. install the governance policy (fail-closed, set-once).
pdf_oxide.crypto_set_policy("fips-strict")
# 3. ... run your PDF workload ...
doc = pdf_oxide.PdfDocument("encrypted-r6.pdf")
doc.verify_signatures()
# 4. emit governance artifacts.
print("policy: ", pdf_oxide.crypto_policy())
print("inventory:", pdf_oxide.crypto_inventory())
with open("pdf-cbom.json", "w") as f:
f.write(pdf_oxide.crypto_cbom())
자주 묻는 질문
암호화 제공자를 확정한 후 다시 전환할 수 있나요? 아니요. use_fips(제공자)와 set_policy(정책)는 모두 설계상 한 번만 설정 가능합니다. 진행 중인 암호화 상태(FIPS 자체 테스트, HSM 세션)가 존재하는 상태에서 프로세스 도중에 교체하는 것은 정합성 위험이며, 런타임 정책 다운그레이드는 공격 벡터입니다. 새 제공자가 필요한 테스트는 별도의 프로세스 네임스페이스에서 실행해야 합니다.
아무 작업도 하지 않았는데 왜 crypto_active_provider()가 "rust-crypto (default, lazy)"라고 표시하나요? 그 읽기는 의도적으로 초기화를 하지 않습니다: 기본값을 확정하지 않고 보고하므로 이후의 crypto_use_fips()가 여전히 성공할 수 있습니다. 실제 이름은 제공자가 확정되면 나타납니다.
FIPS는 모든 패키지에서 사용할 수 있나요? 네이티브 라이브러리가 --features fips로 빌드된 경우에만 가능합니다. 단일 제공자 FIPS 바이너리를 제공하는 병행 -fips 배포판(pdf_oxide_fips, pdf-oxide-fips, PdfOxide.Fips, go-fips)을 사용하세요. 비 FIPS 빌드에서는 fips_available이 false이고 use_fips는 오류를 냅니다.
CBOM은 표준인가요? 그렇습니다. 유효한 CycloneDX 1.6 문서(bomFormat: "CycloneDX", specVersion: "1.6")이므로 SBOM/CBOM 도구와 공급망 감사에 그대로 적용됩니다.
정책을 설정하면 PDF 처리가 느려지나요? 아니요. PDF Oxide는 평균 0.8ms / 100% 통과의 추출 프로파일을 유지합니다. 인벤토리는 잠금 없는 원자적 비트셋이며, 정책 검사는 암호화 강제 경계에서만 일어납니다.