Go library to detect PGP flavor and packet version from ASCII-armored public keys.
- OpenPGP (v6 RFC-9580)
- LibrePGP (v4 RFC-4880, v5 draft-koch-librepgp)
# Install the module
go get github.com/KEINOS/go-which-pgp// Use the package
import "github.com/KEINOS/go-which-pgp/whichpgp"package main
import (
"fmt"
"github.com/KEINOS/go-which-pgp/whichpgp"
)
func main() {
// ASCII armored PGP public key block
pubKey := `-----BEGIN PGP PUBLIC KEY BLOCK-----
**snip**
-----END PGP PUBLIC KEY BLOCK-----`
// Direct string input and output
flavor, version, err := whichpgp.DetectFlavorFromArmor(pubKey)
if err != nil {
panic(err)
}
fmt.Printf("Flavor: %s, Version: %d\n", flavor, version)
//
// Output:
// Flavor: LibrePGP (v5), Version: 5
}package main
import (
"fmt"
"github.com/KEINOS/go-which-pgp/whichpgp"
)
func main() {
// ASCII armored PGP public key block
pubKey := `-----BEGIN PGP PUBLIC KEY BLOCK-----
**snip**
-----END PGP PUBLIC KEY BLOCK-----`
// Return structured result
result, err := whichpgp.DetectFlavorFromBytes([]byte(pubKey))
if err != nil {
panic(err)
}
fmt.Printf("Flavor: %s\n", result.Flavor.String())
fmt.Printf("Version: %d\n", result.PacketVersion)
fmt.Printf("Description: %s\n", result.String())
//
// Output:
// Flavor: OpenPGP
// Version: 6
// Description: OpenPGP (v6)
}You can control the behavior with options.
pubKey := `-----BEGIN PGP PUBLIC KEY BLOCK-----
**snip**
-----END PGP PUBLIC KEY BLOCK-----`
// With options for customization
result, err := whichpgp.DetectFlavorFromBytes([]byte(pubKey),
whichpgp.WithStrictCRC(false), // Allow missing CRC
whichpgp.WithMaxBytes(1024*1024), // Set size limit
whichpgp.WithBufferSize(8192), // Custom buffer size
)import (
"os"
"path/filepath"
)
filePath := filepath.Join("..", "testdata", "sample-v5-certificate-trans.asc")
file, err := os.Open(filePath)
if err != nil {
panic(err)
}
defer file.Close()
// From io.Reader for streaming
result, err := whichpgp.DetectFlavorFromReader(file,
whichpgp.WithStrictCRC(false), // Allow missing CRC
whichpgp.WithMaxBytes(1024*1024), // Set size limit
whichpgp.WithBufferSize(8192), // Custom buffer size
)- 📖 More examples: pkg.go.dev documentation
The library provides three main functions for detecting PGP flavors:
DetectFlavorFromBytes(data []byte, opts ...Option) (Result, error)- Detects PGP flavor from byte data with optional configurationDetectFlavorFromReader(r io.Reader, opts ...Option) (Result, error)- Detects PGP flavor from anyio.ReadersourceDetectFlavorFromString(data string, opts ...Option) (Result, error)- Convenience function for string inputs with optionsDetectFlavorFromArmor(armor string) (string, int, error)- Simple string-based detection API
The new APIs return a structured Result type:
type Result struct {
Flavor Flavor // Type-safe enum: FlavorUnknown, FlavorLibrePGP, FlavorOpenPGP
PacketVersion uint8 // PGP packet version (4, 5, 6)
}
// Methods
result.String() // Human-readable description: "LibrePGP (v4)"
result.Flavor.String() // Flavor name: "LibrePGP"Customize behavior with functional options:
WithStrictCRC(bool)- Require valid CRC checksums (default: false)WithMaxBytes(int)- Set maximum data size limit (default: 8 MiB)WithBufferSize(int)- Set buffer size for reader operations (default: 64 KiB)WithScanCap(int)- Set packet scanning limit for safety (default: 4 MiB)
| Flavor | Packet Versions | Description |
|---|---|---|
| LibrePGP | v4, v5 | Based on draft specifications |
| OpenPGP | v6 | RFC 9580 standard |
| Unknown | - | Unrecognized or invalid format |
- Flavor: Logical family name for the ecosystem. This library returns either "LibrePGP" or "OpenPGP".
- Public‑Key Packet Version: Version of the public‑key packet format inside the key material (v4, v5, or v6). This is not a library or application version.
- Relationship: RFC 9580 standardizes OpenPGP with Packet v6. LibrePGP continues the v4/v5 packet lineage that has been developed in the community. This library focuses on detecting the ecosystem (flavor) and the packet version present in the provided key.
Important
Policy note: Packet versions are not a "higher-is-better" ranking. They represent different specification families and trade-offs. Choose the packet version that aligns with your interoperability and security policy.
All detection functions follow these principles:
- Return values:
DetectFlavorFromBytes,DetectFlavorFromReader&DetectFlavorFromString:ResultwithFlavorenum andPacketVersionuint8DetectFlavorFromArmor: human-readable description string (e.g., "LibrePGP (v4)", "OpenPGP (v6 / RFC 9580)") and version int (4, 5, or 6)
- Headers: Ignores all headers until a blank/whitespace-only line
- Line endings: Accepts LF/CRLF, multiple blank lines, and trailing blanks after END
- Base64 body: Tolerates embedded ASCII whitespace (space/tab/CR/LF)
- If CRC line exists: Validates strictly - invalid CRC returns error
- If CRC missing: Allowed by default (use
WithStrictCRC(true)to require) - Configurable: Use
WithStrictCRC(false)to ignore CRC validation entirely- See RFC 4880 (Armor/Checksum, e.g., Section 6.3) and RFC 9580
- Size limits and safety
- Pre-decode size guard: rejects oversized base64 bodies early (DoS prevention)
- Packet scan cap: internal scanning limited to ~4 MiB by default; error suggests increasing cap
go test ./... # Tests
golangci-lint run # Lint
go test -fuzz=Fuzz* -run=^$ # Fuzz testingMIT