- 0. Overview
- 1. Package Validation
- 2. SecurityStatus Structure
- 3. EncryptionType System
- 4. Generic Encryption Patterns
- 5. ML-KEM Key Structure and Operations
This document defines the security validation API for the NovusPack system, including package validation, encryption, and security status reporting.
Signature verification and signature validation are deferred to v2.
V1 only enforces signed package immutability based on header fields that indicate signature presence (for example, Flags bit 0 "Has signatures" and SignatureOffset > 0).
There are no other immutability-related flags in the v1 header.
V1 does not validate signature contents.
There is no package-wide security level. Security levels apply only to algorithms that define security levels (for example, ML-KEM level 1, 3, or 5) and are selected per encrypted FileEntry and key material.
- Go API Definitions Index - Complete index of all Go API functions, types, and structures
- Core Package Interface - Package operations and compression
- Digital Signature API - Signature management, types, and validation
- Security and Encryption - Comprehensive security architecture and encryption implementation
- File Format Specifications - .nvpk format structure and signature implementation
- Generic Types and Patterns - Generic concurrency patterns and type-safe configuration
- Package Compression API - Generic strategy patterns and compression concurrency
- File Management API - References this API for encryption patterns
Note: V1 focuses on format and integrity validation. Signature validation is deferred to v2.
Package validation methods:
- Package.Validate - Validates package format, structure, and integrity
// ValidateIntegrity validates package integrity using checksums
// Returns *PackageError on failure
func (p *Package) ValidateIntegrity() error// GetSecurityStatus gets comprehensive security status
func (p *Package) GetSecurityStatus() SecurityStatusThis functionality is deferred to version 2 of the API.
// ValidateAllSignatures validates all signatures in order
func (p *Package) ValidateAllSignatures() []SignatureValidationResult// ValidateSignatureType validates signatures of specific type
func (p *Package) ValidateSignatureType(signatureType uint32) []SignatureValidationResult// ValidateSignatureIndex validates signature by index
// Returns *PackageError on failure
func (p *Package) ValidateSignatureIndex(index int) (SignatureValidationResult, error)// ValidateSignatureChain validates signature chain integrity
func (p *Package) ValidateSignatureChain() []SignatureValidationResult- Signature validation is deferred to v2.
- Each signature validates complete package state up to its position
- Chain validation ensures no signatures were removed or modified
- Results provide detailed validation status for each signature
This section describes the SecurityStatus structure used for security validation results.
Note: Signature-related fields are deferred to v2. V1 returns signature-related fields as zero values. There is no package-wide security level in v1.
- SignatureCount (int): Number of signatures in package
- ValidSignatures (int): Number of valid signatures
- TrustedSignatures (int): Number of trusted signatures
- SignatureResults ([]SignatureValidationResult): Individual results
- HasChecksums (bool): Checksums present
- ChecksumsValid (bool): Checksums valid
- ValidationErrors ([]string): Validation errors
See SecurityStatus Structure for the complete structure definition.
Note: This structure is v2-only.
// SignatureValidationResult provides information about individual signature validation results.
type SignatureValidationResult struct {
Index int // Signature index in the package
Type uint32 // Signature type identifier
Valid bool // Whether signature is valid
Trusted bool // Whether signature is trusted
Error string // Error message if validation failed
Timestamp uint32 // When signature was created
PublicKey []byte // Public key used for validation (if available)
}This section describes the EncryptionType system for specifying encryption algorithms.
This section describes the EncryptionType definition and structure.
// EncryptionAlgorithm enumeration
type EncryptionAlgorithm int
const (
EncryptionAlgorithmNone EncryptionAlgorithm = iota
EncryptionAlgorithmAES256GCM
EncryptionAlgorithmChaCha20Poly1305
EncryptionAlgorithmMLKEM512
EncryptionAlgorithmMLKEM768
EncryptionAlgorithmMLKEM1024
)// EncryptionType is a v1 alias of EncryptionAlgorithm.
// This exists for compatibility with existing APIs.
type EncryptionType = EncryptionAlgorithm
const (
EncryptionNone EncryptionType = EncryptionAlgorithmNone
EncryptionAES256GCM EncryptionType = EncryptionAlgorithmAES256GCM
EncryptionChaCha20Poly1305 EncryptionType = EncryptionAlgorithmChaCha20Poly1305
EncryptionMLKEM512 EncryptionType = EncryptionAlgorithmMLKEM512
EncryptionMLKEM768 EncryptionType = EncryptionAlgorithmMLKEM768
EncryptionMLKEM1024 EncryptionType = EncryptionAlgorithmMLKEM1024
)Defines the available encryption algorithms for file encryption.
EncryptionAlgorithmNone: No encryption (default)EncryptionAlgorithmAES256GCM: AES-256-GCM symmetric encryptionEncryptionAlgorithmChaCha20Poly1305: ChaCha20-Poly1305 symmetric encryptionEncryptionAlgorithmMLKEM512: ML-KEM-512 post-quantum encryption (security level 1)EncryptionAlgorithmMLKEM768: ML-KEM-768 post-quantum encryption (security level 3)EncryptionAlgorithmMLKEM1024: ML-KEM-1024 post-quantum encryption (security level 5)
This section describes validation functions for EncryptionType values.
// IsValidEncryptionType checks if the encryption type is valid
func IsValidEncryptionType(encType EncryptionType) bool// GetEncryptionTypeName returns the human-readable name of the encryption type
func GetEncryptionTypeName(encType EncryptionType) stringProvides validation and naming utilities for encryption types.
if !IsValidEncryptionType(encType) {
return fmt.Errorf("invalid encryption type: %d", encType)
}
name := GetEncryptionTypeName(encType)
fmt.Printf("Using encryption: %s\n", name)The on-disk representation of encryption is defined by the NovusPack file format.
See Compression and Encryption Types for the EncryptionType field and its encoded values.
See Encrypted File Data Framing for the per-file ciphertext encoding (nonces, encapsulation, and ciphertext layout).
In v1:
EncryptionAES256GCMmaps to the on-disk AES-256-GCM value.EncryptionChaCha20Poly1305maps to the on-disk ChaCha20-Poly1305 value.EncryptionMLKEM512,EncryptionMLKEM768, andEncryptionMLKEM1024map to the on-disk quantum-safe encryption value.
ML-KEM variants are represented in memory by the selected algorithm constant.
The file format stores quantum-safe encryption as a single on-disk value.
In v1, the specific ML-KEM variant is derived from the key material provided to the API (for example, the Level carried by MLKEMKey) and is not encoded as a distinct EncryptionType value.
This section provides non-normative guidance for selecting encryption algorithms.
- Prefer
EncryptionAES256GCMfor most desktop and server environments. - Prefer
EncryptionChaCha20Poly1305for mobile, embedded, and ARM environments. - Prefer
EncryptionMLKEM768orEncryptionMLKEM1024when post-quantum security is required.
The security API provides generic encryption patterns that extend the generic configuration patterns defined in api_generics.md for type-safe encryption operations.
The EncryptionStrategy[T] interface extends the generic Core Generic Types pattern for encryption-specific operations.
EncryptionStrategy[T] embeds Strategy[T, T] where both input and output are the same type.
The Process method from Strategy[T, T] can be used for encryption operations, while Encrypt and Decrypt provide more specific encryption/decryption methods with key management.
// EncryptionStrategy extends Strategy[T, T] for encryption operations
// Both input and output are the same type T
// The Strategy.Type() method returns "encryption" as the category
type EncryptionStrategy[T any] interface {
Strategy[T, T] // Extends the generic Strategy interface
Encrypt(ctx context.Context, data T, key EncryptionKey[T]) (T, error)
Decrypt(ctx context.Context, data T, key EncryptionKey[T]) (T, error)
EncryptionType() EncryptionType // Returns the specific encryption algorithm type
Name() string
KeySize() int
ValidateKey(ctx context.Context, key EncryptionKey[T]) error
}// ByteEncryptionStrategy is the concrete implementation for []byte data
type ByteEncryptionStrategy interface {
EncryptionStrategy[[]byte]
}This section describes the EncryptionKey structure for managing encryption keys.
// EncryptionKey provides type-safe key management
// Uses Option[T] internally for type-safe key storage
type EncryptionKey[T any] struct {
*Option[T] // See [Option Type](api_generics.md#11-option-type) for details
KeyType EncryptionType
KeyID string
CreatedAt time.Time
ExpiresAt *time.Time
}// NewEncryptionKey creates a new encryption key with the specified type, ID, and key material.
func NewEncryptionKey[T any](keyType EncryptionType, keyID string, key T) *EncryptionKey[T]// GetKey returns the encryption key material.
func (k *EncryptionKey[T]) GetKey() (T, error)// SetKey sets the encryption key material.
func (k *EncryptionKey[T]) SetKey(key T)// IsValid returns true if the encryption key is valid.
func (k *EncryptionKey[T]) IsValid() bool// IsExpired returns true if the encryption key has expired.
func (k *EncryptionKey[T]) IsExpired() boolThis section describes the behavior of GetKey and SetKey methods.
GetKey()returns a copy of the key value, not a reference to the original.- This ensures that modifications to the returned value do not affect the stored key.
- For slice types (
[]byte, etc.), a deep copy is performed to prevent accidental mutation. - Returns an error if the key is missing, invalid, or expired.
- The returned copy SHOULD be used within
runtime/secret.Dofor secure key material handling. - The API cannot enforce
runtime/secret.Dousage outside the NovusPack API boundary.
SetKey()overwrites any existing key value.- The key value is stored as a copy (for slice types, a deep copy is made).
SetKey()does not update timestamps (CreatedAtremains unchanged).- To update timestamps, create a new
EncryptionKeyinstance or manually update the timestamp fields. - Validation Requirements:
SetKey()must validate that:- The key is valid for the encryption type (
KeyTypematches the expected encryption algorithm). - The key is not expired (if
ExpiresAtis set, it must be in the future). - Returns
*PackageErrorwithErrTypeEncryptionif validation fails.
- The key is valid for the encryption type (
GetKey()returns*PackageErrorwithErrTypeEncryptionif:- The key is not set (Option is None) - error message should indicate "key not set"
- The key is invalid (
IsValid()returns false) - error message should indicate "key invalid" - The key is expired (
IsExpired()returns true) - error message should indicate "key expired" - The key type does not match the expected type - error message should indicate "key type mismatch"
All operations that use EncryptionKey must:
- Check key is set: Before attempting to use the key, verify that it is set (Option is not None).
- Re-validate before use: Re-validate that the key is:
- Valid for the encryption type (matches
KeyType) - Not expired (if
ExpiresAtis set, check it's in the future) - Passes
IsValid()check
- Valid for the encryption type (matches
- Return PackageError on failure: If any validation fails, return
*PackageErrorwith:ErrTypeEncryptionas the error type- A descriptive error message indicating the specific reason for failure
- Appropriate context in the error structure
This applies to:
Encrypt()andDecrypt()methods ofEncryptionStrategyimplementations- Any other operations that access or use the key material
This section describes secure operations for EncryptionKey using runtime/secret.
For API simplicity and safety, ALL keys stored in EncryptionKey[T] MUST be treated as private key material and handled with runtime/secret.Do.
This approach:
- Simplifies code paths: No need to distinguish between public and private keys at runtime
- Provides defense in depth: Even if a key is technically "public", protecting it doesn't hurt
- Makes the API contract clear: All keys receive the same security guarantees
ALL keys (regardless of T type, KeyType, or EncryptionType) MUST be handled with runtime/secret.Do.
This is a blanket policy with no exceptions for public keys or non-sensitive data.
This ensures consistent security handling across all key operations.
The following T types are supported for storage in EncryptionKey[T]:
[]byte- Raw byte slices for symmetric keys*rsa.PrivateKey- RSA private keys*ecdsa.PrivateKey- ECDSA private keys*ed25519.PrivateKey- Ed25519 private keys- Other cryptographic key types as specified by the encryption strategy implementation
Since ALL keys are treated as private key material, the following requirements apply.
GetKey()MUST execute key retrieval withinruntime/secret.Dofor all keys.SetKey()MUST execute key assignment withinruntime/secret.Dofor all keys.Encrypt()andDecrypt()MUST execute withinruntime/secret.Dowhen accessing keys.Clear()MUST execute key clearing withinruntime/secret.Dofor all keys.
- Users SHOULD use
runtime/secret.Dowhen handling values returned byGetKey(). - Users SHOULD NOT store key material outside the secret execution context.
Key material has the following lifetime guarantees:
- The returned copy from
GetKey()is valid only within theruntime/secret.Docontext where it was retrieved. - Key material is automatically erased from memory after the
runtime/secret.Dofunction completes. - Callers SHOULD use the returned key within
runtime/secret.Dofor any operations that access the key material. - Callers SHOULD NOT store the key copy outside the secret execution context.
This section describes the semantics of IsValid and IsExpired methods.
IsValid() returns true only when all of the following conditions are met:
- Key is set: The key value must be set (Option is not None).
- KeyID is non-empty:
KeyIDmust be a non-empty string (length > 0). - CreatedAt is non-zero:
CreatedAtmust be a non-zerotime.Timevalue (not the zero value). - KeyType is valid:
KeyTypemust be a valid encryption type (passesIsValidEncryptionType()). - ExpiresAt is valid if set: If
ExpiresAtis notnil, it must be afterCreatedAt(expiration time must be in the future relative to creation time).
IsExpired() returns true when:
- ExpiresAt is set:
ExpiresAtis notnil. - Current time is past expiration: The current time (
time.Now()) is equal to or afterExpiresAt.
ExpiresAt == nilmeans never expires: IfExpiresAtisnil, the key never expires andIsExpired()always returnsfalse.ExpiresAtbeforeCreatedAtis invalid: IfExpiresAtis set and is before or equal toCreatedAt, the key is considered invalid (IsValid()returnsfalse).- Zero time handling: If
CreatedAtis the zero value,IsValid()returnsfalseregardless ofExpiresAt.
When checking key validity, operations should:
- First check
IsValid()to ensure all required fields are present and valid. - Then check
IsExpired()to ensure the key is still within its validity period. - Both checks must pass for the key to be usable.
The EncryptionConfig[T] type extends the generic Configuration Patterns for encryption-specific settings.
// EncryptionConfig provides type-safe encryption configuration
type EncryptionConfig[T any] struct {
*Config[T] // See [Core Generic Types](api_generics.md#1-core-generic-types) for base configuration
// Encryption-specific settings
EncryptionType Option[EncryptionType] // Encryption algorithm type
KeySize Option[int] // Key size in bits
UseRandomIV Option[bool] // Use random initialization vector
AuthenticationTag Option[bool] // Include authentication tag
CompressionLevel Option[int] // Compression level for encrypted data
}This section describes the EncryptionConfigBuilder structure for building encryption configurations.
// EncryptionConfigBuilder provides fluent configuration building for encryption
type EncryptionConfigBuilder[T any] struct {
config *EncryptionConfig[T]
}// NewEncryptionConfigBuilder creates a new encryption configuration builder.
func NewEncryptionConfigBuilder[T any]() *EncryptionConfigBuilder[T]// WithEncryptionType sets the encryption type for the configuration.
func (b *EncryptionConfigBuilder[T]) WithEncryptionType(encType EncryptionType) *EncryptionConfigBuilder[T]// WithKeySize sets the key size for the configuration.
func (b *EncryptionConfigBuilder[T]) WithKeySize(size int) *EncryptionConfigBuilder[T]// WithRandomIV enables or disables random IV generation for the configuration.
func (b *EncryptionConfigBuilder[T]) WithRandomIV(useRandom bool) *EncryptionConfigBuilder[T]// WithAuthenticationTag enables or disables authentication tag generation for the configuration.
func (b *EncryptionConfigBuilder[T]) WithAuthenticationTag(useAuth bool) *EncryptionConfigBuilder[T]// Build constructs and returns the final encryption configuration.
func (b *EncryptionConfigBuilder[T]) Build() *EncryptionConfig[T]The EncryptionValidator[T] type extends the generic Core Generic Types for encryption-specific validation.
// EncryptionValidator provides type-safe encryption validation
type EncryptionValidator[T any] struct {
*Validator[T] // See [Core Generic Types](api_generics.md#1-core-generic-types) for base validation
encryptionRules []EncryptionValidationRule[T]
}// EncryptionValidationRule is an alias for the generic ValidationRule
type EncryptionValidationRule[T any] = ValidationRule[T]// AddEncryptionRule adds an encryption validation rule to the validator.
func (v *EncryptionValidator[T]) AddEncryptionRule(rule EncryptionValidationRule[T])// Returns *PackageError on failure
func (v *EncryptionValidator[T]) ValidateEncryptionData(ctx context.Context, data T) error// Returns *PackageError on failure
func (v *EncryptionValidator[T]) ValidateDecryptionData(ctx context.Context, data T) error// Returns *PackageError on failure
func (v *EncryptionValidator[T]) ValidateEncryptionKey(key EncryptionKey[T]) errorThis section describes file-level encryption operations.
// FileEncryptionHandler provides file-specific encryption operations
type FileEncryptionHandler[T any] interface {
EncryptFile(ctx context.Context, filePath string, data T, key EncryptionKey[T]) error
DecryptFile(ctx context.Context, filePath string, key EncryptionKey[T]) (T, error)
ValidateFileEncryption(ctx context.Context, filePath string) error
}This section describes built-in file encryption handler implementations.
// Built-in file encryption handlers
type AES256GCMFileHandler struct { ... }// ChaCha20Poly1305FileHandler provides file encryption using ChaCha20-Poly1305 algorithm.
type ChaCha20Poly1305FileHandler struct { ... }// MLKEMFileHandler provides file encryption using ML-KEM (post-quantum) algorithm.
type MLKEMFileHandler struct { ... }This section describes package-level file encryption operations.
// EncryptFile encrypts a file using the security API's file encryption patterns
// Returns *PackageError on failure
func (p *Package) EncryptFile[T any](ctx context.Context, path string, data T, handler FileEncryptionHandler[T], key EncryptionKey[T]) error// DecryptFile decrypts a file using the security API's file encryption patterns
// Returns *PackageError on failure
func (p *Package) DecryptFile[T any](ctx context.Context, path string, handler FileEncryptionHandler[T], key EncryptionKey[T]) (T, error)// ValidateFileEncryption validates file encryption using the security API's file encryption patterns
// Returns *PackageError on failure
func (p *Package) ValidateFileEncryption[T any](ctx context.Context, path string, handler FileEncryptionHandler[T]) error// GetFileEncryptionInfo gets encryption information for a file using the security API's patterns
func (p *Package) GetFileEncryptionInfo[T any](path string) (*EncryptionConfig[T], error)Type Constraints: The type parameter T in these functions is typically []byte for file data operations, but can be any type that the FileEncryptionHandler[T] supports.
For most use cases, T should be []byte to work with file content directly.
Error Handling: All encryption operations return errors using NewPackageError or WrapErrorWithContext with EncryptionErrorContext for type-safe error handling.
See EncryptionErrorContext Structure for details.
MUST Requirements: File encryption and decryption operations that use keys MUST execute within Go's runtime/secret.Do function to protect sensitive cryptographic material.
DecryptFilemethod MUST wrap decryption operations within the secret execution context to ensure that keys and decrypted plaintext are handled securelyEncryptFilemethod MUST wrap encryption operations within the secret execution context when accessing keysFileEncryptionHandlerimplementations MUST useruntime/secret.Dowhen accessing key material during encryption and decryption operations- This ensures that sensitive key data and decrypted content are promptly erased from memory registers and stack frames, reducing exposure to memory analysis attacks
The security API defines a typed error context structure for type-safe error handling:
// EncryptionErrorContext provides type-safe error context for encryption operations
type EncryptionErrorContext struct {
Path string // File path that caused the error
Operation string // Operation name (e.g., "EncryptFile", "DecryptFile", "ValidateFileEncryption")
EncryptionType EncryptionType // Encryption algorithm type
KeyID string // Encryption key ID (if applicable)
KeySize int // Key size in bits (if applicable)
ErrorStage string // Stage where error occurred (e.g., "key_generation", "encryption", "decryption")
}Usage: All encryption error-returning functions use EncryptionErrorContext with NewPackageError or WrapErrorWithContext:
// Example: Encryption failure with typed context
err := NewPackageError(ErrTypeEncryption, "encryption failed", nil, EncryptionErrorContext{
Path: "/path/to/file",
Operation: "EncryptFile",
EncryptionType: EncryptionAES256GCM,
KeyID: "key-12345",
KeySize: 256,
ErrorStage: "encryption",
})
// Example: Key validation error with typed context
err := WrapErrorWithContext(validationErr, ErrTypeSecurity, "invalid encryption key", EncryptionErrorContext{
Path: "/path/to/file",
Operation: "EncryptFile",
EncryptionType: EncryptionMLKEM768,
KeyID: "key-67890",
KeySize: 0,
ErrorStage: "key_validation",
})Retrieving Context: Use GetErrorContext to retrieve typed error context:
if ctx, ok := GetErrorContext[EncryptionErrorContext](err, "_typed_context"); ok {
log.Printf("Encryption error: Path=%s, Operation=%s, Type=%v, KeyID=%s",
ctx.Path, ctx.Operation, ctx.EncryptionType, ctx.KeyID)
}Note: NovusPack does not provide key generation functions.
Users are responsible for generating cryptographic keys using appropriate external libraries (e.g., crypto/aes, crypto/rand, Cloudflare CIRCL for ML-KEM).
NovusPack accepts pre-generated keys wrapped in the EncryptionKey[T] structure.
// ML-KEM Key Structure
type MLKEMKey struct {
PublicKey []byte // ML-KEM public key data
PrivateKey []byte // ML-KEM private key data
Level int // Security level (1, 3, or 5)
}Represents an ML-KEM key pair with associated security level.
PublicKey: Public key data for encryptionPrivateKey: Private key data for decryptionLevel: Security level (1, 3, or 5) mapped to ML-KEM-512, ML-KEM-768, and ML-KEM-1024
Performs encryption and decryption operations using ML-KEM keys.
// Encrypt encrypts plaintext using ML-KEM key
func (k *MLKEMKey) Encrypt(ctx context.Context, plaintext []byte) ([]byte, error)// Decrypt decrypts ciphertext using ML-KEM key
func (k *MLKEMKey) Decrypt(ctx context.Context, ciphertext []byte) ([]byte, error)ctx: Context for cancellation and timeout handlingplaintext: Data to encrypt (for Encrypt method)ciphertext: Encrypted data to decrypt (for Decrypt method)
Encrypted data (for Encrypt) or decrypted data (for Decrypt)
ErrEncryptionFailed: Failed to encrypt dataErrDecryptionFailed: Failed to decrypt dataErrInvalidKey: Key is invalid or corruptedErrContextCancelled: Context was cancelledErrContextTimeout: Context timeout exceeded
// Encrypt data
ciphertext, err := key.Encrypt(ctx, []byte("Sensitive data"))
if err != nil {
return fmt.Errorf("encryption failed: %w", err)
}
// Decrypt data
plaintext, err := key.Decrypt(ctx, ciphertext)
if err != nil {
return fmt.Errorf("decryption failed: %w", err)
}MUST Requirements: Encryption and decryption operations that access key material MUST use Go's runtime/secret package to protect sensitive cryptographic data in memory.
Decryptmethod MUST wrap operations that use keys withinruntime/secret.Doto ensure that key data and decrypted plaintext are handled securelyEncryptmethod MUST wrap operations that use keys withinruntime/secret.Doto ensure that key data is handled securely- This prevents sensitive data from persisting in memory registers, stack frames, or heap allocations longer than necessary
- Implementations MUST ensure that all operations involving key material are executed within the secret execution context provided by
runtime/secret.Do
This section describes ML-KEM key information and operations.
// GetPublicKey returns the public key data
func (k *MLKEMKey) GetPublicKey() []byte// GetLevel returns the security level of the key
func (k *MLKEMKey) GetLevel() int// Clear clears sensitive key data from memory
func (k *MLKEMKey) Clear()The ML-KEM public key is not stored in the on-disk package.
The package stores only per-file EncryptionType values and encrypted file data.
Key management is out of scope for the package file format.
Callers must provide the appropriate key material when writing and reading encrypted files.
If you need to persist key identity (for example, a key fingerprint or a key ID), that must be handled out-of-band in v1 (for example, in application metadata).
Provides access to key information and secure key cleanup.
Copy of the public key data (safe to share)
Security level (1, 3, or 5)
- Securely zeroes out all sensitive key material in memory
- Should be called using
defer key.Clear()immediately after key creation - Uses
runtime/secret.Doto ensure secure cleanup
MUST Requirements: Key clearing operations MUST use Go's runtime/secret package to ensure that sensitive data is securely erased from memory.
Clear()method MUST wrap key clearing operations withinruntime/secret.Doto ensure that key data is securely zeroed- This provides defense-in-depth by ensuring that even the cleanup operations are protected from memory analysis attacks
- Implementations MUST wrap key clearing operations within the secret execution context to maximize protection of sensitive cryptographic material