keychainbreaker

package module
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 2, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

keychainbreaker

Go CI codecov Go Reference Go Report Card License

Go library for reading and decrypting macOS Keychain files (login.keychain-db). Supports OS X 10.6 (Snow Leopard) through macOS 26 (Tahoe).

What It Does

Open a macOS Keychain file, unlock it with the user's password, and extract:

  • Generic passwords -- app-stored credentials (Chrome Safe Storage, Wi-Fi, etc.)
  • Internet passwords -- web and network credentials (GitHub tokens, Docker registry, SMB shares, etc.)
  • Private keys -- RSA/EC private keys stored in the keychain
  • X.509 certificates -- DER-encoded certificates

Works on any OS (Linux, macOS, Windows). No CGO. No macOS APIs. Just reads the binary file.

CLI Tool

Install

Homebrew

brew tap moond4rk/tap
brew install moond4rk/tap/keychainbreaker

Go install (requires Go 1.26+)

go install github.com/moond4rk/keychainbreaker/cmd/keychainbreaker@latest

Binary

Download from GitHub Releases.

Usage
$ keychainbreaker -h

Commands:
  dump        Export all keychain data to a JSON file
  hash        Print password hash for offline cracking (no unlock needed)
  version     Print version information

Flags:
  -f, --file string       Keychain file path (default: ~/Library/Keychains/login.keychain-db)
  -p, --password string   Keychain password (omit to be prompted)
  -k, --key string        Hex-encoded 24-byte master key
  -o, --output string     Output file path (default: ./keychain_dump.json)
dump

Export all keychain data (passwords, keys, certificates) to a single JSON file. Passwords are output in three formats: plaintext, hex, and base64.

$ keychainbreaker dump
Keychain: /Users/user/Library/Keychains/login.keychain-db
Enter keychain password:
Extracted:
  Generic passwords:  42
  Internet passwords: 15
  Private keys:       2
  Certificates:       8
Output: keychain_dump.json
# Specify keychain file and password
keychainbreaker dump -f /path/to/login.keychain-db -p "password"

# Export to a specific file
keychainbreaker dump -o result.json

# Wrong password: still exports metadata (service, account, timestamps, certs)
keychainbreaker dump -p "wrong"
hash
# Export hash for offline cracking (no password needed)
keychainbreaker hash
# $keychain$*<salt_hex>*<iv_hex>*<ciphertext_hex>

Go Library

Install
go get github.com/moond4rk/keychainbreaker

Requires Go 1.20+.

Quick Start
// Open the default macOS login keychain
kc, err := keychainbreaker.Open()

// Unlock with the user's macOS login password
err = kc.Unlock(keychainbreaker.WithPassword("your-macos-login-password"))

// Extract all saved passwords
passwords, err := kc.GenericPasswords()
for _, p := range passwords {
    fmt.Printf("Service: %s, Account: %s, Password: %s\n",
        p.Service, p.Account, p.Password)
}
Usage
Open a Keychain
kc, err := keychainbreaker.Open()                                      // default system keychain
kc, err := keychainbreaker.Open(keychainbreaker.WithFile("/path/to"))   // specific file
kc, err := keychainbreaker.Open(keychainbreaker.WithBytes(buf))         // from memory
Unlock
err = kc.Unlock(keychainbreaker.WithPassword("macos-login-password"))   // with password
err = kc.Unlock(keychainbreaker.WithKey("hex-encoded-24-byte-key"))     // with master key
TryUnlock (partial extraction)

TryUnlock attempts to decrypt but does not block extraction on failure. When the password is wrong or unavailable, metadata (service, account, timestamps, etc.) is still returned with encrypted fields set to nil.

// Wrong password: metadata still available, passwords nil
err = kc.TryUnlock(keychainbreaker.WithPassword("maybe-wrong"))
passwords, _ := kc.GenericPasswords()   // passwords[0].Service = "moond4rk.com"
                                        // passwords[0].Password = nil

// No credential: just metadata
kc.TryUnlock()
passwords, _ := kc.GenericPasswords()

// Check if decryption succeeded
if kc.Unlocked() {
    // full data available
}
Extract Records
genericPasswords, err := kc.GenericPasswords()     // app credentials
internetPasswords, err := kc.InternetPasswords()   // web/network credentials
privateKeys, err := kc.PrivateKeys()               // encrypted private keys
certificates, err := kc.Certificates()             // X.509 certificates
hash, err := kc.PasswordHash()                     // offline cracking hash (no unlock needed)
Record Types
GenericPassword
type GenericPassword struct {
    Service     string
    Account     string
    Password    []byte    // raw bytes; caller decides encoding
    Description string
    Comment     string
    Creator     string
    Type        string
    PrintName   string
    Alias       string
    Created     time.Time
    Modified    time.Time
}
InternetPassword
type InternetPassword struct {
    Server         string
    Account        string
    Password       []byte
    SecurityDomain string
    Protocol       string    // "htps", "smb ", etc.
    AuthType       string
    Port           uint32
    Path           string
    Description    string
    Comment        string
    Creator        string
    Type           string
    PrintName      string
    Alias          string
    Created        time.Time
    Modified       time.Time
}
PrivateKey
type PrivateKey struct {
    Name      string // first 12 bytes of decrypted data
    Data      []byte // raw key material (PKCS#8)
    PrintName string
    Label     string
    KeyClass  uint32
    KeyType   uint32
    KeySize   uint32
}
Certificate
type Certificate struct {
    Data      []byte // raw DER-encoded certificate
    Type      uint32
    Encoding  uint32
    PrintName string
    Subject   []byte
    Issuer    []byte
    Serial    []byte
}

How It Works

macOS Keychain uses a three-layer encryption scheme:

Password --> PBKDF2 --> Master Key --> DB Key --> Per-Record Keys --> Plaintext
  1. Master key derived from password via PBKDF2-HMAC-SHA1
  2. Database key decrypted from the keychain's metadata blob
  3. Per-record keys unwrapped using RFC 3217 Triple-DES Key Wrap
  4. Passwords/keys decrypted using per-record keys with 3DES-CBC

The library dynamically discovers table schemas from the keychain file itself, making it robust across macOS versions (10.6 through 26).

See RFC 001 for the full encryption specification.

Compatibility

Supported Not Supported
.keychain and .keychain-db files keychain-2.db (iCloud Keychain)
OS X 10.6 through macOS 26 (Tahoe) Secure Enclave protected keys
Linux, macOS, Windows (cross-compile)

License

Apache-2.0

Documentation

Overview

Package keychainbreaker parses and decrypts macOS Keychain files (login.keychain-db).

The library implements Apple's CSSM-based keychain binary format parser with dynamic schema discovery, and supports decrypting stored credentials using Triple-DES CBC encryption.

Quick Start

Open the default system keychain and unlock with password:

kc, err := keychainbreaker.Open()
if err != nil {
    log.Fatal(err)
}

if err := kc.Unlock(keychainbreaker.WithPassword("your-macos-login-password")); err != nil {
    log.Fatal(err)
}

passwords, err := kc.GenericPasswords()
if err != nil {
    log.Fatal(err)
}

for _, p := range passwords {
    fmt.Printf("Service: %s, Account: %s, Password: %s\n", p.Service, p.Account, p.Password)
}

Open Options

Three ways to open a keychain:

  • Open() -- default system login keychain
  • Open(WithFile) -- specific file path
  • Open(WithBytes) -- in-memory buffer

Unlock Methods

Two unlock methods are supported:

  • WithPassword: derives master key from password via PBKDF2-HMAC-SHA1
  • WithKey: uses a hex-encoded 24-byte master key directly (from memory forensics)

Package keychainbreaker parses and decrypts macOS Keychain files.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidSignature is returned when the file is not a valid keychain.
	ErrInvalidSignature = errors.New("keychainbreaker: invalid keychain signature")

	// ErrParseFailed is returned when the keychain file structure cannot be parsed.
	ErrParseFailed = errors.New("keychainbreaker: parse failed")
)

Errors returned by Open.

View Source
var (
	// ErrLocked is returned when the keychain has not been unlocked yet.
	ErrLocked = errors.New("keychainbreaker: keychain is locked")
)

Errors returned by record extraction methods.

