commit 29d9032e658254c625a5b8db838df2e072a23590
parent 9d30745d69593ffd1654b4ed6965e9a21b046a47
Author: Sean Enck <sean@ttypty.com>
Date: Mon, 12 Jan 2026 21:13:37 -0500
add a checksum field, include path in hashes
Diffstat:
4 files changed, 101 insertions(+), 50 deletions(-)
diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log
@@ -108,62 +108,75 @@ test6/multiline/password
test7/deeper/root/url
{
"test1/key1": {
+ "checksum": "0005c",
"modtime": "XXXX-XX-XX",
- "password": "521b9ccefbcd14d179e7a1bb877752870a6d620938b28a66a107eac6e6805b9d0989f45b5730508041aa5e710847d439ea74cd312c9355f1f2dae08d40e41d50"
+ "password": "57c8df271f1dfecb52f1e56d419bcf57b9cbf649a29bba042ccc8e1301acb0d930b22bedeb1373d8489b9f8eb1ccb310a877f382f62e597c9f5dbdd4a3ee2323"
},
"test4/multiline": {
+ "checksum": "000df",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3"
},
"test5/multiline": {
+ "checksum": "000cf",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859"
},
"test6/multiline": {
+ "checksum": "002cc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad",
+ "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad"
},
"test7/deeper/root": {
+ "checksum": "0007c",
"modtime": "XXXX-XX-XX",
- "url": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "url": "ea959087841400b15d443e852860b70d4350c7264b4a8a446021416868d193c992c7a864881da3c4cbaaed4f6c970a6aeeeee0a2bc39f8a1f6845d2d95e14786"
},
"test7/deeper/rooted": {
+ "checksum": "0033c",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "otp": "3d126ef25692664fd102e72f4ebaebef79c0cf6a8ea442af099dd47d0a89fab9c9b2cb9b77e605b1d00d91c666c1f7d651a9bb2e3a06406fdd1f6155861eae55"
+ "notes": "1abfb8edd6c73ae6b1eb9842863b95a30e2308e7a0fe0070745ea3871a106813e9a3cf04fa43b0d322f79c03da6851b18e2139f827399fe019fc13eaaa993815",
+ "otp": "d7d94959b640a2a11bac8cfde21cb8780943625403592b77a520585cdaf0469d2d0c7a834b70c053676f7298cb3d6f9b0d0a6dba4599dc2d53ec8508aedea801"
},
"test8/unset": {
+ "checksum": "002cc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403",
+ "password": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403"
},
"test9/key1/sub1": {
+ "checksum": "0004c",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "e1fe8b29f0ed07e6af8a6a1100ed5268a58185eaf167726599be55ef09c363adf4df3257f45a84557977fd6b45f0713d6f791f6bc1e0ebce046568256859445b"
},
"test9/key1/sub2": {
+ "checksum": "0007c",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "ef69a69c35f720f0f252d4ed94c65fa5217222689a1ced1b3c409eb0a47736ba9df2d8e78563a6b1b78fb89c36c8cdc74c0bb195e025131b2ef615dc45444784"
},
"test9/key2/sub1": {
+ "checksum": "000cf",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "27141582d19bb3d4e6ef34333895f2e193655ab069bb71331755caf310dac9610ca7027ec66faeceb3ac98b20bac5a77d580e95e6d8bd404b5d1ffb816192b9e"
}
}
{
"test4/multiline": {
+ "checksum": "000df",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3"
},
"test5/multiline": {
+ "checksum": "000cf",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859"
},
"test6/multiline": {
+ "checksum": "002cc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad",
+ "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad"
}
}
totp
@@ -189,52 +202,63 @@ algorithm: SHA1
period: 30
5ae472abqdekjqykoyxk7hvc2leklq5n
"test1/key1": {
+ "checksum": "0005c",
"modtime": "XXXX-XX-XX",
- "password": "521b9ccefbcd14d179e7a1bb877752870a6d620938b28a66a107eac6e6805b9d0989f45b5730508041aa5e710847d439ea74cd312c9355f1f2dae08d40e41d50"
+ "password": "57c8df271f1dfecb52f1e56d419bcf57b9cbf649a29bba042ccc8e1301acb0d930b22bedeb1373d8489b9f8eb1ccb310a877f382f62e597c9f5dbdd4a3ee2323"
}
"test10/key1": {
+ "checksum": "000bc",
"modtime": "XXXX-XX-XX",
- "otp": "b6c44d5d8a75071d8e8a39df231b0b98584d1d42982b5cf230e44f94d9c48e2983e78955a54b70c0acb0428d6db7205101e332f950ffb6b6d643aa37287c6aa5"
+ "otp": "f79f64504a1841638238141f19ec05f237d1b692c935dc605a1efc0f42e7fcfdb838ece01c644fe8ee9586faf6ff5cc5df2c7b300165df7af33e9e2e87322687"
}
"test4/multiline": {
+ "checksum": "000df",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3"
}
"test5/multiline": {
+ "checksum": "000cf",
"modtime": "XXXX-XX-XX",
- "notes": "fdb05182a34667e207ad36cf4688d046951a5877482da0b8dfaf1baa112369ede0fd29786e2cf771e0e00d968837d449ce7e373642a945d0675d888403178b77"
+ "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859"
}
"test6/multiline": {
+ "checksum": "02bcc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "otp": "b6c44d5d8a75071d8e8a39df231b0b98584d1d42982b5cf230e44f94d9c48e2983e78955a54b70c0acb0428d6db7205101e332f950ffb6b6d643aa37287c6aa5",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad",
+ "otp": "6d76012d8097b71107085ba0427460d1cec5bf5f18d93ab7001ec43a65b73def59078680999fc9b9476a923cfe34850b8dbe74292aa7b862e214f5f1070cf720",
+ "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad"
}
"test7/deeper/root": {
+ "checksum": "0007c",
"modtime": "XXXX-XX-XX",
- "url": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "url": "ea959087841400b15d443e852860b70d4350c7264b4a8a446021416868d193c992c7a864881da3c4cbaaed4f6c970a6aeeeee0a2bc39f8a1f6845d2d95e14786"
}
"test7/deeper/rooted": {
+ "checksum": "0033c",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "otp": "3d126ef25692664fd102e72f4ebaebef79c0cf6a8ea442af099dd47d0a89fab9c9b2cb9b77e605b1d00d91c666c1f7d651a9bb2e3a06406fdd1f6155861eae55"
+ "notes": "1abfb8edd6c73ae6b1eb9842863b95a30e2308e7a0fe0070745ea3871a106813e9a3cf04fa43b0d322f79c03da6851b18e2139f827399fe019fc13eaaa993815",
+ "otp": "d7d94959b640a2a11bac8cfde21cb8780943625403592b77a520585cdaf0469d2d0c7a834b70c053676f7298cb3d6f9b0d0a6dba4599dc2d53ec8508aedea801"
}
"test8/unset": {
+ "checksum": "002cc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "notes": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403",
+ "password": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403"
}
"test9/key1/sub1": {
+ "checksum": "0004c",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "e1fe8b29f0ed07e6af8a6a1100ed5268a58185eaf167726599be55ef09c363adf4df3257f45a84557977fd6b45f0713d6f791f6bc1e0ebce046568256859445b"
}
"test9/key1/sub2": {
+ "checksum": "0007c",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "ef69a69c35f720f0f252d4ed94c65fa5217222689a1ced1b3c409eb0a47736ba9df2d8e78563a6b1b78fb89c36c8cdc74c0bb195e025131b2ef615dc45444784"
}
"test9/key2/sub1": {
+ "checksum": "000cf",
"modtime": "XXXX-XX-XX",
- "password": "cbf67e6da88d43f048050e36d7010080536372397b6ed0a0446cd2640340bf47cb616ddbc5d35ec3500e295f875f806fc20120a2d5497b3e729e904091424633"
+ "password": "27141582d19bb3d4e6ef34333895f2e193655ab069bb71331755caf310dac9610ca7027ec66faeceb3ac98b20bac5a77d580e95e6d8bd404b5d1ffb816192b9e"
}
removing
delete entry? (y/N)
@@ -386,10 +410,11 @@ json
}
{
"test6/multiline": {
+ "checksum": "02bcc",
"modtime": "XXXX-XX-XX",
- "notes": "cbf",
- "otp": "b6c",
- "password": "cbf"
+ "notes": "5b8",
+ "otp": "6d7",
+ "password": "5b8"
}
}
clipboard
diff --git a/internal/kdbx/core.go b/internal/kdbx/core.go
@@ -21,9 +21,10 @@ var (
)
const (
- titleKey = "Title"
- pathSep = "/"
- modTimeKey = "ModTime"
+ checksumKey = "checksum"
+ titleKey = "Title"
+ pathSep = "/"
+ modTimeKey = "ModTime"
// OTPField is the totp storage attribute
OTPField = "otp"
// NotesField is the multiline notes key
diff --git a/internal/kdbx/query.go b/internal/kdbx/query.go
@@ -173,22 +173,25 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
}
jsonMode = m
}
- jsonHasher := func(string) string {
+ jsonHasher := func(string, string) string {
return ""
}
+ isChecksum := false
+ formatString := "%" + fmt.Sprintf("%d", len(AllowedFields)+1) + "s"
switch jsonMode {
case output.JSONModes.Raw:
- jsonHasher = func(val string) string {
+ jsonHasher = func(val, _ string) string {
return val
}
case output.JSONModes.Hash:
+ isChecksum = args.Values == JSONValue
hashLength, err := config.EnvJSONHashLength.Get()
if err != nil {
return nil, err
}
l := int(hashLength)
- jsonHasher = func(val string) string {
- data := fmt.Sprintf("%x", sha512.Sum512([]byte(val)))
+ jsonHasher = func(val, path string) string {
+ data := fmt.Sprintf("%x", sha512.Sum512([]byte(val+path)))
if hashLength > 0 && len(data) > l {
data = data[0:hashLength]
}
@@ -200,24 +203,41 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) {
entity := Entity{Path: item.path}
var err error
values := make(EntityValues)
+ var checksums []byte
for _, v := range item.backing.Values {
val := ""
+ raw := ""
key := v.Key
if args.Values != BlankValue {
if args.Values == JSONValue {
values["modtime"] = getValue(item.backing, modTimeKey)
}
val = v.Value.Content
+ raw = val
switch args.Values {
case JSONValue:
- val = jsonHasher(val)
+ val = jsonHasher(val, entity.Path)
}
}
if key == modTimeKey || key == titleKey {
continue
}
+ if isChecksum {
+ if r := jsonHasher(raw, ""); len(r) > 0 {
+ checksums = append(checksums, r[0])
+ }
+ }
values[strings.ToLower(key)] = val
}
+ if isChecksum {
+ var check string
+ if len(checksums) > 0 {
+ checksums = append(checksums, jsonHasher(entity.Path, "")[0])
+ slices.Sort(checksums)
+ check = strings.ReplaceAll(fmt.Sprintf(formatString, string(checksums)), " ", "0")
+ }
+ values[checksumKey] = check
+ }
entity.Values = values
if !yield(entity, err) {
return
diff --git a/internal/kdbx/query_test.go b/internal/kdbx/query_test.go
@@ -155,8 +155,9 @@ func TestValueModes(t *testing.T) {
if !compareEntity(q, kdbx.Entity{
Path: "test/test/abc",
Values: map[string]string{
- "notes": "9057ff1aa9509b2a0af624d687461d2bbeb07e2f37d953b1ce4a9dc921a7f19c45dc35d7c5363b373792add57d0d7dc41596e1c585d6ef7844cdf8ae87af443f",
- "password": "44276ba24db13df5568aa6db81e0190ab9d35d2168dce43dca61e628f5c666b1d8b091f1dda59c2359c86e7d393d59723a421d58496d279031e7f858c11d893e",
+ "checksum": "0049b",
+ "notes": "164f7d1c788400c54db852f5f1ef4629e4d0020a87e935dfd643dc4f765dfd201ce43b2b2ec23ff8f5b966ed15715f79d276d4ededf05691197096bb4247d665",
+ "password": "a3ea1c021135a8070c62a3a1080d9cd3385ebca45687636ba87c9abd1f5c2d68b17d68e72dc22461d0c8fc371573c568664e98fbfb832fcdda000318211b9538",
},
}) {
t.Errorf("invalid entity: %v", q)
@@ -169,8 +170,9 @@ func TestValueModes(t *testing.T) {
if !compareEntity(q, kdbx.Entity{
Path: "test/test/abc",
Values: map[string]string{
- "notes": "9057ff1aa9",
- "password": "44276ba24d",
+ "checksum": "0049b",
+ "notes": "164f7d1c78",
+ "password": "a3ea1c0211",
},
}) {
t.Errorf("invalid entity: %v", q)
@@ -273,8 +275,9 @@ func TestSetModTime(t *testing.T) {
if !compareEntity(q, kdbx.Entity{
Path: "test/xyz",
Values: map[string]string{
- "password": "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff",
+ "password": "f4d691c1399b47b1a17d64da4e91f27ee739d8e49eee11d3ca5185940353325cfd5892cd375dd6a82f0b9f6e52d0365b4ddc2510106d134a1c3e9283becf72c9",
"modtime": testDateTime,
+ "checksum": "000ef",
},
}) {
t.Errorf("invalid entity: %v", q)
@@ -323,7 +326,8 @@ func TestAttributeModes(t *testing.T) {
if !compareEntity(q, kdbx.Entity{
Path: "test/test/totp",
Values: map[string]string{
- "otp": "7f8fd0e1a714f63da75206748d0ea1dd601fc8f92498bc87c9579b403c3004a0eefdd7ead976f7dbd6e5143c9aa7a569e24322d870ec7745a4605a154557458e",
+ "checksum": "0007e",
+ "otp": "cb9c99a3ba9f3370238a302adf9d3f4fa7cf4a2e01fe0225a7f69563b7c8160bd773471481d28d2f6654a6c88b41c54ca5c9930740554578b59832bd8ac2ee66",
},
}) {
t.Errorf("invalid entity: %v", q)
@@ -336,7 +340,8 @@ func TestAttributeModes(t *testing.T) {
if !compareEntity(q, kdbx.Entity{
Path: "test/test/totp",
Values: map[string]string{
- "otp": "7f8fd0e1a7",
+ "checksum": "0007e",
+ "otp": "cb9c99a3ba",
},
}) {
t.Errorf("invalid entity: %v", q)