keepassxc
190 строк · 4.0 Кб
1package main2
3import (4"bytes"5"crypto/hmac"6"crypto/sha1"7"fmt"8"io"9"io/ioutil"10"log"11"os"12"syscall"13
14"encoding/binary"15"encoding/hex"16
17"golang.org/x/crypto/ssh/terminal"18)
19
20const fileVersionCriticalMask uint32 = 0xFFFF000021const argon2Salt = "S"22const endOfHeader = 023const endOfVariantMap = 024const kdfParameters = 1125
26func readSecret() (string, error) {27fmt.Print("Secret: ")28byteSecret, err := terminal.ReadPassword(int(syscall.Stdin))29fmt.Println()30secret := string(byteSecret)31return secret, err32
33}
34func readHeaderField(reader io.Reader) (bool, byte, []byte, error) {35var fieldID byte36err := binary.Read(reader, binary.LittleEndian, &fieldID)37if err != nil {38return true, 0, nil, err39}40
41if fieldID == endOfHeader {42return false, 0, nil, nil43}44
45var fieldLength uint3246err = binary.Read(reader, binary.LittleEndian, &fieldLength)47if err != nil {48return true, fieldID, nil, err49}50
51fieldData := make([]byte, fieldLength)52err = binary.Read(reader, binary.LittleEndian, &fieldData)53if err != nil {54return true, fieldID, fieldData, err55}56return true, fieldID, fieldData, nil57}
58func readVariantMap(reader io.Reader) ([]byte, error) {59var version uint1660err := binary.Read(reader, binary.LittleEndian, &version)61if err != nil {62return nil, err63}64
65var fieldType byte66for err = binary.Read(reader, binary.LittleEndian, &fieldType); fieldType != endOfVariantMap && err == nil; err = binary.Read(reader, binary.LittleEndian, &fieldType) {67
68var nameLen uint3269err = binary.Read(reader, binary.LittleEndian, &nameLen)70if err != nil {71return nil, err72}73
74nameBytes := make([]byte, nameLen)75err = binary.Read(reader, binary.LittleEndian, &nameBytes)76if err != nil {77return nil, err78}79
80name := string(nameBytes)81
82var valueLen uint3283err = binary.Read(reader, binary.LittleEndian, &valueLen)84if err != nil {85return nil, err86}87
88value := make([]byte, valueLen)89err = binary.Read(reader, binary.LittleEndian, &value)90if err != nil {91return nil, err92}93
94if name == argon2Salt {95return value, nil96}97}98return nil, nil99}
100func readKeepassHeader(keepassFilename string) ([]byte, error) {101dbFile, err := os.Open(keepassFilename)102defer dbFile.Close()103if err != nil {104return nil, err105}106
107var sig1, sig2, version uint32108err = binary.Read(dbFile, binary.LittleEndian, &sig1)109if err != nil {110return nil, err111}112
113err = binary.Read(dbFile, binary.LittleEndian, &sig2)114if err != nil {115return nil, err116}117
118err = binary.Read(dbFile, binary.LittleEndian, &version)119if err != nil {120return nil, err121}122
123version &= fileVersionCriticalMask124
125var fieldData []byte126var fieldID byte127var moreFields bool128
129for moreFields, fieldID, fieldData, err = readHeaderField(dbFile); moreFields && err == nil && fieldID != kdfParameters; moreFields, fieldID, fieldData, err = readHeaderField(dbFile) {130}131if err != nil {132return nil, err133}134
135fieldReader := bytes.NewReader(fieldData)136seed, err := readVariantMap(fieldReader)137if err != nil {138return nil, err139}140return seed, nil141
142}
143func main() {144log.SetFlags(0)145args := os.Args146
147if len(args) != 3 {148log.Fatalf("usage: %s keepassxc-database keyfile", args[0])149}150
151dbFilename := args[1]152keyFilename := args[2]153
154if _, err := os.Stat(keyFilename); err == nil {155log.Fatalf("keyfile already exists, exiting")156}157secretHex, err := readSecret()158if err != nil {159log.Fatalf("couldn't read secret from stdin: %s", err)160}161secret, err := hex.DecodeString(secretHex)162
163if err != nil {164log.Fatalf("couldn't decode secret: %s", err)165}166
167challenge, err := readKeepassHeader(dbFilename)168if err != nil {169log.Fatalf("couldn't read challenge: %s", err)170}171
172if len(challenge) < 64 {173padd := make([]byte, 64-len(challenge))174for i, _ := range padd {175padd[i] = byte(64-len(challenge))176}177challenge = append(challenge[:], padd[:]...)178}179
180mac := hmac.New(sha1.New, secret)181mac.Write(challenge)182
183hash := mac.Sum(nil)184
185err = ioutil.WriteFile(keyFilename, hash, 0644)186if err != nil {187log.Fatalf("couldn't write keyfile: %s", err)188}189
190}
191