View Source
var (
	// ErrWrongKey is returned when the provided key or password is incorrect.
	ErrWrongKey = errors.New("keychainbreaker: wrong key or password")
)

Errors returned by Unlock.

Functions

This section is empty.

Types

type Certificate

type Certificate struct {
	Data       []byte `json:"-"`
	DataHex    string `json:"data_hex,omitempty"`
	DataBase64 string `json:"data_base64,omitempty"`
	Type       uint32 `json:"type,omitempty"`
	Encoding   uint32 `json:"encoding,omitempty"`
	PrintName  string `json:"print_name,omitempty"`
	Subject    []byte `json:"-"`
	SubjectHex string `json:"subject_hex,omitempty"`
	Issuer     []byte `json:"-"`
	IssuerHex  string `json:"issuer_hex,omitempty"`
	Serial     []byte `json:"-"`
	SerialHex  string `json:"serial_hex,omitempty"`
}

Certificate represents an X.509 certificate record.

type GenericPassword

type GenericPassword struct {
	Service        string    `json:"service,omitempty"`
	Account        string    `json:"account,omitempty"`
	Password       []byte    `json:"-"`
	PlainPassword  string    `json:"password,omitempty"` //nolint:gosec // intentional credential export
	HexPassword    string    `json:"hex_password,omitempty"`
	Base64Password string    `json:"base64_password,omitempty"`
	Description    string    `json:"description,omitempty"`
	Comment        string    `json:"comment,omitempty"`
	Creator        string    `json:"creator,omitempty"`
	Type           string    `json:"type,omitempty"`
	PrintName      string    `json:"print_name,omitempty"`
	Alias          string    `json:"alias,omitempty"`
	Created        time.Time `json:"created_at,omitempty"`
	Modified       time.Time `json:"modified_at,omitempty"`
}

GenericPassword represents a generic password record.

type InternetPassword

type InternetPassword struct {
	Server         string    `json:"server,omitempty"`
	Account        string    `json:"account,omitempty"`
	Password       []byte    `json:"-"`
	PlainPassword  string    `json:"password,omitempty"` //nolint:gosec // intentional credential export
	HexPassword    string    `json:"hex_password,omitempty"`
	Base64Password string    `json:"base64_password,omitempty"`
	SecurityDomain string    `json:"security_domain,omitempty"`
	Protocol       string    `json:"protocol,omitempty"`
	AuthType       string    `json:"auth_type,omitempty"`
	Port           uint32    `json:"port,omitempty"`
	Path           string    `json:"path,omitempty"`
	Description    string    `json:"description,omitempty"`
	Comment        string    `json:"comment,omitempty"`
	Creator        string    `json:"creator,omitempty"`
	Type           string    `json:"type,omitempty"`
	PrintName      string    `json:"print_name,omitempty"`
	Alias          string    `json:"alias,omitempty"`
	Created        time.Time `json:"created_at,omitempty"`
	Modified       time.Time `json:"modified_at,omitempty"`
}

InternetPassword represents an internet password record.

type Keychain

type Keychain struct {
	// contains filtered or unexported fields
}

Keychain represents a parsed macOS keychain file.

func Open

func Open(opts ...OpenOption) (*Keychain, error)

Open parses a keychain file and returns a locked Keychain. Call Unlock before extracting records.

With no options, it opens the default macOS login keychain (~/Library/Keychains/login.keychain-db):

kc, err := keychainbreaker.Open()

With a specific file path:

kc, err := keychainbreaker.Open(keychainbreaker.WithFile("/path/to/keychain"))

From an in-memory buffer:

kc, err := keychainbreaker.Open(keychainbreaker.WithBytes(buf))

func (*Keychain) Certificates

func (kc *Keychain) Certificates() ([]Certificate, error)

Certificates returns all X.509 certificate records. Returns ErrLocked if neither Unlock nor TryUnlock has been called. Certificates are not encrypted, so they are always fully available regardless of whether decryption succeeded.

func (*Keychain) GenericPasswords

func (kc *Keychain) GenericPasswords() ([]GenericPassword, error)

GenericPasswords returns all generic password records. Returns ErrLocked if neither Unlock nor TryUnlock has been called. When TryUnlock is used and decryption fails, metadata fields are returned but Password will be nil.

