Compare commits
4 Commits
name_gener
...
password/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be5aa063df | ||
|
|
3ca4745bac | ||
|
|
83c2ec5048 | ||
|
|
f48c7373d7 |
@@ -94,6 +94,7 @@ func TestNewXID(t *testing.T) {
|
|||||||
// 测试生成的 XID 是否非空
|
// 测试生成的 XID 是否非空
|
||||||
id := NewXID()
|
id := NewXID()
|
||||||
assert.NotEmpty(t, id, "生成的 XID 应该非空")
|
assert.NotEmpty(t, id, "生成的 XID 应该非空")
|
||||||
|
t.Logf("xid: %s", id)
|
||||||
|
|
||||||
// 测试生成的 XID 的长度是否符合预期
|
// 测试生成的 XID 的长度是否符合预期
|
||||||
assert.Equal(t, 20, len(id), "生成的 XID 长度应该为 20")
|
assert.Equal(t, 20, len(id), "生成的 XID 长度应该为 20")
|
||||||
|
|||||||
95
mapper/README.md
Normal file
95
mapper/README.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# 类型映射器
|
||||||
|
|
||||||
|
类型映射器的作用是将一个数据结构的字段映射到另一个数据结构的字段,通常用于对象之间的数据转换。它可以简化不同类型或结构之间的数据传递,减少手动赋值的代码量,提高开发效率。例如,在处理数据库实体与业务模型或 API 请求/响应模型之间的转换时,类型映射器非常有用。
|
||||||
|
|
||||||
|
## 基于Copier的类型映射器
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tx7do/go-utils/mapper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type DtoType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper := mapper.NewCopierMapper[DtoType, ModelType]()
|
||||||
|
|
||||||
|
// 测试 ToModel 方法
|
||||||
|
dto := &DtoType{Name: "Alice", Age: 25}
|
||||||
|
model := mapper.ToModel(dto)
|
||||||
|
|
||||||
|
// 测试 ToModel 方法,传入 nil
|
||||||
|
modelNil := mapper.ToModel(nil)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法
|
||||||
|
model = &ModelType{Name: "Bob", Age: 30}
|
||||||
|
dtoResult := mapper.ToDto(model)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法,传入 nil
|
||||||
|
dtoNil := mapper.ToDto(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## ent 与 protobuf 的枚举类型映射器
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/tx7do/go-utils/mapper"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
type DtoType string
|
||||||
|
type ModelType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DtoTypeOne DtoType = "One"
|
||||||
|
DtoTypeTwo DtoType = "Two"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModelTypeOne ModelType = 1
|
||||||
|
ModelTypeTwo ModelType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
nameMap := map[int32]string{
|
||||||
|
1: "One",
|
||||||
|
2: "Two",
|
||||||
|
}
|
||||||
|
valueMap := map[string]int32{
|
||||||
|
"One": 1,
|
||||||
|
"Two": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := mapper.NewEnumTypeConverter[DtoType, ModelType](nameMap, valueMap)
|
||||||
|
|
||||||
|
// 测试 ToModel 方法
|
||||||
|
dto := DtoTypeOne
|
||||||
|
model := converter.ToModel(&dto)
|
||||||
|
|
||||||
|
// 测试 ToModel 方法,传入不存在的值
|
||||||
|
dtoInvalid := DtoType("Three")
|
||||||
|
modelInvalid := converter.ToModel(&dtoInvalid)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法
|
||||||
|
tmpModelTwo := ModelTypeTwo
|
||||||
|
model = &tmpModelTwo
|
||||||
|
dtoResult := converter.ToDto(model)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法,传入不存在的值
|
||||||
|
tmpModelThree := ModelType(3)
|
||||||
|
modelInvalid = &tmpModelThree
|
||||||
|
dtoInvalidResult := converter.ToDto(modelInvalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
74
mapper/enum_converter.go
Normal file
74
mapper/enum_converter.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EnumTypeConverter[DTO ~string, MODEL ~int32] struct {
|
||||||
|
nameMap map[int32]string
|
||||||
|
valueMap map[string]int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEnumTypeConverter[DTO ~string, MODEL ~int32](nameMap map[int32]string, valueMap map[string]int32) *EnumTypeConverter[DTO, MODEL] {
|
||||||
|
return &EnumTypeConverter[DTO, MODEL]{
|
||||||
|
valueMap: valueMap,
|
||||||
|
nameMap: nameMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, MODEL]) ToModel(dto *DTO) *MODEL {
|
||||||
|
if dto == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
find, ok := m.valueMap[string(*dto)]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := MODEL(find)
|
||||||
|
return &model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, MODEL]) ToDto(model *MODEL) *DTO {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
find, ok := m.nameMap[int32(*model)]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := DTO(find)
|
||||||
|
return &dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EnumTypeConverter[DTO, MODEL]) NewConverterPair() []copier.TypeConverter {
|
||||||
|
srcType := MODEL(0)
|
||||||
|
dstType := DTO("")
|
||||||
|
|
||||||
|
fromFn := m.ToDto
|
||||||
|
toFn := m.ToModel
|
||||||
|
|
||||||
|
return NewGenericTypeConverterPair(&srcType, &dstType, fromFn, toFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenericTypeConverterPair[A interface{}, B interface{}](srcType A, dstType B, fromFn func(src A) B, toFn func(src B) A) []copier.TypeConverter {
|
||||||
|
return []copier.TypeConverter{
|
||||||
|
{
|
||||||
|
SrcType: srcType,
|
||||||
|
DstType: dstType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return fromFn(src.(A)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SrcType: dstType,
|
||||||
|
DstType: srcType,
|
||||||
|
Fn: func(src interface{}) (interface{}, error) {
|
||||||
|
return toFn(src.(B)), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
20
mapper/go.mod
Normal file
20
mapper/go.mod
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module github.com/tx7do/go-utils/mapper
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.3
|
||||||
|
|
||||||
|
require github.com/jinzhu/copier v0.4.0
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.10.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
25
mapper/go.sum
Normal file
25
mapper/go.sum
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
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/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
10
mapper/interface.go
Normal file
10
mapper/interface.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
// Mapper defines the interface for converting between DTOs and models.
|
||||||
|
type Mapper[DTO any, MODEL any] interface {
|
||||||
|
// ToModel converts a DTO to a MODEL.
|
||||||
|
ToModel(*DTO) *MODEL
|
||||||
|
|
||||||
|
// ToDto converts a MODEL to a DTO.
|
||||||
|
ToDto(*MODEL) *DTO
|
||||||
|
}
|
||||||
51
mapper/mapper.go
Normal file
51
mapper/mapper.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CopierMapper[DTO any, MODEL any] struct {
|
||||||
|
copierOption copier.Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCopierMapper[DTO any, MODEL any]() *CopierMapper[DTO, MODEL] {
|
||||||
|
return &CopierMapper[DTO, MODEL]{
|
||||||
|
copierOption: copier.Option{
|
||||||
|
Converters: []copier.TypeConverter{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, MODEL]) AppendConverter(converter copier.TypeConverter) {
|
||||||
|
m.copierOption.Converters = append(m.copierOption.Converters, converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, MODEL]) AppendConverters(converters []copier.TypeConverter) {
|
||||||
|
m.copierOption.Converters = append(m.copierOption.Converters, converters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, MODEL]) ToModel(dto *DTO) *MODEL {
|
||||||
|
if dto == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var model MODEL
|
||||||
|
if err := copier.CopyWithOption(&model, dto, m.copierOption); err != nil {
|
||||||
|
panic(err) // Handle error appropriately in production code
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *CopierMapper[DTO, MODEL]) ToDto(model *MODEL) *DTO {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dto DTO
|
||||||
|
if err := copier.CopyWithOption(&dto, model, m.copierOption); err != nil {
|
||||||
|
panic(err) // Handle error appropriately in production code
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dto
|
||||||
|
}
|
||||||
93
mapper/mapper_test.go
Normal file
93
mapper/mapper_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopierMapper(t *testing.T) {
|
||||||
|
type DtoType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelType struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper := NewCopierMapper[DtoType, ModelType]()
|
||||||
|
|
||||||
|
// 测试 ToModel 方法
|
||||||
|
dto := &DtoType{Name: "Alice", Age: 25}
|
||||||
|
model := mapper.ToModel(dto)
|
||||||
|
assert.NotNil(t, model)
|
||||||
|
assert.Equal(t, "Alice", model.Name)
|
||||||
|
assert.Equal(t, 25, model.Age)
|
||||||
|
|
||||||
|
// 测试 ToModel 方法,传入 nil
|
||||||
|
modelNil := mapper.ToModel(nil)
|
||||||
|
assert.Nil(t, modelNil)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法
|
||||||
|
model = &ModelType{Name: "Bob", Age: 30}
|
||||||
|
dtoResult := mapper.ToDto(model)
|
||||||
|
assert.NotNil(t, dtoResult)
|
||||||
|
assert.Equal(t, "Bob", dtoResult.Name)
|
||||||
|
assert.Equal(t, 30, dtoResult.Age)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法,传入 nil
|
||||||
|
dtoNil := mapper.ToDto(nil)
|
||||||
|
assert.Nil(t, dtoNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnumTypeConverter(t *testing.T) {
|
||||||
|
type DtoType string
|
||||||
|
type ModelType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
DtoTypeOne DtoType = "One"
|
||||||
|
DtoTypeTwo DtoType = "Two"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModelTypeOne ModelType = 1
|
||||||
|
ModelTypeTwo ModelType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
nameMap := map[int32]string{
|
||||||
|
1: "One",
|
||||||
|
2: "Two",
|
||||||
|
}
|
||||||
|
valueMap := map[string]int32{
|
||||||
|
"One": 1,
|
||||||
|
"Two": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
converter := NewEnumTypeConverter[DtoType, ModelType](nameMap, valueMap)
|
||||||
|
|
||||||
|
// 测试 ToModel 方法
|
||||||
|
dto := DtoTypeOne
|
||||||
|
model := converter.ToModel(&dto)
|
||||||
|
assert.NotNil(t, model)
|
||||||
|
assert.Equal(t, int32(1), int32(*model))
|
||||||
|
|
||||||
|
// 测试 ToModel 方法,传入不存在的值
|
||||||
|
dtoInvalid := DtoType("Three")
|
||||||
|
modelInvalid := converter.ToModel(&dtoInvalid)
|
||||||
|
assert.Nil(t, modelInvalid)
|
||||||
|
|
||||||
|
// 测试 ToDto 方法
|
||||||
|
tmpModelTwo := ModelTypeTwo
|
||||||
|
model = &tmpModelTwo
|
||||||
|
dtoResult := converter.ToDto(model)
|
||||||
|
assert.NotNil(t, dtoResult)
|
||||||
|
assert.Equal(t, "Two", string(*dtoResult))
|
||||||
|
|
||||||
|
// 测试 ToDto 方法,传入不存在的值
|
||||||
|
tmpModelThree := ModelType(3)
|
||||||
|
modelInvalid = &tmpModelThree
|
||||||
|
dtoInvalidResult := converter.ToDto(modelInvalid)
|
||||||
|
assert.Nil(t, dtoInvalidResult)
|
||||||
|
}
|
||||||
14
password/README.md
Normal file
14
password/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 密码加解密
|
||||||
|
|
||||||
|
算法列表
|
||||||
|
|
||||||
|
| 算法名 | 特点 | 用途 |
|
||||||
|
|-----------------------------------------------|-------------------------------|------------------------|
|
||||||
|
| Bcrypt | 基于 Blowfish 算法,内置盐值,支持调整计算成本。 | 密码存储,防止暴力破解。 |
|
||||||
|
| Argon2 | 内存硬性哈希算法,支持多种参数调整(内存、时间、并行度)。 | 码存储,适合高安全性需求。 |
|
||||||
|
| PBKDF2 | 基于 HMAC 的密钥派生函数,支持多种哈希算法。 | 密码存储,兼容性好。 |
|
||||||
|
| SHA-256/SHA-512 | 快速单向哈希算法,无内置盐值。 | 数据完整性校验,需手动添加盐值用于密码存储。 |
|
||||||
|
| AES (Advanced Encryption Standard) | 对称加密算法,支持 128/192/256 位密钥。 | 数据加密传输或存储。 |
|
||||||
|
| RSA | 非对称加密算法,基于大整数分解难题。 | 数据加密、数字签名。 |
|
||||||
|
| ECDSA/ECDH (椭圆曲线算法) | 基于椭圆曲线的非对称加密,密钥更短但安全性高。 | 数据完整性和认证。 |
|
||||||
|
| HMAC (Hash-based Message Authentication Code) | 基于哈希算法的消息认证码。 | 数据完整性和认证。 |
|
||||||
111
password/argon2.go
Normal file
111
password/argon2.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Argon2Crypto 实现 Argon2id 密码哈希算法
|
||||||
|
type Argon2Crypto struct {
|
||||||
|
// 参数可配置,默认使用推荐值
|
||||||
|
Memory uint32
|
||||||
|
Iterations uint32
|
||||||
|
Parallelism uint8
|
||||||
|
SaltLength uint32
|
||||||
|
KeyLength uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArgon2Crypto 创建带默认参数的 Argon2 加密器
|
||||||
|
func NewArgon2Crypto() *Argon2Crypto {
|
||||||
|
return &Argon2Crypto{
|
||||||
|
Memory: 64 * 1024, // 64MB
|
||||||
|
Iterations: 3,
|
||||||
|
Parallelism: 2,
|
||||||
|
SaltLength: 16,
|
||||||
|
KeyLength: 32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (a *Argon2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, a.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成哈希
|
||||||
|
hash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
a.Iterations,
|
||||||
|
a.Memory,
|
||||||
|
a.Parallelism,
|
||||||
|
a.KeyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 格式化输出(兼容标准格式)
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||||
|
argon2.Version,
|
||||||
|
a.Memory,
|
||||||
|
a.Iterations,
|
||||||
|
a.Parallelism,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(hash),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (a *Argon2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 6 {
|
||||||
|
return false, errors.New("无效的 Argon2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
var version int
|
||||||
|
var memory, iterations uint32
|
||||||
|
var parallelism uint8
|
||||||
|
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
|
||||||
|
if err != nil || version != argon2.Version {
|
||||||
|
return false, errors.New("不支持的 Argon2 版本")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的 Argon2 参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值和哈希
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
keyLength := uint32(len(decodedHash))
|
||||||
|
|
||||||
|
// 使用相同参数生成新哈希
|
||||||
|
newHash := argon2.IDKey(
|
||||||
|
[]byte(password),
|
||||||
|
salt,
|
||||||
|
iterations,
|
||||||
|
memory,
|
||||||
|
parallelism,
|
||||||
|
keyLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return subtle.ConstantTimeCompare(newHash, decodedHash) == 1, nil
|
||||||
|
}
|
||||||
41
password/argon2_test.go
Normal file
41
password/argon2_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArgon2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewArgon2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
34
password/bcrypt.go
Normal file
34
password/bcrypt.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BCryptCrypto struct{}
|
||||||
|
|
||||||
|
func NewBCryptCrypto() *BCryptCrypto {
|
||||||
|
return &BCryptCrypto{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 bcrypt 加密密码,返回加密后的字符串和空盐值
|
||||||
|
func (b *BCryptCrypto) Encrypt(password string) (encrypted string, err error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配加密后的字符串
|
||||||
|
func (b *BCryptCrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(encrypted), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
41
password/bcrypt_test.go
Normal file
41
password/bcrypt_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBCryptCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewBCryptCrypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
142
password/ecdsa_ecdh.go
Normal file
142
password/ecdsa_ecdh.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ECDSACrypto 实现基于 ECDSA 的加密和验证
|
||||||
|
type ECDSACrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDSACrypto 创建一个新的 ECDSACrypto 实例
|
||||||
|
func NewECDSACrypto() (*ECDSACrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 ECDSA 对消息进行签名
|
||||||
|
func (e *ECDSACrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hash[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := r.String() + "$" + s.String()
|
||||||
|
return "ecdsa$" + signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证消息的签名是否有效
|
||||||
|
func (e *ECDSACrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 3)
|
||||||
|
if len(parts) != 3 || parts[0] != "ecdsa" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(big.Int)
|
||||||
|
s := new(big.Int)
|
||||||
|
r.SetString(parts[1], 10)
|
||||||
|
s.SetString(parts[2], 10)
|
||||||
|
|
||||||
|
hash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
return ecdsa.Verify(e.publicKey, hash[:], r, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDHCrypto 实现基于 ECDH 的密钥交换
|
||||||
|
type ECDHCrypto struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewECDHCrypto 创建一个新的 ECDHCrypto 实例
|
||||||
|
func NewECDHCrypto() (*ECDHCrypto, error) {
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ECDHCrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 返回公钥作为加密结果
|
||||||
|
func (e *ECDHCrypto) Encrypt(plainPassword string) (string, error) {
|
||||||
|
if plainPassword == "" {
|
||||||
|
return "", errors.New("密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes := elliptic.Marshal(e.privateKey.Curve, e.publicKey.X, e.publicKey.Y)
|
||||||
|
return "ecdh$" + base64.StdEncoding.EncodeToString(publicKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证共享密钥是否一致
|
||||||
|
func (e *ECDHCrypto) Verify(plainPassword, encrypted string) (bool, error) {
|
||||||
|
if plainPassword == "" || encrypted == "" {
|
||||||
|
return false, errors.New("密码或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(encrypted, "$", 2)
|
||||||
|
if len(parts) != 2 || parts[0] != "ecdh" {
|
||||||
|
return false, errors.New("加密字符串格式无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return false, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
expectedHash := sha256.Sum256(sharedX.Bytes())
|
||||||
|
actualHash := sha256.Sum256([]byte(plainPassword))
|
||||||
|
|
||||||
|
return expectedHash == actualHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ECDHCrypto) DeriveSharedSecret(publicKey string) ([]byte, error) {
|
||||||
|
// 解码对方的公钥
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化公钥
|
||||||
|
x, y := elliptic.Unmarshal(e.privateKey.Curve, publicKeyBytes)
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return nil, errors.New("无效的公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算共享密钥
|
||||||
|
sharedX, _ := e.privateKey.Curve.ScalarMult(x, y, e.privateKey.D.Bytes())
|
||||||
|
|
||||||
|
// 返回共享密钥的字节表示
|
||||||
|
return sharedX.Bytes(), nil
|
||||||
|
}
|
||||||
60
password/ecdsa_ecdh_test.go
Normal file
60
password/ecdsa_ecdh_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestECDSACrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto, err := NewECDSACrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 签名消息
|
||||||
|
encrypted, err := crypto.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("签名失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证签名
|
||||||
|
isValid, err := crypto.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("签名验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDHCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto1, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例1失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto2, err := NewECDHCrypto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 ECDHCrypto 实例2失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "test message"
|
||||||
|
|
||||||
|
// 获取公钥
|
||||||
|
encrypted, err := crypto1.Encrypt(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证共享密钥
|
||||||
|
isValid, err := crypto2.Verify(message, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("共享密钥验证未通过")
|
||||||
|
}
|
||||||
|
}
|
||||||
11
password/go.mod
Normal file
11
password/go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/tx7do/go-utils/password
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
|
require golang.org/x/crypto v0.39.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.33.0 // indirect
|
||||||
|
|
||||||
|
replace github.com/tx7do/go-utils => ../
|
||||||
6
password/go.sum
Normal file
6
password/go.sum
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
50
password/hmac.go
Normal file
50
password/hmac.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HMACCrypto 实现基于 HMAC 的加密和验证
|
||||||
|
type HMACCrypto struct {
|
||||||
|
secretKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHMACCrypto 创建一个新的 HMACCrypto 实例
|
||||||
|
func NewHMACCrypto(secretKey string) *HMACCrypto {
|
||||||
|
return &HMACCrypto{
|
||||||
|
secretKey: []byte(secretKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用 HMAC-SHA256 对数据进行加密
|
||||||
|
func (h *HMACCrypto) Encrypt(data string) (string, error) {
|
||||||
|
if data == "" {
|
||||||
|
return "", errors.New("数据不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, h.secretKey)
|
||||||
|
_, err := mac.Write([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := mac.Sum(nil)
|
||||||
|
return hex.EncodeToString(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证数据的 HMAC 值是否匹配
|
||||||
|
func (h *HMACCrypto) Verify(data, encrypted string) (bool, error) {
|
||||||
|
if data == "" || encrypted == "" {
|
||||||
|
return false, errors.New("数据或加密字符串不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHash, err := h.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hmac.Equal([]byte(expectedHash), []byte(encrypted)), nil
|
||||||
|
}
|
||||||
43
password/hmac_test.go
Normal file
43
password/hmac_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMACCrypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
secretKey := "mysecretkey"
|
||||||
|
crypto := NewHMACCrypto(secretKey)
|
||||||
|
|
||||||
|
data := "testdata"
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
encrypted, err := crypto.Encrypt(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证
|
||||||
|
isValid, err := crypto.Verify(data, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败的情况
|
||||||
|
isValid, err = crypto.Verify("wrongdata", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证结果错误,预期验证失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
29
password/interface.go
Normal file
29
password/interface.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Crypto 密码加解密接口
|
||||||
|
type Crypto interface {
|
||||||
|
// Encrypt 加密密码,返回加密后的字符串(包含算法标识和盐值)
|
||||||
|
Encrypt(plainPassword string) (encrypted string, err error)
|
||||||
|
|
||||||
|
// Verify 验证密码是否匹配
|
||||||
|
Verify(plainPassword, encrypted string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateCrypto(algorithm string) (Crypto, error) {
|
||||||
|
algorithm = strings.ToLower(algorithm)
|
||||||
|
switch algorithm {
|
||||||
|
case "bcrypt":
|
||||||
|
return NewBCryptCrypto(), nil
|
||||||
|
case "pbkdf2":
|
||||||
|
return NewPBKDF2Crypto(), nil
|
||||||
|
case "argon2":
|
||||||
|
return NewArgon2Crypto(), nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("不支持的加密算法")
|
||||||
|
}
|
||||||
|
}
|
||||||
150
password/pbkdf2.go
Normal file
150
password/pbkdf2.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PBKDF2Crypto 实现 PBKDF2-HMAC 密码哈希算法
|
||||||
|
type PBKDF2Crypto struct {
|
||||||
|
// 可配置参数,默认使用推荐值
|
||||||
|
Iterations int
|
||||||
|
KeyLength int
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2Crypto 创建带默认参数的 PBKDF2 加密器 (SHA256)
|
||||||
|
func NewPBKDF2Crypto() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 310000, // NIST 推荐最小值
|
||||||
|
KeyLength: 32, // 256-bit
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPBKDF2WithSHA512 创建使用 SHA512 的 PBKDF2 加密器
|
||||||
|
func NewPBKDF2WithSHA512() *PBKDF2Crypto {
|
||||||
|
return &PBKDF2Crypto{
|
||||||
|
Iterations: 600000, // SHA512 需要更多迭代
|
||||||
|
KeyLength: 64, // 512-bit
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密
|
||||||
|
func (p *PBKDF2Crypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值 (16 bytes 推荐最小值)
|
||||||
|
salt := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成密钥
|
||||||
|
key := pbkdf2Key([]byte(password), salt, p.Iterations, p.KeyLength, p.Hash)
|
||||||
|
|
||||||
|
// 格式: pbkdf2:<hash>:<iterations>:<base64-salt>:<base64-key>
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"pbkdf2:%s:%d:%s:%s",
|
||||||
|
p.HashName,
|
||||||
|
p.Iterations,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
base64.RawStdEncoding.EncodeToString(key),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (p *PBKDF2Crypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, ":")
|
||||||
|
if len(parts) != 5 || parts[0] != "pbkdf2" {
|
||||||
|
return false, errors.New("无效的 PBKDF2 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析参数
|
||||||
|
hashName := parts[1]
|
||||||
|
iterations, err := strconv.Atoi(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.New("无效的迭代次数")
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[3])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedKey, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据哈希名称选择哈希函数
|
||||||
|
hashFunc, ok := getHashFunction(hashName)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("不支持的哈希算法: %s", hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新密钥
|
||||||
|
keyLength := len(expectedKey)
|
||||||
|
newKey := pbkdf2Key([]byte(password), salt, iterations, keyLength, hashFunc)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return hmac.Equal(newKey, expectedKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pbkdf2Key 实现 PBKDF2 核心算法
|
||||||
|
func pbkdf2Key(password, salt []byte, iterations, keyLength int, hashFunc func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(hashFunc, password)
|
||||||
|
hashLength := prf.Size()
|
||||||
|
blockCount := (keyLength + hashLength - 1) / hashLength
|
||||||
|
|
||||||
|
output := make([]byte, 0, blockCount*hashLength)
|
||||||
|
for i := 1; i <= blockCount; i++ {
|
||||||
|
// U1 = PRF(password, salt || INT(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
binary.BigEndian.PutUint32(make([]byte, 4), uint32(i))
|
||||||
|
prf.Write([]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)})
|
||||||
|
u := prf.Sum(nil)
|
||||||
|
|
||||||
|
// F = U1 ⊕ U2 ⊕ ... ⊕ U_iterations
|
||||||
|
f := make([]byte, len(u))
|
||||||
|
copy(f, u)
|
||||||
|
|
||||||
|
for j := 1; j < iterations; j++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(u)
|
||||||
|
u = prf.Sum(nil)
|
||||||
|
for k := 0; k < len(f); k++ {
|
||||||
|
f[k] ^= u[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output = append(output, f...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output[:keyLength]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getHashFunction 根据名称获取哈希函数
|
||||||
|
func getHashFunction(name string) (func() hash.Hash, bool) {
|
||||||
|
switch name {
|
||||||
|
case "sha256":
|
||||||
|
return sha256.New, true
|
||||||
|
case "sha512":
|
||||||
|
return sha512.New, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
77
password/pbkdf2_test.go
Normal file
77
password/pbkdf2_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPBKDF2Crypto_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2Crypto()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPBKDF2WithSHA512_EncryptAndVerify(t *testing.T) {
|
||||||
|
crypto := NewPBKDF2WithSHA512()
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
// 测试验证成功
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证未通过,密码应匹配")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试验证失败
|
||||||
|
isValid, err = crypto.Verify("wrongpassword", encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
t.Fatal("验证通过,但密码不应匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
73
password/rsa.go
Normal file
73
password/rsa.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RSACrypto 实现 RSA 加密和解密
|
||||||
|
type RSACrypto struct {
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRSACrypto 创建一个新的 RSACrypto 实例
|
||||||
|
func NewRSACrypto(keySize int) (*RSACrypto, error) {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RSACrypto{
|
||||||
|
privateKey: privateKey,
|
||||||
|
publicKey: &privateKey.PublicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 使用公钥加密数据
|
||||||
|
func (r *RSACrypto) Encrypt(data string) (string, error) {
|
||||||
|
encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, r.publicKey, []byte(data), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(encryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt 使用私钥解密数据
|
||||||
|
func (r *RSACrypto) Decrypt(encryptedData string) (string, error) {
|
||||||
|
decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
decryptedBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, r.privateKey, decodedData, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(decryptedBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPrivateKey 导出私钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPrivateKey() (string, error) {
|
||||||
|
privateKeyBytes := x509.MarshalPKCS1PrivateKey(r.privateKey)
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: privateKeyBytes,
|
||||||
|
})
|
||||||
|
return string(privateKeyPEM), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPublicKey 导出公钥为 PEM 格式
|
||||||
|
func (r *RSACrypto) ExportPublicKey() (string, error) {
|
||||||
|
publicKeyBytes, err := x509.MarshalPKIXPublicKey(r.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: publicKeyBytes,
|
||||||
|
})
|
||||||
|
return string(publicKeyPEM), nil
|
||||||
|
}
|
||||||
63
password/rsa_test.go
Normal file
63
password/rsa_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRSACrypto_EncryptAndDecrypt(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试加密
|
||||||
|
originalData := "securedata"
|
||||||
|
encryptedData, err := crypto.Encrypt(originalData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encryptedData == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encryptedData)
|
||||||
|
|
||||||
|
// 测试解密
|
||||||
|
decryptedData, err := crypto.Decrypt(encryptedData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("解密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if decryptedData != originalData {
|
||||||
|
t.Fatalf("解密结果不匹配,期望: %s,实际: %s", originalData, decryptedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRSACrypto_ExportKeys(t *testing.T) {
|
||||||
|
// 创建 RSACrypto 实例
|
||||||
|
crypto, err := NewRSACrypto(2048)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 RSACrypto 实例失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出私钥
|
||||||
|
privateKey, err := crypto.ExportPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出私钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == "" {
|
||||||
|
t.Fatal("导出的私钥为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试导出公钥
|
||||||
|
publicKey, err := crypto.ExportPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("导出公钥失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == "" {
|
||||||
|
t.Fatal("导出的公钥为空")
|
||||||
|
}
|
||||||
|
}
|
||||||
110
password/sha.go
Normal file
110
password/sha.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SHACrypto 实现 SHA-256/SHA-512 密码哈希算法(带盐值)
|
||||||
|
type SHACrypto struct {
|
||||||
|
Hash func() hash.Hash
|
||||||
|
HashName string
|
||||||
|
SaltLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA256Crypto 创建 SHA-256 加密器
|
||||||
|
func NewSHA256Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha256.New,
|
||||||
|
HashName: "sha256",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA512Crypto 创建 SHA-512 加密器
|
||||||
|
func NewSHA512Crypto() *SHACrypto {
|
||||||
|
return &SHACrypto{
|
||||||
|
Hash: sha512.New,
|
||||||
|
HashName: "sha512",
|
||||||
|
SaltLength: 16, // 16 字节盐值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt 实现密码加密(带盐值)
|
||||||
|
func (s *SHACrypto) Encrypt(password string) (string, error) {
|
||||||
|
// 生成随机盐值
|
||||||
|
salt := make([]byte, s.SaltLength)
|
||||||
|
if _, err := rand.Read(salt); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
hashBytes := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 格式: sha256:$salt$hash 或 sha512:$salt$hash
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s$%s$%s",
|
||||||
|
s.HashName,
|
||||||
|
base64.RawStdEncoding.EncodeToString(salt),
|
||||||
|
hex.EncodeToString(hashBytes),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 验证密码
|
||||||
|
func (s *SHACrypto) Verify(password, encrypted string) (bool, error) {
|
||||||
|
// 解析哈希字符串
|
||||||
|
parts := strings.Split(encrypted, "$")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return false, errors.New("无效的 SHA 哈希格式")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashName := parts[0]
|
||||||
|
if hashName != s.HashName {
|
||||||
|
return false, fmt.Errorf("哈希算法不匹配: 期望 %s, 实际 %s", s.HashName, hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码盐值
|
||||||
|
salt, err := base64.RawStdEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码原始哈希值
|
||||||
|
originalHash, err := hex.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算新哈希
|
||||||
|
hashValue := s.Hash()
|
||||||
|
hashValue.Write(salt)
|
||||||
|
hashValue.Write([]byte(password))
|
||||||
|
newHash := hashValue.Sum(nil)
|
||||||
|
|
||||||
|
// 安全比较
|
||||||
|
return compareHash(newHash, originalHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareHash 安全比较两个哈希值
|
||||||
|
func compareHash(h1, h2 []byte) bool {
|
||||||
|
if len(h1) != len(h2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
result := 0
|
||||||
|
for i := 0; i < len(h1); i++ {
|
||||||
|
result |= int(h1[i] ^ h2[i])
|
||||||
|
}
|
||||||
|
return result == 0
|
||||||
|
}
|
||||||
53
password/sha_test.go
Normal file
53
password/sha_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package password
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA256(t *testing.T) {
|
||||||
|
crypto := NewSHA256Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSHACrypto_EncryptAndVerify_SHA512(t *testing.T) {
|
||||||
|
crypto := NewSHA512Crypto()
|
||||||
|
|
||||||
|
password := "securepassword"
|
||||||
|
encrypted, err := crypto.Encrypt(password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("加密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted == "" {
|
||||||
|
t.Fatal("加密结果为空")
|
||||||
|
}
|
||||||
|
t.Log(encrypted)
|
||||||
|
|
||||||
|
isValid, err := crypto.Verify(password, encrypted)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("验证失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isValid {
|
||||||
|
t.Fatal("验证结果不匹配")
|
||||||
|
}
|
||||||
|
}
|
||||||
2
tag.bat
2
tag.bat
@@ -8,6 +8,8 @@ git tag jwtutil/v0.0.2
|
|||||||
git tag id/v0.0.2
|
git tag id/v0.0.2
|
||||||
git tag slug/v0.0.1
|
git tag slug/v0.0.1
|
||||||
git tag name_generator/v0.0.1
|
git tag name_generator/v0.0.1
|
||||||
|
git tag mapper/v0.0.1
|
||||||
|
git tag password/v0.0.1
|
||||||
|
|
||||||
git tag entgo/v1.1.31
|
git tag entgo/v1.1.31
|
||||||
git tag gorm/v1.1.6
|
git tag gorm/v1.1.6
|
||||||
|
|||||||
Reference in New Issue
Block a user