Skip to content

Instantly share code, notes, and snippets.

@daaku
Created May 20, 2026 06:49
Show Gist options
  • Select an option

  • Save daaku/00f5f10e6c7282a0577b8484e857c07a to your computer and use it in GitHub Desktop.

Select an option

Save daaku/00f5f10e6c7282a0577b8484e857c07a to your computer and use it in GitHub Desktop.
Fix BitWarden Passkeys imported into KeePassXC for compatibility with KeePassium
package main
import (
"encoding/pem"
"fmt"
"os"
"strings"
"syscall"
"github.com/daaku/serr"
"github.com/tobischo/gokeepasslib/v3"
"golang.org/x/term"
)
func fixPEM(input string) string {
// If it already decodes cleanly, return as-is
if block, _ := pem.Decode([]byte(input)); block != nil {
return input
}
// Extract header, footer, and body
headerEnd := strings.Index(input[5:], "-----") + 5 + 5
header := input[:headerEnd]
footerStart := strings.LastIndex(input, "-----END")
footer := input[footerStart:]
body := input[headerEnd:footerStart]
// Split body into 64-character chunks
var chunks []string
for len(body) > 0 {
chunkSize := min(len(body), 64)
chunks = append(chunks, body[:chunkSize])
body = body[chunkSize:]
}
// Reassemble with proper newlines
var sb strings.Builder
sb.WriteString(header)
sb.WriteString("\n")
for _, chunk := range chunks {
sb.WriteString(chunk)
sb.WriteString("\n")
}
sb.WriteString(footer)
sb.WriteString("\n")
return sb.String()
}
func updateEntries(entries []gokeepasslib.Entry) error {
for _, e := range entries {
pem := e.Get("KPEX_PASSKEY_PRIVATE_KEY_PEM")
if pem != nil {
fixed := fixPEM(pem.Value.Content)
if fixed == pem.Value.Content {
fmt.Println("OK:", e.GetTitle())
} else {
pem.Value.Content = fixed
fmt.Println("FIXED:", e.GetTitle())
}
}
}
return nil
}
func updateGroups(groups []gokeepasslib.Group) error {
for _, g := range groups {
if err := updateEntries(g.Entries); err != nil {
return err
}
if err := updateGroups(g.Groups); err != nil {
return err
}
}
return nil
}
func run() error {
file, err := os.Open(os.Args[1])
if err != nil {
return serr.Wrap(err)
}
fmt.Print("Enter password: ")
password, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return serr.Wrap(err)
}
fmt.Println() // move to next line after prompt
db := gokeepasslib.NewDatabase()
db.Credentials = gokeepasslib.NewPasswordCredentials(string(password))
if err := gokeepasslib.NewDecoder(file).Decode(db); err != nil {
return serr.Wrap(err)
}
if err := db.UnlockProtectedEntries(); err != nil {
return serr.Wrap(err)
}
if err := updateGroups(db.Content.Root.Groups); err != nil {
return err
}
if err := db.LockProtectedEntries(); err != nil {
return serr.Wrap(err)
}
writeFile, err := os.Create(os.Args[2])
if err != nil {
return serr.Wrap(err)
}
keepassEncoder := gokeepasslib.NewEncoder(writeFile)
if err := keepassEncoder.Encode(db); err != nil {
return serr.Wrap(err)
}
if err := writeFile.Close(); err != nil {
return serr.Wrap(err)
}
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment