Bastion Label Format Specification
Version: 1.0
Date: 2025-11-30
Status: Stable
Note on Examples: Version numbers shown in examples (e.g.,
VERSION=0.3.0) are illustrative. Refer to VERSION for the canonical tool version used during label generation.
Overview
This specification defines the unified label format for Bastion. Labels serve two purposes:
- 1Password Tag Hierarchy — The hierarchy portion creates browsable folder structure in 1Password UI
- Reproducible Generation — The complete label contains all parameters needed to regenerate credentials
Design Principles
| Principle | Rationale |
|---|---|
| Front-loaded hierarchy | Most important classification info appears first, creating useful 1P folder structure |
| Consistent separators | / for hierarchy, : for metadata, # for params, \| for check digit |
| UPPERCASE attributes | Visual consistency, easy parsing, follows cryptographic convention |
| URL query-string notation | ATTR=value&ATTR=value pattern uses familiar URL conventions |
| Self-describing | VERSION always required; label contains everything needed for regeneration |
| 1P tag compatible | Hierarchy portion (before first :) is a valid 1Password tag |
Grammar
LABEL = HIERARCHY ":" IDENT ":" DATE "#" PARAMS "|" CHECK
LABEL = HIERARCHY ":" IDENT ":" DATE "#" PARAMS (without check)
HIERARCHY = TOOL "/" TYPE "/" ALGO
ALGO = ALGO_PART ("/" ALGO_PART)*
PARAMS = "VERSION=" VERSION_NUM ("&" ATTR "=" VALUE)*
TOOL = "Bastion" (or other PascalCase tool name)
TYPE = "USER" | "CARD" | "KEY"
ALGO_PART = [A-Z0-9]+
IDENT = [a-z0-9._-]+
DATE = YYYY-MM-DD | user-defined
VERSION_NUM = MAJOR ("." MINOR)?
ATTR = [A-Z]+
VALUE = [A-Za-z0-9._-]+ (non-empty, no &, =, |, :, #)
CHECK = [0-9A-Z] (single Luhn mod-36 character)
Field Definitions
Hierarchy Fields (1Password Tag)
| Field | Format | Description | Examples |
|---|---|---|---|
| TOOL | PascalCase | Tool that generated the credential | Bastion |
| TYPE | UPPERCASE | Generator type | USER, CARD, KEY |
| ALGO | UPPERCASE, /-separated |
Algorithm family/variant | SHA2/512, SLIP39/ARGON2ID |
Metadata Fields
| Field | Format | Description | Examples |
|---|---|---|---|
| IDENT | lowercase, allows . - _ |
Service or purpose identifier | github.com, aws-prod, banking.a0 |
| DATE | ISO 8601 recommended | Generation date or descriptor | 2025-11-30, initial |
Parameter Fields
| Field | Format | Description | Examples |
|---|---|---|---|
| VERSION | VERSION={semver} |
Bastion tool version that generated the credential (always required) | VERSION=0.3.0, VERSION=1.0.0 |
| TIME | TIME={n} |
Argon2 time cost (iterations) | TIME=3, TIME=10 |
| MEMORY | MEMORY={n} |
Argon2 memory cost (KB) | MEMORY=65536, MEMORY=2048 |
| PARALLELISM | PARALLELISM={n} |
Argon2 parallelism (threads) | PARALLELISM=4, PARALLELISM=8 |
| NONCE | NONCE={value} |
Random nonce for non-recoverable generation (see Nonce Mode) | NONCE=Kx7mQ9bL |
| LENGTH | LENGTH={n} |
Output length (characters) | LENGTH=16, LENGTH=24 |
| ENCODING | ENCODING={n} |
Output encoding alphabet size | ENCODING=36, ENCODING=64, ENCODING=90 |
Note on VERSION parameter: The
VERSIONparameter records which version of Bastion generated the credential. This allows checking release history for potential breaking changes. For example,VERSION=0.3.0indicates the credential was generated by Bastion v0.3.0. This replaces the previous dual-parameter system (BASTION + VERSION) used in earlier drafts.
Nonce Mode (Stolen Seed Protection)
The NONCE parameter enables non-recoverable usernames that provide protection against stolen seeds.
Threat Model: Without nonce mode, an attacker who compromises the secret salt can regenerate ALL usernames by iterating through common domains. Nonce mode prevents this attack by including a random value in the generation that only exists in the stored label.
How It Works:
- When
--nonceflag is used, a random URL-safe string is generated - The nonce is included in the HMAC message:
HMAC(salt, label + ":" + nonce) - The nonce is stored in the label’s PARAMS section:
NONCE=Kx7mQ9bL - To regenerate, you MUST have both the salt AND the original label with nonce
Security Trade-off:
| Mode | Recoverable? | Stolen Seed Risk | Dependency |
|---|---|---|---|
| Standard | Yes, from salt + domain | All usernames exposed | Salt only |
| Nonce | Only with original label | Only stored usernames exposed | Salt + 1Password |
When to Use Nonce Mode:
- High-security accounts where you always have 1Password access
- When you want defense-in-depth against salt compromise
- When you don’t need offline recovery from salt alone
When NOT to Use Nonce Mode:
- Emergency recovery scenarios where 1Password may be unavailable
- Accounts that need to be recovered from seed cards only
- When you want deterministic generation from minimal inputs
Delimiters
| Delimiter | Purpose | Example |
|---|---|---|
/ |
Hierarchy separator (creates 1P folders) | Bastion/USER/SHA2/512 |
: |
Metadata field separator | :github.com:2025-11-30 |
# |
Params section marker | #VERSION=1&LENGTH=16 |
& |
Param attribute separator (URL-style) | TIME=3&MEMORY=65536&PARALLELISM=4 |
= |
Attribute name/value separator (URL-style) | VERSION=1, LENGTH=16 |
\| |
Check digit separator | \|K |
Algorithm Naming Convention
Algorithms use hierarchical naming with / separator:
Hash Functions
| Algorithm | Label Format | 1P Folder Path |
|---|---|---|
| SHA-256 (SHA-2) | SHA2/256 |
…/SHA2/256 |
| SHA-512 (SHA-2) | SHA2/512 |
…/SHA2/512 |
| SHA3-256 | SHA3/256 |
…/SHA3/256 |
| SHA3-512 | SHA3/512 |
…/SHA3/512 |
Key Derivation Functions
| Algorithm | Label Format | 1P Folder Path |
|---|---|---|
| Argon2id | ARGON2ID |
…/ARGON2ID |
| SLIP39 + Argon2id | SLIP39/ARGON2ID |
…/SLIP39/ARGON2ID |
| PBKDF2 + SHA-256 | PBKDF2/SHA2/256 |
…/PBKDF2/SHA2/256 |
Key Types
| Algorithm | Label Format | 1P Folder Path |
|---|---|---|
| X25519 | X25519 |
…/X25519 |
| Ed25519 | ED25519 |
…/ED25519 |
| RSA 2048-bit | RSA/2048 |
…/RSA/2048 |
| RSA 4096-bit | RSA/4096 |
…/RSA/4096 |
Parameter Ordering
When multiple attributes are present, they MUST appear in this fixed order:
VERSION— Bastion tool version (SemVer, always required)TIME— Argon2 time costMEMORY— Argon2 memory costPARALLELISM— Argon2 parallelismNONCE— Generation nonceLENGTH— Output lengthENCODING— Output encoding
Only include attributes relevant to the algorithm. VERSION is always required.
Examples
Bastion Username (SHA2-512)
Bastion/USER/SHA2/512:github.com:2025-11-30#VERSION=0.3.0&LENGTH=16|K
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/USER/SHA2/512 |
Browsable in 1Password |
| IDENT | github.com |
Service identifier |
| DATE | 2025-11-30 |
Generation date |
| VERSION | 0.3.0 |
Bastion tool version |
| LENGTH | 16 |
16-character username |
| CHECK | K |
Luhn mod-36 check digit |
Bastion Username with Nonce (SHA2-512, Stolen Seed Protection)
Bastion/USER/SHA2/512:github.com:2025-11-30#VERSION=0.3.0&NONCE=Kx7mQ9bL&LENGTH=16|M
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/USER/SHA2/512 |
Browsable in 1Password |
| IDENT | github.com |
Service identifier |
| DATE | 2025-11-30 |
Generation date |
| VERSION | 0.3.0 |
Bastion tool version |
| NONCE | Kx7mQ9bL |
Random nonce (makes non-recoverable) |
| LENGTH | 16 |
16-character username |
| CHECK | M |
Luhn mod-36 check digit |
Security Note: This username cannot be regenerated from salt + domain alone. The nonce value must be preserved in 1Password.
Bastion Username (SHA3-512, minor version)
Bastion/USER/SHA3/512:aws.amazon.com:2025-11-30#VERSION=1.1&LENGTH=24|M
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/USER/SHA3/512 |
SHA3 variant |
| VERSION | 1.1 |
Label format v1.1 |
| LENGTH | 24 |
24-character username |
Bastion Card Token (SLIP39/ARGON2ID)
Bastion/CARD/SLIP39/ARGON2ID:banking.a0:2025-11-30#VERSION=1&TIME=3&MEMORY=2048&PARALLELISM=8&NONCE=Kx7mQ9bL&ENCODING=90|X
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/CARD/SLIP39/ARGON2ID |
Full algo hierarchy |
| IDENT | banking.a0 |
Card ID + index |
| TIME | 3 |
Argon2 iterations |
| MEMORY | 2048 |
Argon2 memory (KB) |
| PARALLELISM | 8 |
Argon2 threads |
| NONCE | Kx7mQ9bL |
Unique nonce |
| ENCODING | 90 |
Base90 output |
Bastion Key (X25519, minimal params)
Bastion/KEY/X25519:ssh-primary:2025-11-30#VERSION=1|J
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/KEY/X25519 |
Key type |
| IDENT | ssh-primary |
Key purpose |
| VERSION | 1 |
Only required param |
Bastion Key (RSA 4096)
Bastion/KEY/RSA/4096:signing-key:2025-11-30#VERSION=1|Q
| Component | Value | Description |
|---|---|---|
| 1P Tag | Bastion/KEY/RSA/4096 |
RSA with key size in hierarchy |
1Password Tag Extraction
To extract the 1Password tag from a label:
TAG = everything before the first ":"
Examples:
Bastion/USER/SHA2/512:...|K→Bastion/USER/SHA2/512Bastion/CARD/SLIP39/ARGON2ID:...|X→Bastion/CARD/SLIP39/ARGON2ID
This creates a browsable folder hierarchy in 1Password:
Bastion/
├── USER/
│ ├── SHA2/
│ │ ├── 256/
│ │ └── 512/
│ └── SHA3/
│ └── 512/
├── CARD/
│ └── SLIP39/
│ └── ARGON2ID/
├── KEY/
│ ├── X25519/
│ ├── ED25519/
│ └── RSA/
│ ├── 2048/
│ └── 4096/
├── SALT/ # Salt items (no algorithm hierarchy)
├── ENTROPY/ # Entropy pool items
└── CONFIG/ # Configuration items
Luhn Mod-36 Check Digit
The optional check digit detects transcription errors:
- Alphabet:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ(36 characters) - Computed over: Everything before
|(the BODY) - Algorithm: Standard Luhn mod-N with N=36
The check digit catches:
- Single character substitutions
- Adjacent character transpositions
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Hierarchy separator | / |
Native 1Password tag hierarchy support |
| VERSION location | In PARAMS, not hierarchy | Keeps 1P folder structure clean; version is parsing metadata |
| PARAMS at end | After # |
Separates machine-parsed params from human-readable hierarchy/metadata |
| Attribute format | ATTR.value |
Consistent dot notation; avoids ambiguity with - separator |
| UPPERCASE attributes | TIME, MEMORY, etc. |
Visual consistency, cryptographic convention, easy parsing |
| Tool name case | PascalCase | Better UX in 1Password UI folder display |
| CARD type | Bastion/CARD/... |
Seed card tokens unified under Bastion; distinguishes from usernames and keys |
| SHA2 vs SHA | SHA2/512 |
Explicit SHA-2 family disambiguation from SHA-1 and SHA-3 |
| RSA with size | RSA/2048 |
Groups RSA keys; size in hierarchy for browsability |
| VERSION always required | Yes | Labels are fully self-describing; no implicit defaults |
| Fixed param order | Yes | Deterministic parsing; canonical form for comparison |
Character Sets
| Field | Allowed Characters |
|---|---|
| TOOL | [A-Za-z][A-Za-z0-9]* (PascalCase) |
| TYPE | [A-Z]+ |
| ALGO | [A-Z0-9/]+ |
| IDENT | [a-z0-9._-]+ |
| DATE | [A-Za-z0-9-]+ |
| PARAMS keys | [A-Z]+ (UPPERCASE attribute names) |
| PARAMS values | [A-Za-z0-9._-]+ (non-empty, no &, =, \|, :, #) |
| CHECK | [0-9A-Z] (single character) |
Validation Rules
- HIERARCHY must contain at least
TOOL/TYPE/ALGO - IDENT must not be empty
- DATE must not be empty
- PARAMS must start with
VERSION={n} - If
|is present, exactly one CHECK character must follow - All attributes in PARAMS must be UPPERCASE
- Attributes must appear in canonical order
- Parameter values must be non-empty and contain only
[A-Za-z0-9._-] - Parameter values cannot contain reserved characters:
&,=,|,:,#
1Password Record Structure
When creating 1Password items, the label components are stored in structured sections for both human readability and machine recovery.
Tags
The HIERARCHY portion of the label becomes a 1Password tag, creating a browsable folder structure:
Tags: Bastion/USER/SHA2/512
The tag is extracted from the label hierarchy (everything before the first :).
Bastion Label Section
A dedicated section stores the complete label and its parsed components, in label order:
| Field | Type | Description | Example |
|---|---|---|---|
| Label | text | Complete label string | Bastion/USER/SHA2/512:github.com:2025-11-30#VERSION=1&LENGTH=16\|K |
| Type | text | Generator type | USER |
| Algorithm | text | Algorithm identifier | SHA2/512 |
| Identifier | text | Service/purpose | github.com |
| Date | text | Generation date | 2025-11-30 |
| Version | text | Label format version | 1 |
| Length | text | Output length (USER) | 16 |
| Time | text | Argon2 time cost (CARD) | 3 |
| Memory | text | Argon2 memory KB (CARD) | 2048 |
| Parallelism | text | Argon2 threads (CARD) | 8 |
| Nonce | text | Generation nonce (CARD) | Kx7mQ9bL |
| Encoding | text | Output encoding (CARD) | 90 |
Only include fields relevant to the generator type.
Salt Section
A dedicated section stores the reference to the salt used for generation:
| Field | Type | Description | Example |
|---|---|---|---|
| UUID | text | Reference to salt item in 1Password | abc123-def456-... |
Example: Username Record
Title: Github
URL: https://github.com
Username: 10178t59dlzfkece
Tags:
- Bastion/USER/SHA2/512
[Bastion Label]
Label: Bastion/USER/SHA2/512:github.com:2025-11-30#VERSION=1&LENGTH=16|K
Type: USER
Algorithm: SHA2/512
Identifier: github.com
Date: 2025-11-30
Version: 1
Length: 16
[Salt]
UUID: abc123-def456-...
Example: Card Token Record
Title: Banking Card A0
Category: Secure Note
Tags:
- Bastion/CARD/SLIP39/ARGON2ID
[Bastion Label]
Label: Bastion/CARD/SLIP39/ARGON2ID:banking.a0:2025-11-30#VERSION=1&TIME=3&MEMORY=2048&PARALLELISM=8&NONCE=Kx7mQ9bL&ENCODING=90|X
Type: CARD
Algorithm: SLIP39/ARGON2ID
Identifier: banking.a0
Date: 2025-11-30
Version: 1
Time: 3
Memory: 2048
Parallelism: 8
Nonce: Kx7mQ9bL
Encoding: 90
[Salt]
UUID: xyz789-...
Example: Key Record
Title: SSH Primary Key
Category: Secure Note
Tags:
- Bastion/KEY/X25519
[Bastion Label]
Label: Bastion/KEY/X25519:ssh-primary:2025-11-30#VERSION=1|J
Type: KEY
Algorithm: X25519
Identifier: ssh-primary
Date: 2025-11-30
Version: 1
[Salt]
UUID: key456-...
Example: Entropy Source Record
Title: Bastion Entropy Source #3
Category: Secure Note
Tags:
- Bastion/ENTROPY
[Pool Info]
pool_type: source
source: YubiKey OpenPGP
source_type: hardware
[Size]
bytes: 256
bits: 2048
[Lifecycle]
created: 2025-11-30T10:30:00Z
status: active
Example: Entropy Source Record (Infinite Noise TRNG)
Title: Bastion Entropy Source #4
Category: Secure Note
Tags:
- Bastion/ENTROPY
[Pool Info]
pool_type: source
source: Infinite Noise TRNG
source_type: hardware
[Device Metadata]
serial: ABC123XYZ
whitened: true
collection_method: infnoise CLI
[Size]
bytes: 64
bits: 512
[Lifecycle]
created: 2025-11-30T11:00:00Z
status: active
Example: Entropy Derived Record
Title: Bastion Entropy Derived #1
Category: Secure Note
Tags:
- Bastion/ENTROPY
[Pool Info]
pool_type: derived
source: combine
derivation_method: sha3_512
[Entropy Sources]
Source 1: abc123-def456-...
Source 2: xyz789-012345-...
Source 3: ghi678-jkl901-...
[Size]
bytes: 64
bits: 512
[Lifecycle]
created: 2025-11-30T10:35:00Z
status: active
Entropy Sources Section
For derived entropy pools, source UUIDs are stored as individual fields rather than a CSV string. This provides:
- Browsability: Each source is a distinct field in 1Password UI
- Linking: 1Password can potentially auto-link to referenced items
- Readability: No need to parse CSV when viewing the record
Field naming: Source 1, Source 2, etc. (numbered sequentially)
Field Naming Convention
Following 1Password data model best practices:
- Human-readable: Use natural language field names (
Salt UUIDnotsalt_uuid) - Title case: Field names are title case for readability
- Separate sections: Label metadata and params in distinct sections
- Complete redundancy: Store both full label AND parsed components (for recovery)