Compare commits
5 Commits
41a75bd299
...
31549c6a94
Author | SHA1 | Date | |
---|---|---|---|
31549c6a94 | |||
52ac92c3e0 | |||
011c293cb8 | |||
647627629a | |||
a0c5a497c7 |
7
go.mod
7
go.mod
@ -3,14 +3,13 @@ module unlock-music.dev/mmkv
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/stretchr/testify v1.8.1
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||||
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb
|
google.golang.org/protobuf v1.33.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
25
go.sum
25
go.sum
@ -1,27 +1,14 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb h1:QIsP/NmClBICkqnJ4rSIhnrGiGR7Yv9ZORGGnmmLTPk=
|
|
||||||
golang.org/x/exp v0.0.0-20221204150635-6dcec336b2bb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -5,6 +5,7 @@ type Manager interface {
|
|||||||
// If the vault does not exist, it will be created.
|
// If the vault does not exist, it will be created.
|
||||||
// If id is empty, DefaultVaultID will be used.
|
// If id is empty, DefaultVaultID will be used.
|
||||||
OpenVault(id string) (Vault, error)
|
OpenVault(id string) (Vault, error)
|
||||||
|
OpenVaultCrypto(id string, cryptoKey string) (Vault, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Vault interface {
|
type Vault interface {
|
||||||
|
50
internal/protobuffer.go
Normal file
50
internal/protobuffer.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
// extracted from: https://github.com/golang/protobuf/blob/v1.5.4/proto/buffer.go
|
||||||
|
|
||||||
|
import "google.golang.org/protobuf/encoding/protowire"
|
||||||
|
|
||||||
|
// ProtoBuffer is a buffer for encoding and decoding the protobuf wire format.
|
||||||
|
// It may be reused between invocations to reduce memory usage.
|
||||||
|
type ProtoBuffer struct {
|
||||||
|
buf []byte
|
||||||
|
idx int
|
||||||
|
deterministic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProtoBuffer allocates a new ProtoBuffer initialized with buf,
|
||||||
|
// where the contents of buf are considered the unread portion of the buffer.
|
||||||
|
func NewProtoBuffer(buf []byte) *ProtoBuffer {
|
||||||
|
return &ProtoBuffer{buf: buf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// It does not validate whether the raw bytes contain valid UTF-8.
|
||||||
|
func (b *ProtoBuffer) DecodeStringBytes() (string, error) {
|
||||||
|
v, n := protowire.ConsumeString(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return "", protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRawBytes consumes a length-prefixed raw bytes from the buffer.
|
||||||
|
// If alloc is specified, it returns a copy the raw bytes
|
||||||
|
// rather than a sub-slice of the buffer.
|
||||||
|
func (b *ProtoBuffer) DecodeRawBytes(alloc bool) ([]byte, error) {
|
||||||
|
v, n := protowire.ConsumeBytes(b.buf[b.idx:])
|
||||||
|
if n < 0 {
|
||||||
|
return nil, protowire.ParseError(n)
|
||||||
|
}
|
||||||
|
b.idx += n
|
||||||
|
if alloc {
|
||||||
|
v = append([]byte(nil), v...)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unread returns the unread portion of the buffer.
|
||||||
|
func (b *ProtoBuffer) Unread() []byte {
|
||||||
|
return b.buf[b.idx:]
|
||||||
|
}
|
24
manager.go
24
manager.go
@ -42,7 +42,7 @@ func (m *manager) OpenVault(id string) (Vault, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vault, err := m.openVault(id)
|
vault, err := m.openVault(id, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open vault: %w", err)
|
return nil, fmt.Errorf("failed to open vault: %w", err)
|
||||||
}
|
}
|
||||||
@ -51,7 +51,25 @@ func (m *manager) OpenVault(id string) (Vault, error) {
|
|||||||
return vault, nil
|
return vault, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manager) openVault(id string) (Vault, error) {
|
func (m *manager) OpenVaultCrypto(id string, cryptoKey string) (Vault, error) {
|
||||||
|
if id == "" {
|
||||||
|
id = DefaultVaultID
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := m.vaults[id]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vault, err := m.openVault(id, cryptoKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open vault: %w", err)
|
||||||
|
}
|
||||||
|
m.vaults[id] = vault
|
||||||
|
|
||||||
|
return vault, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) openVault(id string, cryptoKey string) (Vault, error) {
|
||||||
metaFile, err := os.Open(path.Join(m.dir, id+".crc"))
|
metaFile, err := os.Open(path.Join(m.dir, id+".crc"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open metadata file: %w", err)
|
return nil, fmt.Errorf("failed to open metadata file: %w", err)
|
||||||
@ -69,7 +87,7 @@ func (m *manager) openVault(id string) (Vault, error) {
|
|||||||
return nil, fmt.Errorf("failed to load metadata: %w", err)
|
return nil, fmt.Errorf("failed to load metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := loadVault(vaultFile, meta)
|
v, err := loadVault(vaultFile, meta, cryptoKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load vault: %w", err)
|
return nil, fmt.Errorf("failed to load vault: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNewManager(t *testing.T) {
|
func TestNewManager(t *testing.T) {
|
||||||
mgr, err := NewManager("./testdata")
|
t.Run("Default", func(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
mgr, err := NewManager("./testdata")
|
||||||
assert.NotNil(t, mgr)
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, mgr)
|
||||||
|
|
||||||
vault, err := mgr.OpenVault("")
|
vault, err := mgr.OpenVault("")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, vault)
|
assert.NotNil(t, vault)
|
||||||
|
})
|
||||||
|
t.Run("Crypto", func(t *testing.T) {
|
||||||
|
mgr, err := NewManager("./testdata")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, mgr)
|
||||||
|
|
||||||
|
vault, err := mgr.OpenVaultCrypto("crypto", "123456")
|
||||||
|
val, err := vault.GetString("world")
|
||||||
|
assert.NotNil(t, vault)
|
||||||
|
|
||||||
|
assert.Equal(t, "hello", val)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = vault.GetBytes("foo")
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
BIN
testdata/crypto
vendored
Normal file
BIN
testdata/crypto
vendored
Normal file
Binary file not shown.
BIN
testdata/crypto.crc
vendored
Normal file
BIN
testdata/crypto.crc
vendored
Normal file
Binary file not shown.
23
vault.go
23
vault.go
@ -1,14 +1,16 @@
|
|||||||
package mmkv
|
package mmkv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"google.golang.org/protobuf/encoding/protowire"
|
"google.golang.org/protobuf/encoding/protowire"
|
||||||
|
"unlock-music.dev/mmkv/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type vault map[string][]byte
|
type vault map[string][]byte
|
||||||
@ -44,8 +46,8 @@ func (v vault) GetString(key string) (string, error) {
|
|||||||
return string(val), nil
|
return string(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// metadata is optional. but if it exists, validate with it.
|
// metadata and cryptoKey are optional. but if it exists, validate with them.
|
||||||
func loadVault(src io.Reader, m *metadata) (Vault, error) {
|
func loadVault(src io.Reader, m *metadata, cryptoKey string) (Vault, error) {
|
||||||
fileSizeBuf := make([]byte, 4)
|
fileSizeBuf := make([]byte, 4)
|
||||||
_, err := io.ReadFull(src, fileSizeBuf)
|
_, err := io.ReadFull(src, fileSizeBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,12 +69,25 @@ func loadVault(src io.Reader, m *metadata) (Vault, error) {
|
|||||||
return nil, fmt.Errorf("metadata and vault payload crc32 mismatch")
|
return nil, fmt.Errorf("metadata and vault payload crc32 mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将数据库完整解密
|
||||||
|
if len(cryptoKey) > 0 {
|
||||||
|
m_key := make([]byte, aes.BlockSize) // 16 bytes key
|
||||||
|
copy(m_key, cryptoKey)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(m_key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create aes cipher")
|
||||||
|
}
|
||||||
|
stream := cipher.NewCFBDecrypter(block, m.aesVector)
|
||||||
|
stream.XORKeyStream(buf, buf)
|
||||||
|
}
|
||||||
|
|
||||||
v := make(vault)
|
v := make(vault)
|
||||||
|
|
||||||
// mmkv is not really protobuf compatible,
|
// mmkv is not really protobuf compatible,
|
||||||
// type of key & value (the first 4 bytes) is incorrect.
|
// type of key & value (the first 4 bytes) is incorrect.
|
||||||
// so skip the first 4 bytes & manually parse the rest.
|
// so skip the first 4 bytes & manually parse the rest.
|
||||||
rd := proto.NewBuffer(buf[4:])
|
rd := internal.NewProtoBuffer(buf[4:])
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if len(rd.Unread()) == 0 {
|
if len(rd.Unread()) == 0 {
|
||||||
|
@ -12,7 +12,7 @@ func Test_loadVault(t *testing.T) {
|
|||||||
file, err := os.Open("./testdata/mmkv.default")
|
file, err := os.Open("./testdata/mmkv.default")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
v, err := loadVault(file, nil)
|
v, err := loadVault(file, nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, 2, len(v.Keys()))
|
assert.Equal(t, 2, len(v.Keys()))
|
||||||
|
Loading…
Reference in New Issue
Block a user