func (*Keychain) InternetPasswords

func (kc *Keychain) InternetPasswords() ([]InternetPassword, error)

InternetPasswords returns all internet password records. Returns ErrLocked if neither Unlock nor TryUnlock has been called. When TryUnlock is used and decryption fails, metadata fields are returned but Password will be nil.

func (*Keychain) PasswordHash

func (kc *Keychain) PasswordHash() (string, error)

PasswordHash exports the keychain password hash for offline cracking. Format: $keychain$*<salt_hex>*<iv_hex>*<ciphertext_hex> Compatible with hashcat mode 23100 and John the Ripper. Does not require Unlock.

func (*Keychain) PrivateKeys

func (kc *Keychain) PrivateKeys() ([]PrivateKey, error)

PrivateKeys returns all private key records. Returns ErrLocked if neither Unlock nor TryUnlock has been called. When TryUnlock is used and decryption fails, metadata fields are returned but Name and Data will be empty.

func (*Keychain) TryUnlock added in v0.2.0

func (kc *Keychain) TryUnlock(opts ...UnlockOption) error

TryUnlock attempts to decrypt the keychain, but does not block record extraction on failure. If the credential is wrong or missing, extraction methods still return record metadata (service, account, timestamps, etc.) with encrypted fields (passwords, private key data) set to nil.

TryUnlock returns any unlock error (e.g. ErrWrongKey) for informational purposes, but the caller can safely ignore it and proceed with extraction.

func (*Keychain) Unlock

func (kc *Keychain) Unlock(opt UnlockOption) error

Unlock decrypts the keychain using the provided credential. After a successful Unlock, record extraction methods become available. If Unlock fails, extraction methods return ErrLocked. Use TryUnlock instead if you want to extract metadata even when the password is wrong or unavailable.

func (*Keychain) Unlocked added in v0.2.0

func (kc *Keychain) Unlocked() bool

Unlocked reports whether the keychain has been successfully decrypted.

type Logger added in v0.2.5

type Logger interface {
	Debug(msg string, keysAndValues ...any)
	Info(msg string, keysAndValues ...any)
	Warn(msg string, keysAndValues ...any)
	Error(msg string, keysAndValues ...any)
}

Logger is the interface for diagnostic logging within keychainbreaker. Implement this interface to receive verbose diagnostic messages from the library during parsing and unlock operations.

The keysAndValues arguments are key-value pairs (e.g., "saltLen", 20).

The default logger is silent (no-op). Use WithLogger to inject a custom implementation.

type OpenOption

type OpenOption func(*openConfig)

OpenOption configures how to open a keychain.

func WithBytes

func WithBytes(buf []byte) OpenOption

WithBytes provides keychain data from an in-memory buffer.

func WithFile

func WithFile(path string) OpenOption

WithFile specifies a keychain file path.

func WithLogger added in v0.2.5

func WithLogger(l Logger) OpenOption

WithLogger sets a custom logger for diagnostic output. By default, the library is silent (no-op logger).

type PrivateKey

type PrivateKey struct {
	Name       string `json:"name,omitempty"`
	Data       []byte `json:"-"`
	DataHex    string `json:"data_hex,omitempty"`
	DataBase64 string `json:"data_base64,omitempty"`
	PrintName  string `json:"print_name,omitempty"`
	Label      string `json:"label,omitempty"`
	KeyClass   uint32 `json:"key_class,omitempty"`
	KeyType    uint32 `json:"key_type,omitempty"`
	KeySize    uint32 `json:"key_size,omitempty"`
}

PrivateKey represents a private key record.

type UnlockOption

type UnlockOption func(*unlockConfig)

UnlockOption configures how to unlock the keychain.

func WithKey

func WithKey(hexKey string) UnlockOption

WithKey unlocks using a raw hex-encoded 24-byte master key.

func WithPassword

func WithPassword(password string) UnlockOption

WithPassword unlocks using the keychain password. The master key is derived via PBKDF2-HMAC-SHA1 with the salt from the keychain file. An empty password is treated as a valid unlock attempt, not as "no password provided".

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL