lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 1f0674f8d6a579cb474f6a8af65ffd03a9f39bf3
parent d7d15be67abe320bd324339173d50c902f4567c5
Author: Sean Enck <sean@ttypty.com>
Date:   Mon, 12 Jan 2026 22:13:31 -0500

no longer output all the fields and just produce the singular checksum field

Diffstat:
Mcmd/lb/tests/expected.log | 37+------------------------------------
Minternal/config/vars.go | 10+++++-----
Minternal/config/vars_test.go | 2+-
Minternal/kdbx/query.go | 42+++++++++++++++++++++---------------------
Minternal/kdbx/query_test.go | 12++++--------
5 files changed, 32 insertions(+), 71 deletions(-)

diff --git a/cmd/lb/tests/expected.log b/cmd/lb/tests/expected.log @@ -110,73 +110,56 @@ test7/deeper/root/url "test1/key1": { "checksum": "[00 00 00 00 cd 5p]", "modtime": "XXXX-XX-XX", - "password": "57c8df271f1dfecb52f1e56d419bcf57b9cbf649a29bba042ccc8e1301acb0d930b22bedeb1373d8489b9f8eb1ccb310a877f382f62e597c9f5dbdd4a3ee2323" }, "test4/multiline": { "checksum": "[00 00 00 00 dd fn]", "modtime": "XXXX-XX-XX", - "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3" }, "test5/multiline": { "checksum": "[00 00 00 00 cd fn]", "modtime": "XXXX-XX-XX", - "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859" }, "test6/multiline": { "checksum": "[00 00 00 2d cn cp]", "modtime": "XXXX-XX-XX", - "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad", - "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad" }, "test7/deeper/root": { "checksum": "[00 00 00 00 7d cu]", "modtime": "XXXX-XX-XX", - "url": "ea959087841400b15d443e852860b70d4350c7264b4a8a446021416868d193c992c7a864881da3c4cbaaed4f6c970a6aeeeee0a2bc39f8a1f6845d2d95e14786" }, "test7/deeper/rooted": { "checksum": "[00 00 00 3d cn 3o]", "modtime": "XXXX-XX-XX", - "notes": "1abfb8edd6c73ae6b1eb9842863b95a30e2308e7a0fe0070745ea3871a106813e9a3cf04fa43b0d322f79c03da6851b18e2139f827399fe019fc13eaaa993815", - "otp": "d7d94959b640a2a11bac8cfde21cb8780943625403592b77a520585cdaf0469d2d0c7a834b70c053676f7298cb3d6f9b0d0a6dba4599dc2d53ec8508aedea801" }, "test8/unset": { "checksum": "[00 00 00 2d cn cp]", "modtime": "XXXX-XX-XX", - "notes": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403", - "password": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403" }, "test9/key1/sub1": { "checksum": "[00 00 00 00 4d cp]", "modtime": "XXXX-XX-XX", - "password": "e1fe8b29f0ed07e6af8a6a1100ed5268a58185eaf167726599be55ef09c363adf4df3257f45a84557977fd6b45f0713d6f791f6bc1e0ebce046568256859445b" }, "test9/key1/sub2": { "checksum": "[00 00 00 00 7d cp]", "modtime": "XXXX-XX-XX", - "password": "ef69a69c35f720f0f252d4ed94c65fa5217222689a1ced1b3c409eb0a47736ba9df2d8e78563a6b1b78fb89c36c8cdc74c0bb195e025131b2ef615dc45444784" }, "test9/key2/sub1": { "checksum": "[00 00 00 00 fd cp]", "modtime": "XXXX-XX-XX", - "password": "27141582d19bb3d4e6ef34333895f2e193655ab069bb71331755caf310dac9610ca7027ec66faeceb3ac98b20bac5a77d580e95e6d8bd404b5d1ffb816192b9e" } } { "test4/multiline": { "checksum": "[00 00 00 00 dd fn]", "modtime": "XXXX-XX-XX", - "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3" }, "test5/multiline": { "checksum": "[00 00 00 00 cd fn]", "modtime": "XXXX-XX-XX", - "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859" }, "test6/multiline": { "checksum": "[00 00 00 2d cn cp]", "modtime": "XXXX-XX-XX", - "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad", - "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad" } } totp @@ -204,61 +187,46 @@ period: 30 "test1/key1": { "checksum": "[00 00 00 00 cd 5p]", "modtime": "XXXX-XX-XX", - "password": "57c8df271f1dfecb52f1e56d419bcf57b9cbf649a29bba042ccc8e1301acb0d930b22bedeb1373d8489b9f8eb1ccb310a877f382f62e597c9f5dbdd4a3ee2323" } "test10/key1": { "checksum": "[00 00 00 00 cd bo]", "modtime": "XXXX-XX-XX", - "otp": "f79f64504a1841638238141f19ec05f237d1b692c935dc605a1efc0f42e7fcfdb838ece01c644fe8ee9586faf6ff5cc5df2c7b300165df7af33e9e2e87322687" } "test4/multiline": { "checksum": "[00 00 00 00 dd fn]", "modtime": "XXXX-XX-XX", - "notes": "08ecffb3ead6b577fbbacef5b1b87c240588208eee82904acc8e300fc10c504758878e9a2202a6e161d7d4925983117ee00293434bddb592656fcf01bca69ad3" } "test5/multiline": { "checksum": "[00 00 00 00 cd fn]", "modtime": "XXXX-XX-XX", - "notes": "6be4f7e6c6761aa37ad0da252f7647e0d5401608dc193e0cf6c9cf16331600ca96df65cc5bdb2a085e79fb0818e98d1aabe8ae4e129ae369115f67f56ef9a859" } "test6/multiline": { "checksum": "[00 00 2d cn bo cp]", "modtime": "XXXX-XX-XX", - "notes": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad", - "otp": "6d76012d8097b71107085ba0427460d1cec5bf5f18d93ab7001ec43a65b73def59078680999fc9b9476a923cfe34850b8dbe74292aa7b862e214f5f1070cf720", - "password": "5b88331608e33b74c5fd5d22e9053a65f5cace6a3c046d7fbc9aaea1340bd1de90b6198a99af93d02df3c4715febb1daac5babb596867137b2a65ff91fd799ad" } "test7/deeper/root": { "checksum": "[00 00 00 00 7d cu]", "modtime": "XXXX-XX-XX", - "url": "ea959087841400b15d443e852860b70d4350c7264b4a8a446021416868d193c992c7a864881da3c4cbaaed4f6c970a6aeeeee0a2bc39f8a1f6845d2d95e14786" } "test7/deeper/rooted": { "checksum": "[00 00 00 3d cn 3o]", "modtime": "XXXX-XX-XX", - "notes": "1abfb8edd6c73ae6b1eb9842863b95a30e2308e7a0fe0070745ea3871a106813e9a3cf04fa43b0d322f79c03da6851b18e2139f827399fe019fc13eaaa993815", - "otp": "d7d94959b640a2a11bac8cfde21cb8780943625403592b77a520585cdaf0469d2d0c7a834b70c053676f7298cb3d6f9b0d0a6dba4599dc2d53ec8508aedea801" } "test8/unset": { "checksum": "[00 00 00 2d cn cp]", "modtime": "XXXX-XX-XX", - "notes": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403", - "password": "0956b6450f907573260ad631985167421abbc38d2d69e690f87a370a0e4755a388b6e20e8ec15cfa8ce8d3c2a11117bbf345492ff9446f5781c10cfd54d68403" } "test9/key1/sub1": { "checksum": "[00 00 00 00 4d cp]", "modtime": "XXXX-XX-XX", - "password": "e1fe8b29f0ed07e6af8a6a1100ed5268a58185eaf167726599be55ef09c363adf4df3257f45a84557977fd6b45f0713d6f791f6bc1e0ebce046568256859445b" } "test9/key1/sub2": { "checksum": "[00 00 00 00 7d cp]", "modtime": "XXXX-XX-XX", - "password": "ef69a69c35f720f0f252d4ed94c65fa5217222689a1ced1b3c409eb0a47736ba9df2d8e78563a6b1b78fb89c36c8cdc74c0bb195e025131b2ef615dc45444784" } "test9/key2/sub1": { "checksum": "[00 00 00 00 fd cp]", "modtime": "XXXX-XX-XX", - "password": "27141582d19bb3d4e6ef34333895f2e193655ab069bb71331755caf310dac9610ca7027ec66faeceb3ac98b20bac5a77d580e95e6d8bd404b5d1ffb816192b9e" } removing delete entry? (y/N) @@ -410,11 +378,8 @@ json } { "test6/multiline": { - "checksum": "[00 00 2d cn bo cp]", + "checksum": "[0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 23cd cbfn b6co cbfp]", "modtime": "XXXX-XX-XX", - "notes": "5b8", - "otp": "6d7", - "password": "5b8" } } clipboard diff --git a/internal/config/vars.go b/internal/config/vars.go @@ -35,13 +35,13 @@ var ( }) // EnvJSONHashLength handles the hashing output length EnvJSONHashLength = environmentRegister(EnvironmentInt{ - environmentDefault: newDefaultedEnvironment(0, + environmentDefault: newDefaultedEnvironment(1, environmentBase{ - key: jsonCategory + "HASH_LENGTH", - description: fmt.Sprintf("Maximum string length of the JSON value when '%s' mode is set for JSON output.", output.JSONModes.Hash), + key: jsonCategory + "HASH_LENGTH", + description: fmt.Sprintf(`Maximum string length of the JSON checksum value when JSON '%s' mode is set. +This value is appended to the single character type field`, output.JSONModes.Hash), }), - short: "hash length", - canZero: true, + short: "checksum value length", }) // EnvReadOnly indicates if in read-only mode EnvReadOnly = environmentRegister(EnvironmentBool{ diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go @@ -66,7 +66,7 @@ func TestFormatTOTP(t *testing.T) { } func TestHashLength(t *testing.T) { - checkInt(config.EnvJSONHashLength, "LOCKBOX_JSON_HASH_LENGTH", "hash length", 0, true, t) + checkInt(config.EnvJSONHashLength, "LOCKBOX_JSON_HASH_LENGTH", "checksum value length", 1, false, t) } func TestMaxTOTP(t *testing.T) { diff --git a/internal/kdbx/query.go b/internal/kdbx/query.go @@ -173,33 +173,30 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) { } jsonMode = m } - jsonHasher := func(string, string) string { + jsonHasher := func(string) string { return "" } isChecksum := false - requiredChecksum := len(AllowedFields) + 2 + to := 1 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() + length, err := config.EnvJSONHashLength.Get() if err != nil { return nil, err } - l := int(hashLength) - 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] - } - return data + to = max(int(length), to) + isChecksum = args.Values == JSONValue + jsonHasher = func(val string) string { + return fmt.Sprintf("%x", sha512.Sum512([]byte(val))) } } + requiredChecksum := (len(AllowedFields) + 2) * to type checksummable struct { - value byte + value string typeof byte } return func(yield func(Entity, error) bool) { @@ -218,9 +215,11 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) { } val = v.Value.Content raw = val - switch args.Values { - case JSONValue: - val = jsonHasher(val, entity.Path) + if !isChecksum { + switch args.Values { + case JSONValue: + val = jsonHasher(val) + } } } if key == modTimeKey || key == titleKey { @@ -228,25 +227,26 @@ func (t *Transaction) QueryCallback(args QueryOptions) (QuerySeq2, error) { } field := strings.ToLower(key) if isChecksum { - if r := jsonHasher(raw, ""); len(r) > 0 { - checksums = append(checksums, checksummable{r[0], field[0]}) + if r := jsonHasher(raw); len(r) > 0 { + checksums = append(checksums, checksummable{r[0:to], field[0]}) } + continue } values[field] = val } if isChecksum { var check string if len(checksums) > 0 { - checksums = append(checksums, checksummable{jsonHasher(entity.Path, "")[0], byte('d')}) + checksums = append(checksums, checksummable{jsonHasher(entity.Path)[0:to], byte('d')}) slices.SortFunc(checksums, func(x, y checksummable) int { return int(x.typeof) - int(y.typeof) }) var vals []string for _, item := range checksums { - vals = append(vals, fmt.Sprintf("%s%s", string(item.value), string(item.typeof))) + vals = append(vals, fmt.Sprintf("%s%s", item.value, string(item.typeof))) } for len(vals) < requiredChecksum { - vals = append([]string{"00"}, vals...) + vals = append([]string{"0" + strings.Repeat("0", to)}, vals...) } check = fmt.Sprintf("[%s]", strings.Join(vals, " ")) } diff --git a/internal/kdbx/query_test.go b/internal/kdbx/query_test.go @@ -156,13 +156,11 @@ func TestValueModes(t *testing.T) { Path: "test/test/abc", Values: map[string]string{ "checksum": "[00 00 00 bd 9n 4p]", - "notes": "164f7d1c788400c54db852f5f1ef4629e4d0020a87e935dfd643dc4f765dfd201ce43b2b2ec23ff8f5b966ed15715f79d276d4ededf05691197096bb4247d665", - "password": "a3ea1c021135a8070c62a3a1080d9cd3385ebca45687636ba87c9abd1f5c2d68b17d68e72dc22461d0c8fc371573c568664e98fbfb832fcdda000318211b9538", }, }) { t.Errorf("invalid entity: %v", q) } - store.SetInt64("LOCKBOX_JSON_HASH_LENGTH", 10) + store.SetInt64("LOCKBOX_JSON_HASH_LENGTH", 5) q, err = fullSetup(t, true).Get("test/test/abc", kdbx.JSONValue) if err != nil { t.Errorf("no error: %v", err) @@ -170,9 +168,7 @@ func TestValueModes(t *testing.T) { if !compareEntity(q, kdbx.Entity{ Path: "test/test/abc", Values: map[string]string{ - "checksum": "[00 00 00 bd 9n 4p]", - "notes": "164f7d1c78", - "password": "a3ea1c0211", + "checksum": "[000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 b00d0d 9057fn 44276p]", }, }) { t.Errorf("invalid entity: %v", q) @@ -332,7 +328,7 @@ func TestAttributeModes(t *testing.T) { }) { t.Errorf("invalid entity: %v", q) } - store.SetInt64("LOCKBOX_JSON_HASH_LENGTH", 10) + store.SetInt64("LOCKBOX_JSON_HASH_LENGTH", 4) q, err = fullSetup(t, true).Get("test/test/totp", kdbx.JSONValue) if err != nil { t.Errorf("no error: %v", err) @@ -340,7 +336,7 @@ func TestAttributeModes(t *testing.T) { if !compareEntity(q, kdbx.Entity{ Path: "test/test/totp", Values: map[string]string{ - "checksum": "[00 00 00 00 ed 7o]", + "checksum": "[00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 ef10d 7f8fo]", "otp": "cb9c99a3ba", }, }) {