first commit
This commit is contained in:
commit
763be7adbb
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
# OS General
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
pem
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
## 功能介绍
|
||||||
|
|
||||||
|
1. 接口 SDK。详见 [接口介绍](services)。
|
||||||
|
2. HTTP 客户端,支持请求签名和应答验签。如果 SDK 未支持你需要的接口,请用此客户端发起请求。
|
||||||
|
3. 回调通知处理库,支持回调通知的验签。详见 [回调通知验签](#回调通知的验签)。
|
||||||
|
4. 密钥生成下载、[敏感信息加解密](#敏感信息加解密) 等辅助能力。
|
||||||
|
|
||||||
|
|
||||||
|
#### 名词解释
|
||||||
|
+ **商户 API 公钥**,是用来证实商户身份的
|
||||||
|
+ **商户 API 私钥**。是用来证实商户身份的
|
||||||
|
+ **商户 API 密钥**。是商户用来加密请求参数的密钥,为加强数据安全,使用的对称加密密钥。
|
||||||
|
> :warning: 不要把私钥文件暴露在公共场合,如上传到 Github,写在客户端代码等。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
#### 1、使用 Go Modules 管理你的项目
|
||||||
|
|
||||||
|
如果你的项目还不是使用 Go Modules 做依赖管理,在项目根目录下执行:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go mod init
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2、在项目目录中执行:
|
||||||
|
```shell
|
||||||
|
go get -u github.com/sleepinggodoflove/lansexiongdi-marketing-sdk
|
||||||
|
```
|
||||||
|
来添加依赖,完成 `go.mod` 修改与 SDK 下载。
|
||||||
|
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
#### [获取key码](https://tvd8jq9lqkp.feishu.cn/wiki/PVq3wtanPicDu0kyfpLc0McMnAc?from=from_copylink)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: "123",
|
||||||
|
PrivateKey: "私钥",
|
||||||
|
PublicKey: "验签公钥",
|
||||||
|
Key: "业务参数密钥key",
|
||||||
|
SignType: "签名类型",
|
||||||
|
BaseURL: "请求地址:https://api.lansexiongdi.com",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new core err:%v", err)
|
||||||
|
}
|
||||||
|
a := &key.Key{c}
|
||||||
|
_,_,r, err := a.Order(context.Background(), &key.OrderRequest{
|
||||||
|
OutBizNo: "123456",
|
||||||
|
ActivityNo: "123456",
|
||||||
|
Number: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("key get err:%v", err)
|
||||||
|
}
|
||||||
|
log.Printf(r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [查询key码](https://tvd8jq9lqkp.feishu.cn/wiki/GvRswEDyfiXGUUkkDCYc8xg4nVX?from=from_copylink)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: "123",
|
||||||
|
PrivateKey: "私钥",
|
||||||
|
PublicKey: "验签公钥",
|
||||||
|
Key: "业务参数密钥key",
|
||||||
|
SignType: "签名类型",
|
||||||
|
BaseURL: "请求地址",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new core err:%v", err)
|
||||||
|
}
|
||||||
|
a := &key.Key{c}
|
||||||
|
_,_,r, err := a.Query(context.Background(), &key.QueryRequest{
|
||||||
|
OutBizNo: "123456",
|
||||||
|
trade_no: "123456",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("key query err:%v", err)
|
||||||
|
}
|
||||||
|
log.Printf(r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [作废key码](https://tvd8jq9lqkp.feishu.cn/wiki/R9NMw96eIiXLiRkOi7icANkynbb?from=from_copylink)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: "123",
|
||||||
|
PrivateKey: "私钥",
|
||||||
|
PublicKey: "验签公钥",
|
||||||
|
Key: "业务参数密钥key",
|
||||||
|
SignType: "签名类型",
|
||||||
|
BaseURL: "请求地址",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new core err:%v", err)
|
||||||
|
}
|
||||||
|
a := &key.Key{c}
|
||||||
|
_,_,r, err := a.Discard(context.Background(), &key.DiscardRequest{
|
||||||
|
OutBizNo: "123456",
|
||||||
|
trade_no: "123456",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("key query err:%v", err)
|
||||||
|
}
|
||||||
|
log.Printf(r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [回调通知](https://alidocs.dingtalk.com/i/nodes/N7dx2rn0Jb6A1wvLixErNlLkJMGjLRb3?utm_scene=team_space)
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/key"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: "123",
|
||||||
|
PrivateKey: "私钥",
|
||||||
|
PublicKey: "验签公钥",
|
||||||
|
Key: "业务参数密钥key",
|
||||||
|
SignType: "签名类型",
|
||||||
|
BaseURL: "请求地址",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new core err:%v", err)
|
||||||
|
}
|
||||||
|
a := &key.Key{c}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Header: nil, // 请求头
|
||||||
|
Body: nil, // 请求体
|
||||||
|
}
|
||||||
|
r, err := a.CallBack(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("key callBack err:%v", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("key notify err:%v", err)
|
||||||
|
}
|
||||||
|
log.Printf(r)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [其它调用](https://alidocs.dingtalk.com/i/nodes/N7dx2rn0Jb6A1wvLixErNlLkJMGjLRb3?utm_scene=team_space)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api/v1/anyapi"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: "appid",
|
||||||
|
PrivateKey: "私钥",
|
||||||
|
PublicKey: "验签公钥",
|
||||||
|
Key: "业务参数密钥key",
|
||||||
|
SignType: "签名类型",
|
||||||
|
BaseURL: "请求地址",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new core err:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bizContent := struct {
|
||||||
|
Source string `json:"source"` // 来源
|
||||||
|
AppId string `json:"app_id"` // 应用Id
|
||||||
|
MchPublicKey string `json:"mch_public_key"` // 客户公钥
|
||||||
|
NotifyUrl string `json:"notify_url"` // 事件通知地址,可为空
|
||||||
|
}{
|
||||||
|
Source: "来源",
|
||||||
|
AppId: "123",
|
||||||
|
MchPublicKey: "123",
|
||||||
|
NotifyUrl: "https://xx.com/xx",
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &anyapi.AnyApi{c}
|
||||||
|
|
||||||
|
method := "/openapi/v1/xxx"
|
||||||
|
|
||||||
|
_,_, r, err := a.AnyApi(context.Background(), method, bizContent)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("call err:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
log.Fatalf("err:%s", r.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bizDataContent = struct {
|
||||||
|
Ciphertext string `json:"ciphertext,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
_ = json.Unmarshal(r.Data, &bizDataContent)
|
||||||
|
|
||||||
|
bizJsonContent, _ := c.CryptographySuite.Cipher.Decode(bizDataContent.Ciphertext)
|
||||||
|
|
||||||
|
log.Printf("bizJsonContent=%s", bizJsonContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
*core.Core
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package anyapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnyApi api.Service
|
||||||
|
|
||||||
|
func (a *AnyApi) AnyApi(ctx context.Context, method string, bizContent any) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
p, err := a.BuildAnyApiParams(bizContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBodyBytes, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := a.GetHeaders(p)
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s%s", a.Config.BaseURL, method)
|
||||||
|
|
||||||
|
httpResponse, bodyBytes, err := a.Request(ctx, h, http.MethodPost, url, reqBodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package anyapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appId = "OP001"
|
||||||
|
privateKey = ""
|
||||||
|
publicKey = ""
|
||||||
|
key = ""
|
||||||
|
baseURL = "http://127.0.0.1:9000"
|
||||||
|
signType = core.SignRSA
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCore() *core.Core {
|
||||||
|
|
||||||
|
c, err := core.NewCore(&core.Config{
|
||||||
|
AppID: appId,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
Key: key,
|
||||||
|
SignType: signType,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_AnyApi(t *testing.T) {
|
||||||
|
|
||||||
|
c := newCore()
|
||||||
|
|
||||||
|
bizContent := struct {
|
||||||
|
Source string `json:"source"` // 来源
|
||||||
|
AppId string `json:"app_id"` // 应用Id
|
||||||
|
MchPublicKey string `json:"mch_public_key"` // 客户公钥
|
||||||
|
NotifyUrl string `json:"notify_url"` // 事件通知地址,可为空
|
||||||
|
}{
|
||||||
|
Source: "来源",
|
||||||
|
AppId: "OP002",
|
||||||
|
MchPublicKey: "",
|
||||||
|
NotifyUrl: "https://utils.85938.cn/utils/v1/wechat/notify",
|
||||||
|
}
|
||||||
|
|
||||||
|
a := &AnyApi{c}
|
||||||
|
|
||||||
|
method := "/openapi/v1/merchant/appSet"
|
||||||
|
|
||||||
|
h, _, r, err := a.AnyApi(context.Background(), method, bizContent)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Error(r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("data=%s", string(r.Data))
|
||||||
|
|
||||||
|
var bizDataContent = struct {
|
||||||
|
Ciphertext string `json:"ciphertext,omitempty"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
_ = json.Unmarshal(r.Data, &bizDataContent)
|
||||||
|
|
||||||
|
bizJsonContent, _ := c.CryptographySuite.Cipher.Decode(bizDataContent.Ciphertext)
|
||||||
|
|
||||||
|
t.Logf("header=%+v", h)
|
||||||
|
t.Logf("bizJsonContent=%s", bizJsonContent)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
orderMethod = "/openapi/v1/key/order"
|
||||||
|
queryMethod = "/openapi/v1/key/query"
|
||||||
|
discardMethod = "/openapi/v1/key/discard"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key api.Service
|
||||||
|
|
||||||
|
func (k *Key) Order(ctx context.Context, request *OrderRequest) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
h, httpResponse, bodyBytes, err := k.Post(ctx, orderMethod, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Query(ctx context.Context, request *QueryRequest) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
h, httpResponse, bodyBytes, err := k.Post(ctx, queryMethod, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Discard(ctx context.Context, request *DiscardRequest) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
h, httpResponse, bodyBytes, err := k.Post(ctx, discardMethod, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Notify(_ context.Context, n *Notify) (*NotifyData, error) {
|
||||||
|
|
||||||
|
if !k.CryptographySuite.Verifier.Verify(n.SignString(), n.Sign) {
|
||||||
|
return nil, fmt.Errorf("verify sign fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &n.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) CallBack(ctx context.Context, req *http.Request) (*NotifyData, error) {
|
||||||
|
|
||||||
|
body, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var n *Notify
|
||||||
|
if err = json.Unmarshal(body, &n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := req.Header.Get("Sign")
|
||||||
|
|
||||||
|
if sign == "" {
|
||||||
|
return k.Notify(ctx, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := req.Header.Get("Timestamp")
|
||||||
|
if timestamp == "" {
|
||||||
|
return nil, fmt.Errorf("timestamp is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
appid := req.Header.Get("Appid")
|
||||||
|
if appid == "" {
|
||||||
|
return nil, fmt.Errorf("appid is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
signType := req.Header.Get("Sign-Type")
|
||||||
|
if signType == "" {
|
||||||
|
return nil, fmt.Errorf("sign-type is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if appid != k.Config.AppID {
|
||||||
|
return nil, fmt.Errorf("appid is invalid")
|
||||||
|
}
|
||||||
|
if signType != string(k.Config.SignType) {
|
||||||
|
return nil, fmt.Errorf("sign-type is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := k.GetCiphertext(&n.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !k.Verify(timestamp, ciphertext, sign) {
|
||||||
|
return nil, fmt.Errorf("call back verify sign fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &n.Data, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appId = "lzm"
|
||||||
|
privateKey = "MIIEowIBAAKCAQEA7a2I4l8OOdW4weVFvj4u/mBqP3aZhJ0mOTKl4MCW4Pf6gNAlZa5dZYOS/BocmG872+pd10BiI73qiAWsuVaPwCL0A37lQbCXlG0fDAfCLogXuF1qVNRZgkYKrx/5Gppo2PNed7E5YyCUkMUKVPbuwuZteMZJH8d1o6Uojbb/xJQvAGOlx5Y04VZWp/6p2GjhW0srwgbpVegMyyn2Qblx1Lo+Uq5zG8um7FTpbtb/L/itpBFEDSZGIIKDfn4FPyt+jQ0SW5TDYQClSvWHK4V3RkWOVkD1nHeBpZyp7JNehK+7kBfO6G4NJabkyoWqFEiZcTy38ZWQdqJ9N4LZuY37NwIDAQABAoIBAGs2u6e5z1YBda1pehN+Q36WCXeFTW0H4qUslq0S0zy6P/L5cdUzWYggWR6FvN56Vts2Foyxy1NqKTCgtrCIPqIiYkZtaIdAXLAkpTutCEgrNeABq6SGgbYFWG51Es6QVrl+1t9RP5zaponDiIyZM00R2tH/SB8gv41JREjhAvEuNIwPyaoVVt+U/kAdhJgiMKsDpoGaMfsJk76sORu6qQqBkBN8cglN94xC0QtROytW3EY8SnZmgGZHcY3YTXM74CWM8yBg7rNuKv0982f9hKvUDHKMFYly1PzYiSgplkT7RYCMjo2FFf1lt7k7N61+4nalS/EM6324m2poisTRFAkCgYEA+3oXvtwrTim0kXG7V7w5PS8u5dU0sAAH4ACSyzy8nEdKEk4ipoaTGm6km8ko0O+9E70SwZQgK/eAnmsYf5WtMzvItweKUUVsBCm01qSHlu5vzGO3H1ndi7hg+tH9VrOQH3+odQJP9FqC4BkncMszHM4nLglWSTixTTvGIovQLy0CgYEA8fPnu0tWqhfQV6svaA5kt4h6cL52ARKlubRuYkI4hGuikKYpd2A3WuVtD1LkuPQSjwID9730HAqLc7ZMwONjQ8NANi9ZoJR6A+Vzba9zDPQmSc80Ax7Kkjc03D1Y7yiP6P8bWnhCCbRcMy+dcobvBZc2zaWzSNjZwPOSV9xaMnMCgYAclz354hg+U7mGy7JsACdV0HZ5hOrvk6FRk18dIjOjZOuD90QzQJua5rdqSs2MK6WIh/eI8KlTtlj2KeDoKIE/kO15+a59HPJx6rf3q08LFuK5DyEzvEjW6MiF27f80n9xRVdGrlOeyWeVyOZWCZQvEzUbI86eloZ57HDTXqf1pQKBgF6T6xeJgZ0Hpgc/AU75oWEk1kfQC6yrr2CCKUv7esA4mtlUOo1RbRH48MK2snWh4sdIEGj9NbjoXk6jCim0OQ85+ZW0uKJOp8tyG8baeGyt23GqrzgxBxpUvjMBQAxsnKSFZBnfPGEywX+4syEbob9btq54gTaOncAQ9jmmBxQFAoGBAIpPbq2lYwOhgoUJ2BR34xjpmNOiOAF5AVLPGTH44a+iGMJ4tbF9AvfL4xsCWK9zMi3ExaKVN0lNn0cWx2lpXxwO6B+l4L//eczmHx4h1eLJd6ZWyTj7lq+RBOOUgHLKEssZfJ11RYTZjSD7s75JZteM2OFw7BVRRNgw387A3mj6"
|
||||||
|
publicKey = "MIIBCgKCAQEA0w4XGS1eEO9gAtWoB0E1vi1QH3xZAiHnkzZMhZRJOKeZhNUb9nmPzrGtCFD1c+to9/hxKnWZnRi1dklRGI4uXaB7PKuDhifHarBTTPBzW/8m+YqKEwjT2XWYvnG1Zeek4a45xze5cHhLA7Ow1Lwgy0u1rhalvz8GbCa9A7ZHKvZtIJJzfPSIV6gZIz5b7+v7rXZzMNNxvC7m+cwtvvERPjhJoj3O7ithcgdiT3JkZd1fZxkA6HCJx1I+TElt4qA9WnV+rqQwjka1gxBO497c0MUq+4Tx+lLGKpb61RPja4+9wiLFvEiS80WyZYkptWlA0Z5mhsxURs/OqaMyVXzbhwIDAQAB"
|
||||||
|
key = "5f42e758a38cc003c5da7cee814ddfd5"
|
||||||
|
baseURL = "https://gateway.dev.cdlsxd.cn"
|
||||||
|
signType = core.SignRSA
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCore() *Key {
|
||||||
|
c, _ := core.NewCore(&core.Config{
|
||||||
|
AppID: appId,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
Key: key,
|
||||||
|
SignType: signType,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
})
|
||||||
|
k := Key{c}
|
||||||
|
|
||||||
|
return &k
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder(t *testing.T) {
|
||||||
|
|
||||||
|
c := newCore()
|
||||||
|
|
||||||
|
h, _, r, err := c.Order(context.Background(), &OrderRequest{
|
||||||
|
OutBizNo: "cs002",
|
||||||
|
ActivityNo: "lzmcs",
|
||||||
|
Number: 1,
|
||||||
|
NotifyUrl: "",
|
||||||
|
Account: "",
|
||||||
|
Extra: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Errorf("获取key失败:%s", r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("data=%s", string(r.Data))
|
||||||
|
t.Logf("headers=%+v", h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
|
||||||
|
c := newCore()
|
||||||
|
|
||||||
|
h, _, r, err := c.Query(context.Background(), &QueryRequest{
|
||||||
|
OutBizNo: "cs001",
|
||||||
|
TradeNo: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Errorf("查询失败:%s", r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("data=%s", string(r.Data))
|
||||||
|
t.Logf("headers=%+v", h)
|
||||||
|
//t.Log(result.Status.IsNormal())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDiscard(t *testing.T) {
|
||||||
|
|
||||||
|
c := newCore()
|
||||||
|
|
||||||
|
h, _, r, err := c.Discard(context.Background(), &DiscardRequest{
|
||||||
|
OutBizNo: "N123456003",
|
||||||
|
TradeNo: "",
|
||||||
|
Reason: "正常作废",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Errorf(r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("headers=%+v", h)
|
||||||
|
t.Logf("respons=%+v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResponse(t *testing.T) {
|
||||||
|
|
||||||
|
jsonBytes := []byte(`{"code":200,"data":{},"message":"成功"}`)
|
||||||
|
resp, err := core.BuildResponse(jsonBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := ConvertData(resp.Data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%+v", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallBack(t *testing.T) {
|
||||||
|
|
||||||
|
reqStr := `{"data": {"url": "https://gateway.dev.cdlsxd.cn/yxh5/dpK5ly6oVVE2AM0W", "status": 2, "account": "18479266021", "trade_no": "794167617429315585", "notify_id": "7345294954732199936", "usage_num": 0, "out_biz_no": "0627002001", "usable_num": 2, "usage_time": "2025-06-30 11:39:46", "valid_end_time": "2026-06-30 23:59:59", "settlement_price": 2, "valid_begin_time": "2025-06-24 11:00:08"}, "sign": "XLwRQ12EBXSGOSVzUMXwjSlKP88P4Odhe6c9MrfaszKLe+3HtPTeB6QWvyAmXGeIvsy02P0YtcOYV4xQHlWo3Uh5FZc6IJU/+KN+xVnn/DlFLpc+DhCKw6o4hYv+eLLyshjFZPZYVUU2I2YmkI1ZlwBaufsB+N9ds8gBz5+hELn17/qcFcbO6pYOd2te7xmJSGKOAMn0q2c2DSvTLvyQXhKUlDZfUZZGBOc1LGChy9CHc7Z/0E8/p2YYTlMPnvk0VHjEjV5sJxDnXwhSZqE7f3mRx0IN3au3VtZnXJsgl/whxdTyab9dYpfIxK75bS0mjncdqxGf1hLdhYJhTx8bog==", "app_id": "lzm", "sign_type": "RSA", "timestamp": "2025-06-30 11:39:46"}`
|
||||||
|
headerStr := `{"Sign": ["Boj6IrOOrRATJt0IBE+z5Ie/g4mo3MZk+JpJ4bLYoBbDfMqvgTBhxqiC8CheRm/nEF9iFFJCvq9S0dL25fLexQ1k5AxE3cX1+qR5fCRdaiZvqWG4jaXOjUUW8K7fQ9g5ii6T4b3cWp71FBHiG3ZH5XohM9JuLo3W17MxrizsLLD0euGROAY3bXcakVustto07V3i0g59+ajsCTTdxF/gNcrsO5a3eTJ8CTSDnMgpwqMbU+E9YMX1zGFH/+m/RtL9s8tLRf8j4T/t8g6b94JfvBv+Fu1wV4eMUO7H4Iv0LJ1TL8qMBkWul5BbwGxSdEGQoWU0CIAehYTfR5meKxTOTQ=="], "Appid": ["lzm"], "Version": ["1.0"], "Sign-Type": ["RSA"], "Timestamp": ["2025-06-30 11:39:46"], "Content-Type": ["application/json"]}`
|
||||||
|
|
||||||
|
var headers http.Header
|
||||||
|
if err := json.Unmarshal([]byte(headerStr), &headers); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newCore()
|
||||||
|
request := &http.Request{
|
||||||
|
Header: headers,
|
||||||
|
Body: io.NopCloser(bytes.NewBuffer([]byte(reqStr))),
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := c.CallBack(context.Background(), request)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("response=%+v", r)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ core.Request = (*OrderRequest)(nil)
|
||||||
|
var _ core.Request = (*DiscardRequest)(nil)
|
||||||
|
var _ core.Request = (*QueryRequest)(nil)
|
||||||
|
var _ core.Request = (*NotifyData)(nil)
|
||||||
|
|
||||||
|
type OrderRequest struct {
|
||||||
|
OutBizNo string `validate:"required,alphanum,min=2,max=32" json:"out_biz_no"` // 同一商户应用下不可重复
|
||||||
|
ActivityNo string `validate:"required,min=2,max=32" json:"activity_no"`
|
||||||
|
Number int32 `validate:"required,eq=1" json:"number"` // v1只支持1,若要多个,请异步v2接口批量生产
|
||||||
|
NotifyUrl string `json:"notify_url,omitempty"` // 回调地址,可为空
|
||||||
|
Account string `json:"account,omitempty"` // 可兑换账号
|
||||||
|
Extra string `json:"extra,omitempty"` // 拓展参数,备用
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *OrderRequest) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OrderRequest) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(c); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryRequest struct {
|
||||||
|
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
|
||||||
|
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *QueryRequest) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QueryRequest) Validate() error {
|
||||||
|
|
||||||
|
if q.OutBizNo == "" && q.TradeNo == "" {
|
||||||
|
return fmt.Errorf("参数错误,out_biz_no/trade_no 二选一")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validator.New().Struct(q); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscardRequest struct {
|
||||||
|
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
|
||||||
|
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
|
||||||
|
Reason string `json:"reason,omitempty" validate:"omitempty,min=1,max=50"` // 可为空
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *DiscardRequest) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DiscardRequest) Validate() error {
|
||||||
|
|
||||||
|
if d.OutBizNo == "" && d.TradeNo == "" {
|
||||||
|
return fmt.Errorf("out_biz_no/trade_no 二选一")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validator.New().Struct(d); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyData struct {
|
||||||
|
NotifyId string `json:"notify_id" validate:"required,alphanum,min=2,max=32"`
|
||||||
|
OutBizNo string `json:"out_biz_no" validate:"required,alphanum,min=2,max=32"`
|
||||||
|
TradeNo string `json:"trade_no" validate:"required,alphanum,min=2,max=32"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
UsableNum uint32 `json:"usable_num"`
|
||||||
|
UsageNum uint32 `json:"usage_num"`
|
||||||
|
Status Status `json:"status" validate:"required"`
|
||||||
|
Url string `json:"url,omitempty"`
|
||||||
|
Amount float32 `json:"amount,omitempty"`
|
||||||
|
PayAmount float32 `json:"pay_amount,omitempty"`
|
||||||
|
PayTime string `json:"pay_time,omitempty"`
|
||||||
|
SettlementPrice float32 `json:"settlement_price,omitempty"`
|
||||||
|
ValidBeginTime string `json:"valid_begin_time,omitempty"`
|
||||||
|
ValidEndTime string `json:"valid_end_time,omitempty"`
|
||||||
|
UsageTime string `json:"usage_time,omitempty"`
|
||||||
|
DiscardTime string `json:"discard_time,omitempty"`
|
||||||
|
Account string `json:"account,omitempty"` // 可兑换账号
|
||||||
|
}
|
||||||
|
type Notify struct {
|
||||||
|
AppId string `json:"app_id" validate:"required"`
|
||||||
|
SignType string `json:"sign_type" validate:"required"`
|
||||||
|
Timestamp string `json:"timestamp" validate:"required"`
|
||||||
|
Sign string `json:"sign" validate:"required"`
|
||||||
|
Data NotifyData `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NotifyData) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(d); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NotifyData) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Notify) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(d); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Notify) String() string {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Notify) SignString() string {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a.Data)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.AppId + a.Timestamp + string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
OutBizNo string `json:"out_biz_no"`
|
||||||
|
TradeNo string `json:"trade_no"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
UsableNum uint32 `json:"usable_num"`
|
||||||
|
UsageNum uint32 `json:"usage_num"`
|
||||||
|
Status Status `json:"status"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Amount float32 `json:"amount,omitempty"`
|
||||||
|
PayAmount float32 `json:"pay_amount,omitempty"`
|
||||||
|
PayTime string `json:"pay_time,omitempty"`
|
||||||
|
SettlementPrice float32 `json:"settlement_price,omitempty"`
|
||||||
|
ValidBeginTime string `json:"valid_begin_time,omitempty"`
|
||||||
|
ValidEndTime string `json:"valid_end_time,omitempty"`
|
||||||
|
UsageTime string `json:"usage_time,omitempty"`
|
||||||
|
DiscardTime string `json:"discard_time,omitempty"`
|
||||||
|
Account string `json:"account,omitempty"` // 可兑换账号
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertData(b []byte) (*Data, error) {
|
||||||
|
|
||||||
|
var data *Data
|
||||||
|
if err := json.Unmarshal(b, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package key
|
||||||
|
|
||||||
|
type Status uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Normal Status = iota + 1
|
||||||
|
Used
|
||||||
|
Discard
|
||||||
|
Expire
|
||||||
|
)
|
||||||
|
|
||||||
|
var statusMap = map[Status]string{
|
||||||
|
Normal: "正常",
|
||||||
|
Used: "已核销",
|
||||||
|
Discard: "已作废",
|
||||||
|
Expire: "已过期",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) Value() uint8 {
|
||||||
|
return uint8(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) GetText() string {
|
||||||
|
t, ok := statusMap[s]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) IsNormal() bool {
|
||||||
|
return s == Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) IsUsed() bool {
|
||||||
|
return s == Used
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) IsDiscard() bool {
|
||||||
|
return s == Discard
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) IsExpire() bool {
|
||||||
|
return s == Expire
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/api"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
orderMethod = "/openapi/v2/key/order"
|
||||||
|
queryMethod = "/openapi/v2/key/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key api.Service
|
||||||
|
|
||||||
|
func (k *Key) Order(ctx context.Context, request *OrderRequest) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
h, httpResponse, bodyBytes, err := k.Post(ctx, orderMethod, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Query(ctx context.Context, request *QueryRequest) (http.Header, *http.Response, *core.Response, error) {
|
||||||
|
|
||||||
|
h, httpResponse, bodyBytes, err := k.Post(ctx, queryMethod, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := core.BuildResponse(bodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return h, httpResponse, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, res, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/core"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appId = ""
|
||||||
|
privateKey = ""
|
||||||
|
publicKey = ""
|
||||||
|
key = ""
|
||||||
|
baseURL = ""
|
||||||
|
signType = core.SignRSA
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCore() (*core.Core, error) {
|
||||||
|
return core.NewCore(&core.Config{
|
||||||
|
AppID: appId,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
PublicKey: publicKey,
|
||||||
|
Key: key,
|
||||||
|
SignType: signType,
|
||||||
|
BaseURL: baseURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrder(t *testing.T) {
|
||||||
|
c, err := newCore()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := &Key{c}
|
||||||
|
_, r, err := a.Order(context.Background(), &OrderRequest{
|
||||||
|
OutBizNo: "b202412270z8q7r1f704",
|
||||||
|
ActivityNo: "2024070901134",
|
||||||
|
Number: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf("response=%+v", r)
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Errorf("获取key失败:%s", r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuery(t *testing.T) {
|
||||||
|
c, err := newCore()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a := &Key{c}
|
||||||
|
_, r, err := a.Query(context.Background(), &QueryRequest{
|
||||||
|
OutBizNo: "006",
|
||||||
|
TradeNo: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Logf("response=%+v", r)
|
||||||
|
if !r.IsSuccess() {
|
||||||
|
t.Errorf("查询失败:%s", r.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotify(t *testing.T) {
|
||||||
|
c, err := newCore()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &Notify{
|
||||||
|
AppId: "",
|
||||||
|
SignType: "",
|
||||||
|
Timestamp: "",
|
||||||
|
Sign: "",
|
||||||
|
Data: NotifyData{
|
||||||
|
Event: "",
|
||||||
|
NotifyId: "",
|
||||||
|
OutBizNo: "",
|
||||||
|
TradeNo: "",
|
||||||
|
ActivityNo: "",
|
||||||
|
Number: 0,
|
||||||
|
Status: 0,
|
||||||
|
KeyMapCiphertext: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
str, err := n.SignString()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sign, err := c.CryptographySuite.Signer.Sign(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Sign = sign
|
||||||
|
|
||||||
|
b := c.CryptographySuite.Verifier.Verify(str, sign)
|
||||||
|
if !b {
|
||||||
|
t.Error("验签失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMapDecode, err := c.CryptographySuite.Cipher.Decode(n.Data.KeyMapCiphertext)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
keyMap := make([]*KeyInfo, 0, n.Data.Number)
|
||||||
|
if err = json.Unmarshal([]byte(keyMapDecode), &keyMap); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderRequest struct {
|
||||||
|
OutBizNo string `validate:"required,alphanum,min=2,max=32" json:"out_biz_no"` // 同一商户应用下不可重复
|
||||||
|
ActivityNo string `validate:"required,min=2,max=32" json:"activity_no"`
|
||||||
|
Number int32 `validate:"required,min=1,max=10000" json:"number"`
|
||||||
|
NotifyUrl string `json:"notify_url,omitempty"` // 回调地址,为空则使用客户应用设置地址
|
||||||
|
Extra string `json:"extra,omitempty"` // 拓展参数,备用
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderResponse struct {
|
||||||
|
OutBizNo string `json:"out_biz_no"`
|
||||||
|
TradeNo string `json:"trade_no"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *OrderRequest) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OrderRequest) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(c); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryRequest struct {
|
||||||
|
OutBizNo string `json:"out_biz_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 同一商户应用下不可重复
|
||||||
|
TradeNo string `json:"trade_no,omitempty" validate:"omitempty,alphanum,min=2,max=32"` // out_biz_no/trade_no二选一 若不为空,则优先使用
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryResponse struct {
|
||||||
|
OutBizNo string `json:"out_biz_no"`
|
||||||
|
TradeNo string `json:"trade_no"`
|
||||||
|
Status Status `json:"status"`
|
||||||
|
Number int32 `json:"number"`
|
||||||
|
KeyMapCiphertext string `json:"ciphertext" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyInfo struct {
|
||||||
|
Key string `json:"key,omitempty"` // key码
|
||||||
|
Url string `json:"url,omitempty"` // 短链接
|
||||||
|
UsableNum uint32 `json:"usable_num"` // 可兑换次数
|
||||||
|
UsageNum uint32 `json:"usage_num"` // 已核销次数
|
||||||
|
Status KeyStatus `json:"status"` // 状态
|
||||||
|
BeginTime string `json:"begin_time"` // 开始时间
|
||||||
|
EndTime string `json:"end_time"` // 结束时间
|
||||||
|
Amount float32 `json:"amount,omitempty"` // 金额
|
||||||
|
PayAmount float32 `json:"pay_amount,omitempty"` // 支付金额
|
||||||
|
PayTime string `json:"pay_time,omitempty"` // 支付时间
|
||||||
|
SettlementPrice float32 `json:"settlement_price,omitempty"` // 结算价
|
||||||
|
UsageTime string `json:"usage_time,omitempty"` // 最后一次核销时间
|
||||||
|
DiscardTime string `json:"discard_time,omitempty"` // 作废时间
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *QueryRequest) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *QueryRequest) Validate() error {
|
||||||
|
|
||||||
|
if q.OutBizNo == "" && q.TradeNo == "" {
|
||||||
|
return fmt.Errorf("参数错误,out_biz_no/trade_no 二选一")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validator.New().Struct(q); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notify struct {
|
||||||
|
AppId string `json:"app_id" validate:"required"`
|
||||||
|
SignType string `json:"sign_type" validate:"required"`
|
||||||
|
Timestamp string `json:"timestamp" validate:"required"`
|
||||||
|
Sign string `json:"sign" validate:"required"`
|
||||||
|
Data NotifyData `json:"data" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyData struct {
|
||||||
|
Event NotifyEvent `json:"event" validate:"required"`
|
||||||
|
NotifyId string `json:"notify_id" validate:"required"`
|
||||||
|
OutBizNo string `json:"out_biz_no" validate:"required"`
|
||||||
|
TradeNo string `json:"trade_no" validate:"required"`
|
||||||
|
ActivityNo string `json:"activity_no" validate:"required"`
|
||||||
|
Number int32 `json:"number" validate:"required"`
|
||||||
|
Status Status `json:"status" validate:"required"`
|
||||||
|
KeyMapCiphertext string `json:"ciphertext" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Notify) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(d); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Notify) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NotifyData) Validate() error {
|
||||||
|
|
||||||
|
if err := validator.New().Struct(d); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return fmt.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NotifyData) String() (string, error) {
|
||||||
|
|
||||||
|
b, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Notify) SignString() (string, error) {
|
||||||
|
|
||||||
|
b, err := a.Data.String()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.AppId + a.Timestamp + b, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
const SuccessCode = 200
|
||||||
|
|
||||||
|
type Status uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusIng Status = iota + 1
|
||||||
|
StatusSuccess
|
||||||
|
StatusFailed
|
||||||
|
)
|
||||||
|
|
||||||
|
var statusMap = map[Status]string{
|
||||||
|
StatusIng: "生成中",
|
||||||
|
StatusSuccess: "生成完成",
|
||||||
|
StatusFailed: "生成失败",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) Value() uint8 {
|
||||||
|
return uint8(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) GetText() string {
|
||||||
|
t, ok := statusMap[s]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyEvent string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotifyEventKeyCreate NotifyEvent = "key.create"
|
||||||
|
NotifyEventKeyUsage NotifyEvent = "key.usage"
|
||||||
|
NotifyEventKeyDiscard NotifyEvent = "key.discard"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s NotifyEvent) Value() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NotifyEvent) IsKeyCreate() bool {
|
||||||
|
return NotifyEventKeyCreate == s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NotifyEvent) IsKeyUsage() bool {
|
||||||
|
return NotifyEventKeyUsage == s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NotifyEvent) IsKeyDiscard() bool {
|
||||||
|
return NotifyEventKeyDiscard == s
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyStatus uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyNormal KeyStatus = iota + 1
|
||||||
|
KeyUsed
|
||||||
|
KeyDiscard
|
||||||
|
KeyExpire
|
||||||
|
)
|
||||||
|
|
||||||
|
var keyStatusMap = map[KeyStatus]string{
|
||||||
|
KeyNormal: "正常",
|
||||||
|
KeyUsed: "已核销",
|
||||||
|
KeyDiscard: "已作废",
|
||||||
|
KeyExpire: "已过期",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) Value() uint8 {
|
||||||
|
return uint8(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) GetText() string {
|
||||||
|
t, ok := keyStatusMap[s]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) IsNormal() bool {
|
||||||
|
return s == KeyNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) IsUsed() bool {
|
||||||
|
return s == KeyUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) IsDiscard() bool {
|
||||||
|
return s == KeyDiscard
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeyStatus) IsExpire() bool {
|
||||||
|
return s == KeyExpire
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
n := rsa.NewGenerateKey()
|
||||||
|
err := n.SavePem("../../pem")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
privateKeyStr, publicKeyStr := n.GetKey()
|
||||||
|
fmt.Println("privateKeyStr=", privateKeyStr)
|
||||||
|
fmt.Println("publicKeyStr=", publicKeyStr)
|
||||||
|
|
||||||
|
fmt.Println("aesKey=", rsa.GenerateAesKey())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
priK, pubK, err := sm.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("privateKeyStr=", priK)
|
||||||
|
fmt.Println("publicKeyStr=", pubK)
|
||||||
|
sm4key, err := sm.GenerateSM4Key()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("sm4key=", sm4key)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SignType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SignRSA SignType = "RSA"
|
||||||
|
SignSM SignType = "SM"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config merchant app Config
|
||||||
|
type Config struct {
|
||||||
|
AppID string `validate:"required"`
|
||||||
|
PrivateKey string `validate:"required"`
|
||||||
|
PublicKey string `validate:"required"`
|
||||||
|
Key string `validate:"required"`
|
||||||
|
SignType SignType `validate:"required"`
|
||||||
|
BaseURL string `validate:"required,url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if err := validator.New().Struct(c); err != nil {
|
||||||
|
for _, err = range err.(validator.ValidationErrors) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SignType) IsRSA() bool {
|
||||||
|
return s == SignRSA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SignType) IsSM() bool {
|
||||||
|
return s == SignSM
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) CryptographySuite() (*CryptographySuite, error) {
|
||||||
|
if c.SignType.IsRSA() {
|
||||||
|
return c.CryptographySuiteRSA()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SignType.IsSM() {
|
||||||
|
return c.CryptographySuiteSM()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("[%s] invalid sign type", c.SignType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) CryptographySuiteRSA() (*CryptographySuite, error) {
|
||||||
|
prk, err := rsa.PrivateKeyRSA(c.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
puk, err := rsa.PublicKeyRSA(c.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CryptographySuite{
|
||||||
|
Signer: &RsaSigner{privateKey: prk},
|
||||||
|
Verifier: &RsaVerifier{publicKey: puk},
|
||||||
|
Cipher: &RsaEncodeDecode{key: c.Key},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) CryptographySuiteSM() (*CryptographySuite, error) {
|
||||||
|
prk, err := sm.PrivateKeySM(c.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
puk, err := sm.PublicKeySM(c.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CryptographySuite{
|
||||||
|
Signer: &SmSigner{privateKey: prk},
|
||||||
|
Verifier: &SmVerifier{publicKey: puk},
|
||||||
|
Cipher: &SmEncodeDecode{key: c.Key},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Params request params
|
||||||
|
type Params struct {
|
||||||
|
// AppId app id
|
||||||
|
AppId string `json:"app_id"`
|
||||||
|
// SignType sign type
|
||||||
|
SignType SignType `json:"sign_type"`
|
||||||
|
// Timestamp 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
// Sign
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
// Ciphertext
|
||||||
|
Ciphertext string `json:"ciphertext"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Core structure
|
||||||
|
type Core struct {
|
||||||
|
// HttpClient http client
|
||||||
|
HttpClient *http.Client
|
||||||
|
// Config config
|
||||||
|
Config *Config
|
||||||
|
// CryptographySuite
|
||||||
|
CryptographySuite *CryptographySuite
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*Core)
|
||||||
|
|
||||||
|
// WithHttpClient sets the http client
|
||||||
|
func WithHttpClient(client *http.Client) Option {
|
||||||
|
return func(s *Core) {
|
||||||
|
s.HttpClient = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCore creates a new Core instance
|
||||||
|
func NewCore(c *Config, o ...Option) (*Core, error) {
|
||||||
|
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
core := &Core{
|
||||||
|
HttpClient: http.DefaultClient,
|
||||||
|
Config: c,
|
||||||
|
}
|
||||||
|
for _, f := range o {
|
||||||
|
f(core)
|
||||||
|
}
|
||||||
|
|
||||||
|
crs, err := c.CryptographySuite()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
core.CryptographySuite = crs
|
||||||
|
|
||||||
|
return core, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBizSignStr gets the biz sign str Go 版本是 1.15 或更高版本
|
||||||
|
func (c *Core) GetBizSignStr(request any) (plaintext string, err error) {
|
||||||
|
|
||||||
|
kvs := utils.SortStruct(request)
|
||||||
|
|
||||||
|
kvm := make(map[string]any, len(kvs))
|
||||||
|
var order []string
|
||||||
|
|
||||||
|
for _, kv := range kvs {
|
||||||
|
kvm[kv.Key] = kv.Value
|
||||||
|
order = append(order, kv.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedMap := make(map[string]any)
|
||||||
|
for _, key := range order {
|
||||||
|
orderedMap[key] = kvm[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将 orderedMap 转换成 JSON 字符串,保持顺序
|
||||||
|
kvmBytes, err := json.Marshal(orderedMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(kvmBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCiphertext gets the ciphertext
|
||||||
|
func (c *Core) GetCiphertext(request any) (string, error) {
|
||||||
|
|
||||||
|
plaintext, err := c.GetBizSignStr(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := c.CryptographySuite.Cipher.Encode(plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildParams gets the params
|
||||||
|
func (c *Core) BuildParams(request Request) (*Params, error) {
|
||||||
|
|
||||||
|
if err := request.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := c.GetCiphertext(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamps := time.Now().Format(time.DateTime)
|
||||||
|
dataToSign := c.Config.AppID + timestamps + ciphertext
|
||||||
|
|
||||||
|
signature, err := c.CryptographySuite.Signer.Sign(dataToSign)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Params{
|
||||||
|
AppId: c.Config.AppID,
|
||||||
|
SignType: c.Config.SignType,
|
||||||
|
Timestamp: timestamps,
|
||||||
|
Sign: signature,
|
||||||
|
Ciphertext: ciphertext,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildAnyApiParams gets the params
|
||||||
|
func (c *Core) BuildAnyApiParams(bizContent any) (*Params, error) {
|
||||||
|
|
||||||
|
ciphertext, err := c.GetCiphertext(bizContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamps := time.Now().Format(time.DateTime)
|
||||||
|
dataToSign := c.Config.AppID + timestamps + ciphertext
|
||||||
|
|
||||||
|
signature, err := c.CryptographySuite.Signer.Sign(dataToSign)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Params{
|
||||||
|
AppId: c.Config.AppID,
|
||||||
|
SignType: c.Config.SignType,
|
||||||
|
Timestamp: timestamps,
|
||||||
|
Sign: signature,
|
||||||
|
Ciphertext: ciphertext,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies the params
|
||||||
|
func (c *Core) Verify(timestamp, ciphertext, sign string) bool {
|
||||||
|
dataToSign := c.Config.AppID + timestamp + ciphertext
|
||||||
|
return c.CryptographySuite.Verifier.Verify(dataToSign, sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) GetHeaders(p *Params) http.Header {
|
||||||
|
|
||||||
|
h := http.Header{}
|
||||||
|
|
||||||
|
h.Set("Content-Type", "application/json")
|
||||||
|
h.Set("Version", "1.0")
|
||||||
|
h.Set("Appid", c.Config.AppID)
|
||||||
|
h.Set("Sign-Type", string(c.Config.SignType))
|
||||||
|
|
||||||
|
h.Set("Timestamp", p.Timestamp)
|
||||||
|
h.Set("Sign", p.Sign)
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestBody gets the request body
|
||||||
|
func (c *Core) GetRequestBody(_ context.Context, request Request) (http.Header, []byte, error) {
|
||||||
|
|
||||||
|
p, err := c.BuildParams(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := c.GetHeaders(p)
|
||||||
|
|
||||||
|
reqBodyBytes, err := json.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, reqBodyBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post sends the request and Analysis the response
|
||||||
|
func (c *Core) Post(ctx context.Context, method string, request Request) (http.Header, *http.Response, []byte, error) {
|
||||||
|
|
||||||
|
h, reqBodyBytes, err := c.GetRequestBody(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
httpResponse, body, err := c.Request(ctx, h, http.MethodPost, c.Config.BaseURL+method, reqBodyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, httpResponse, body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request sends the request and Analysis the response
|
||||||
|
func (c *Core) Request(ctx context.Context, h http.Header, method, url string, body []byte) (*http.Response, []byte, error) {
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header = h
|
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("sending HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, nil, fmt.Errorf("HTTP status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, bodyBytes, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRSASignVerify(t *testing.T) {
|
||||||
|
c := Config{
|
||||||
|
AppID: "",
|
||||||
|
PrivateKey: "",
|
||||||
|
PublicKey: "",
|
||||||
|
Key: "",
|
||||||
|
SignType: SignRSA,
|
||||||
|
BaseURL: "http://127.0.0.1:9000",
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: time.Second * 10,
|
||||||
|
}
|
||||||
|
core, err := NewCore(&c, WithHttpClient(httpClient))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signStr := "123456{}测试"
|
||||||
|
signature, err := core.CryptographySuite.Signer.Sign(signStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := core.CryptographySuite.Verifier.Verify(signStr, signature)
|
||||||
|
if !b {
|
||||||
|
t.Error("验签失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSMSignVerify(t *testing.T) {
|
||||||
|
c := Config{
|
||||||
|
AppID: "123456",
|
||||||
|
PrivateKey: "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc=",
|
||||||
|
PublicKey: "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM=",
|
||||||
|
Key: "t+VxHnp+K9huhtNT84Pk7A==",
|
||||||
|
SignType: SignSM,
|
||||||
|
BaseURL: "http://127.0.0.1:9000",
|
||||||
|
}
|
||||||
|
core, err := NewCore(&c)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signStr := "123456{}测试"
|
||||||
|
signature, err := core.CryptographySuite.Signer.Sign(signStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := core.CryptographySuite.Verifier.Verify(signStr, signature)
|
||||||
|
if !b {
|
||||||
|
t.Error("验签失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
// Signer interfaces for signing data
|
||||||
|
type Signer interface {
|
||||||
|
Sign(data string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier interfaces for verifying signatures
|
||||||
|
type Verifier interface {
|
||||||
|
Verify(data, signature string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cipher interfaces for Encode or Decode request
|
||||||
|
type Cipher interface {
|
||||||
|
Encode(plaintext string) (string, error)
|
||||||
|
Decode(ciphertext string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptographySuite .
|
||||||
|
type CryptographySuite struct {
|
||||||
|
Signer Signer
|
||||||
|
Verifier Verifier
|
||||||
|
Cipher Cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request interfaces for request
|
||||||
|
type Request interface {
|
||||||
|
String() (string, error)
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SuccessCode = 200
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int32 `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildResponse(b []byte) (*Response, error) {
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
if err := json.Unmarshal(b, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Response) IsSuccess() bool {
|
||||||
|
return a.Code == SuccessCode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
sdkrsa "github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/rsa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RsaSigner for RSA signing
|
||||||
|
type RsaSigner struct {
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// RsaVerifier for RSA verification
|
||||||
|
type RsaVerifier struct {
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// RsaEncodeDecode .
|
||||||
|
type RsaEncodeDecode struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RsaSigner) Sign(data string) (string, error) {
|
||||||
|
return sdkrsa.Sign(data, r.privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RsaVerifier) Verify(data, signature string) bool {
|
||||||
|
b, err := sdkrsa.Verify(data, signature, r.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RsaEncodeDecode) Encode(plaintext string) (string, error) {
|
||||||
|
return sdkrsa.Encode(r.key, plaintext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RsaEncodeDecode) Decode(data string) (string, error) {
|
||||||
|
return sdkrsa.Decode(r.key, data), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sleepinggodoflove/lansexiongdi-marketing-sdk/utils/sm"
|
||||||
|
"github.com/tjfoc/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SmSigner for SM signing (国密)
|
||||||
|
type SmSigner struct {
|
||||||
|
privateKey *sm2.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmVerifier for SM verification (国密)
|
||||||
|
type SmVerifier struct {
|
||||||
|
publicKey *sm2.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// SmEncodeDecode .
|
||||||
|
type SmEncodeDecode struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmSigner) Sign(data string) (string, error) {
|
||||||
|
return sm.Sign(data, s.privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmVerifier) Verify(data, signature string) bool {
|
||||||
|
b, err := sm.Verify(data, signature, s.publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmEncodeDecode) Encode(plaintext string) (string, error) {
|
||||||
|
return sm.Encode(s.key, []byte(plaintext))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmEncodeDecode) Decode(ciphertext string) (string, error) {
|
||||||
|
return sm.Decode(s.key, ciphertext)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
module github.com/sleepinggodoflove/lansexiongdi-marketing-sdk
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-playground/validator/v10 v10.22.1
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/tjfoc/gmsm v1.4.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||||
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateAesKey() string {
|
||||||
|
key := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(key); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode 加密函数
|
||||||
|
func Encode(key, plaintext string) string {
|
||||||
|
// 创建AES加密器
|
||||||
|
block, err := aes.NewCipher([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PKCS7填充
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
padding := blockSize - len(plaintext)%blockSize
|
||||||
|
padText := string(byte(padding))
|
||||||
|
for i := 0; i < padding; i++ {
|
||||||
|
plaintext += padText
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建ECB模式加密器
|
||||||
|
mode := NewECEncrypted(block)
|
||||||
|
|
||||||
|
// 计算加密后数据的长度
|
||||||
|
encrypted := make([]byte, len(plaintext))
|
||||||
|
mode.CryptBlocks(encrypted, []byte(plaintext))
|
||||||
|
|
||||||
|
// Base64编码
|
||||||
|
return base64.StdEncoding.EncodeToString(encrypted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode 解密函数
|
||||||
|
func Decode(key, code string) string {
|
||||||
|
// Base64解码
|
||||||
|
encryptString, err := base64.StdEncoding.DecodeString(code)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建AES解密器
|
||||||
|
block, err := aes.NewCipher([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建ECB模式解密器
|
||||||
|
mode := NewECDecrypted(block)
|
||||||
|
|
||||||
|
// 解密
|
||||||
|
decrypted := make([]byte, len(encryptString))
|
||||||
|
mode.CryptBlocks(decrypted, encryptString)
|
||||||
|
|
||||||
|
// 去除填充
|
||||||
|
padding := decrypted[len(decrypted)-1]
|
||||||
|
return string(decrypted[:len(decrypted)-int(padding)])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ecEncrypted ECB加密器
|
||||||
|
type ecEncrypted struct {
|
||||||
|
b cipher.Block
|
||||||
|
blockSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECEncrypted(b cipher.Block) cipher.BlockMode {
|
||||||
|
return &ecEncrypted{b, b.BlockSize()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ecEncrypted) BlockSize() int { return x.blockSize }
|
||||||
|
|
||||||
|
func (x *ecEncrypted) CryptBlocks(dst, src []byte) {
|
||||||
|
if len(src)%x.blockSize != 0 {
|
||||||
|
panic("src not full blocks")
|
||||||
|
}
|
||||||
|
if len(dst) < len(src) {
|
||||||
|
panic("output smaller than input")
|
||||||
|
}
|
||||||
|
for len(src) > 0 {
|
||||||
|
x.b.Encrypt(dst, src[:x.blockSize])
|
||||||
|
src = src[x.blockSize:]
|
||||||
|
dst = dst[x.blockSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECB解密器
|
||||||
|
type ecDecrypted struct {
|
||||||
|
b cipher.Block
|
||||||
|
blockSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewECDecrypted(b cipher.Block) cipher.BlockMode {
|
||||||
|
return &ecDecrypted{b, b.BlockSize()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ecDecrypted) BlockSize() int { return x.blockSize }
|
||||||
|
|
||||||
|
func (x *ecDecrypted) CryptBlocks(dst, src []byte) {
|
||||||
|
if len(src)%x.blockSize != 0 {
|
||||||
|
panic("src not full blocks")
|
||||||
|
}
|
||||||
|
if len(dst) < len(src) {
|
||||||
|
panic("output smaller than input")
|
||||||
|
}
|
||||||
|
for len(src) > 0 {
|
||||||
|
x.b.Decrypt(dst, src[:x.blockSize])
|
||||||
|
src = src[x.blockSize:]
|
||||||
|
dst = dst[x.blockSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGenerateAesKey(t *testing.T) {
|
||||||
|
s := GenerateAesKey()
|
||||||
|
t.Log(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncode(t *testing.T) {
|
||||||
|
key := "870abfc720f86ce2c5e4d3345741d48d"
|
||||||
|
str := "123yie一二三"
|
||||||
|
e := Encode(key, str)
|
||||||
|
t.Log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecode(t *testing.T) {
|
||||||
|
key := "870abfc720f86ce2c5e4d3345741d48d"
|
||||||
|
e := "xxxx"
|
||||||
|
d := Decode(key, e)
|
||||||
|
t.Log(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecode(t *testing.T) {
|
||||||
|
key := "870abfc720f86ce2c5e4d3345741d48d"
|
||||||
|
str := "123yie一二三"
|
||||||
|
e := Encode(key, str)
|
||||||
|
t.Log(e)
|
||||||
|
d := Decode(key, e)
|
||||||
|
t.Log(d)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Cipher(publicKey *rsa.PublicKey, plaintext []byte) (string, error) {
|
||||||
|
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error encrypting data: %v", err)
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Plain(privateKey *rsa.PrivateKey, ciphertext string) (string, error) {
|
||||||
|
ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error decoding base64: %v", err)
|
||||||
|
}
|
||||||
|
decryptedData, err := privateKey.Decrypt(nil, ciphertextBytes, &rsa.OAEPOptions{Hash: crypto.SHA256})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error decrypting data: %v", err)
|
||||||
|
}
|
||||||
|
return string(decryptedData), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCipher(t *testing.T) {
|
||||||
|
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
|
||||||
|
pukRsa, err := PublicKeyRSA(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
plaintext := []byte("123一二三{\"key\":\"value\"}")
|
||||||
|
cipherStr, err := Cipher(pukRsa, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(cipherStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlain(t *testing.T) {
|
||||||
|
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
|
||||||
|
pukRsa, err := PublicKeyRSA(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
plaintext := []byte("123一二三{\"key\":\"value\"}")
|
||||||
|
cipherStr, err := Cipher(pukRsa, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(cipherStr)
|
||||||
|
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
|
||||||
|
prkRsa, err := PrivateKeyRSA(rsaPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
plainStr, err := Plain(prkRsa, cipherStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(plainStr)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Generate struct {
|
||||||
|
publicKeyBytes []byte
|
||||||
|
privateKeyBytes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerateKey() *Generate {
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return &Generate{
|
||||||
|
publicKeyBytes: x509.MarshalPKCS1PublicKey(&privateKey.PublicKey),
|
||||||
|
privateKeyBytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generate) SavePem(path string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
if err = os.MkdirAll(path, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyFile, err := os.Create(path + "/private.pem")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer privateKeyFile.Close()
|
||||||
|
|
||||||
|
if err = pem.Encode(privateKeyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: g.privateKeyBytes}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyFile, err := os.Create(path + "/public.pem")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer publicKeyFile.Close()
|
||||||
|
|
||||||
|
return pem.Encode(publicKeyFile, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: g.publicKeyBytes})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Generate) GetKey() (privateKeyStr, publicKeyStr string) {
|
||||||
|
privateKeyStr = base64.StdEncoding.EncodeToString(g.privateKeyBytes)
|
||||||
|
publicKeyStr = base64.StdEncoding.EncodeToString(g.publicKeyBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
n := NewGenerateKey()
|
||||||
|
err := n.SavePem("../../pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privateKeyStr, publicKeyStr := n.GetKey()
|
||||||
|
t.Log("privateKeyStr=", privateKeyStr)
|
||||||
|
t.Log("publicKeyStr=", publicKeyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSavePem(t *testing.T) {
|
||||||
|
n := NewGenerateKey()
|
||||||
|
err := n.SavePem("../../pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateGetKey(t *testing.T) {
|
||||||
|
n := NewGenerateKey()
|
||||||
|
privateKeyStr, publicKeyStr := n.GetKey()
|
||||||
|
t.Log("privateKeyStr=", privateKeyStr)
|
||||||
|
t.Log("publicKeyStr=", publicKeyStr)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrivateKeyRSA(privateKeyStr string) (*rsa.PrivateKey, error) {
|
||||||
|
privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解码 Base64 编码的 RSA 私钥字符串: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析 RSA 私钥: %v", err)
|
||||||
|
}
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyPem(privateKeyStr string) (string, error) {
|
||||||
|
privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("解码 Base64 编码的 RSA 私钥字符串: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("解析 RSA 私钥: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKeyPEM := &pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pem.EncodeToMemory(privateKeyPEM)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyRSA(publicKeyStr string) (*rsa.PublicKey, error) {
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解码 Base64 编码的 RSA 公钥字符串: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := x509.ParsePKCS1PublicKey(publicKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("解析 RSA 公钥: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyPem(publicKeyStr string) (string, error) {
|
||||||
|
publicKeyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("解码 Base64 编码的 RSA 公钥字符串: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := x509.ParsePKCS1PublicKey(publicKeyBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("解析 RSA 公钥:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyPEM := &pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PublicKey(publicKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pem.EncodeToMemory(publicKeyPEM)), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPrivateKeyPem(t *testing.T) {
|
||||||
|
privateKeyStr := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
|
||||||
|
prkPem, err := PrivateKeyPem(privateKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(prkPem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyPem(t *testing.T) {
|
||||||
|
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
|
||||||
|
pukPem, err := PublicKeyPem(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(pukPem)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Sign(data string, privateKey *rsa.PrivateKey) (string, error) {
|
||||||
|
hashed := sha256.Sum256([]byte(data))
|
||||||
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(signature), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify(data string, signature string, publicKey *rsa.PublicKey) (bool, error) {
|
||||||
|
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
hashed := sha256.Sum256([]byte(data))
|
||||||
|
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], signatureBytes)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Sign(t *testing.T) {
|
||||||
|
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
|
||||||
|
signStr := "123一二三{\"key\":\"value\"}"
|
||||||
|
prkRsa, err := PrivateKeyRSA(rsaPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got, err := Sign(signStr, prkRsa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign() error = %v", err)
|
||||||
|
}
|
||||||
|
t.Log(got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Verify(t *testing.T) {
|
||||||
|
signStr := "123一二三{\"key\":\"value\"}"
|
||||||
|
signature := "0vQLV1LKzcExH4Nc7U37OwOoq+3o2Sdz8dfV9mME99L9YiaM5LN9dv9CaxQI4NSTxDTbJ9iTrScHE5TyquKRcyeJCW1qBEYySoYhiAdF9TxhVcyXYgbz4oMfjTF0J0C78hKZUb+mZeoeq7hgUsOhMQwmbumjoxKM6Y/rsZHfJQSwyty7Z4jc6BkHr8IZzPUvDlQkmwcnk4EWwx0au47fKVGxdW8dD2Gf0vstYiDN0MSy7BZnWh1/RY0g3EnjmmO7NvSJFxdlOqLwA9HR2ch2Fot/dxot2nSPK2+pj8k1+vZ1+ga/Ee2hmrRG5gSVmesBiI79NVzgPsrGLAscupz2Mg=="
|
||||||
|
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
|
||||||
|
pukRsa, err := PublicKeyRSA(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got, err := Verify(signStr, signature, pukRsa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !got {
|
||||||
|
t.Fatal("Verify() error = false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_SignVerify(t *testing.T) {
|
||||||
|
rsaPrivateKey := "MIIEpQIBAAKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQABAoIBAQCQkaXi3y8YWrdWvCwkUN0/fWkJmLtExn2Dmpu/wsEf9iQurVvo1SheY9JG+fUQa7bsAQuXRntNF/GgpsGsT+HXezwckog4Q7gSk066LZY8IKZUsKXXkeH4H5hbKUFsrcGIf4n+GoCKNglGmPUkjsq68kVmEn8Y1FF6rpU5n2P40xEKAieKxlM2JNwR22DQYRw3iw4PcMAD88nKx9OBUwGig8MQnUka7OCZk9fNLdwBT0VfgCzRdvyBCDieif4vB7TnMmvYlr6wWOMi2Ad9ccY2wTlVOUyHoC6BZ72FgOYyfnAmEZbDChCNTEDNNj0m056slCTMO+nIVUqMip4imgiBAoGBAOWI84+4dRA+gm6xynXsp9TAto43/DmbohHrUWRE2tSGqPevBz2i0c5AOaNUdxtzoEWOj260Zdzf3gRhv/iH3Mgp2+R2+cJ1QYoaX/2auJ50dPJf1SHZrKVIYhYqqlIWc8jQo8XBK/Ys/Lf+N2We2EsMLMtUUMH9OZ+20XtlxQBZAoGBAPAYGVOzJnqFuSqxFzAA7VXP6p9WKxGEzbDUCxFaLj751DnvogI8FczAE6ADBxNYVkeQYtvqzMb9nTollOL6+/T9MUJn3DXTT8/St+REVdWvmO9Az9nGVDkLGjz0CH0iMjN49iSMFsdmo6L2528kUOj20dPh5IkzyWykYqni+fwlAoGBAMa1A51U40rXwpTPp2TlJfnBh4ihIOJCQFDg9YonLYY0uUwKousR7C1wXjVuJtqGA6aTnsoIs/I9f3ctpEIkY9aInksvUFKurbk/0f+7FL5gNOmqWtk+Fv7TJc7oyp/bvgqHzG+jJkqscW9bTVvU4ow9kv3HFU6KyHriioEX/i6pAoGBAJs5yW4e3lrKh/u9ANPNVaRsRzF64V9zMBUKEpnGZy3aEcbfUiwFssZszINgUbvFGgsso22xcXGZ2IQWdhsFz84FwEpBodK+6tPfVXrkX2ZHICZXDcqrehpjPjR4ReC5MiGrK+BXHgcPKe6bmOd3YEQuB1zop/u4mpp98TgLAjptAoGAF1lgcmwnHDduvWErwSmMR3w45FppV0pOdSEmv/6DU6iglzleSuuCm4fhXAEgSahJMKiflNx1ufl84IGir0r/we8GNfs4ceSPlyKDYnaBG19f5jWLLNFTWfwVhZqa85pd9b2dcqVWMazHq8psmyt0opmctRcM9s4lNq2OmvB7vM4="
|
||||||
|
signStr := "123一二三{\"key\":\"value\"}"
|
||||||
|
prkPem, err := PrivateKeyRSA(rsaPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
signature, err := Sign(signStr, prkPem)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign() error = %v", err)
|
||||||
|
}
|
||||||
|
t.Log(signature)
|
||||||
|
|
||||||
|
publicKeyStr := "MIIBCgKCAQEA10X/4v44TU0sPOKhnHLx7Qe0SREVIne9+3DEtTCbzrewXSrlHedMT6RVlwv1i0D6q5zWGV9WXMI4ZrM2EM6wE4bZfjZU5FvzYTQN8fjiBQTggU8BDFpUPGibwt2Fyv6SAnj0AQzD7itvcxydu0tDOgg9Aaa701kxhz9yZukpZGVhAEBussRc41EuEwE2gQJ5prSFijzhqg5awwAJESX9gDHX3DncLO2s3FuaaReJ1U/c/LsJIvRGPckCllSJM1s9WAvjz5yFXZMpvWpmAnmpxd5fNd349Gr/swfM50TN5D3cTJblO771FifNFm6r+4o1nkR/thWX5vMasvKwimGo3QIDAQAB"
|
||||||
|
pukRsa, err := PublicKeyRSA(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got, err := Verify(signStr, signature, pukRsa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !got {
|
||||||
|
t.Fatal("Verify() error = false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/tjfoc/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cipher 使用公钥加密数据
|
||||||
|
func Cipher(publicKey *sm2.PublicKey, plaintext []byte) (string, error) {
|
||||||
|
ciphertext, err := sm2.Encrypt(publicKey, plaintext, rand.Reader, sm2.C1C2C3)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plain 使用私钥解密数据
|
||||||
|
func Plain(privateKey *sm2.PrivateKey, ciphertext string) (string, error) {
|
||||||
|
ciphertextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
decryptedText, err := sm2.Decrypt(privateKey, ciphertextBytes, sm2.C1C2C3)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(decryptedText), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCipher(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlain(t *testing.T) {
|
||||||
|
data := "123456{}测试"
|
||||||
|
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
|
||||||
|
pukStr := "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM="
|
||||||
|
prk, err := PrivateKeySM(prkStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
puk, err := PublicKeySM(pukStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cs, err := Cipher(puk, []byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(cs)
|
||||||
|
ps, err := Plain(prk, cs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ps)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tjfoc/gmsm/sm2"
|
||||||
|
"github.com/tjfoc/gmsm/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateKey 生成公钥和私钥
|
||||||
|
func GenerateKey() (pri, puk string, err error) {
|
||||||
|
privateKey, err := sm2.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return pri, puk, fmt.Errorf("秘钥生成失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
priKeyBytes, err := hex.DecodeString(x509.WritePrivateKeyToHex(privateKey))
|
||||||
|
pri = base64.StdEncoding.EncodeToString(priKeyBytes)
|
||||||
|
|
||||||
|
publicKeyBytes, err := hex.DecodeString(x509.WritePublicKeyToHex(&privateKey.PublicKey))
|
||||||
|
puk = base64.StdEncoding.EncodeToString(publicKeyBytes)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPukByPrK 根据私钥得到公钥
|
||||||
|
func GetPukByPrK(prk string) (string, error) {
|
||||||
|
privateKeyBytes, err := base64.StdEncoding.DecodeString(prk)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("私钥base64解码失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := x509.ReadPrivateKeyFromHex(hex.EncodeToString(privateKeyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("私钥hex解码失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeyBytes, err := hex.DecodeString(x509.WritePublicKeyToHex(&privateKey.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("公钥hex解码失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(publicKeyBytes), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_GenerateKey(t *testing.T) {
|
||||||
|
priK, pubK, err := GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("\n私钥=%s\n公钥=%s\n", priK, pubK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test_GetPukByPrK 根据私钥获取公钥
|
||||||
|
func Test_GetPukByPriK(t *testing.T) {
|
||||||
|
prk := "ZvMCTiG67qUyPN65fcgg+EHhy2W/fN+9ixBudcmfbuU="
|
||||||
|
puk, err := GetPukByPrK(prk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want := "BL6PopnKr/hhPkxgn700Li1hPGx2/J5y2dQ4BDPLKDXe1sS4JeIG8/W1B8AO7hBzi0bKTArti0E/HJJcR9WcH/I="
|
||||||
|
fmt.Printf("prk: %s \n", prk)
|
||||||
|
fmt.Printf("puk: %s \n", puk)
|
||||||
|
assert.Equal(t, puk, want)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tjfoc/gmsm/sm2"
|
||||||
|
"github.com/tjfoc/gmsm/x509"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrivateKeySM(privateKeyStr string) (*sm2.PrivateKey, error) {
|
||||||
|
keyBytes, err := base64.StdEncoding.DecodeString(privateKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("privateKey base64 decode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := x509.ReadPrivateKeyFromHex(hex.EncodeToString(keyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read private key from hex failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeySM(publicKeyStr string) (*sm2.PublicKey, error) {
|
||||||
|
keyBytes, err := base64.StdEncoding.DecodeString(publicKeyStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("publicKeyStr base64 decode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := x509.ReadPublicKeyFromHex(hex.EncodeToString(keyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read public key from hex failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tjfoc/gmsm/sm2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Sign(data string, privateKey *sm2.PrivateKey) (string, error) {
|
||||||
|
signatureBytes, err := privateKey.Sign(rand.Reader, []byte(data), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("sign failed: %v", err)
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(signatureBytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Verify(data string, signature string, publicKey *sm2.PublicKey) (bool, error) {
|
||||||
|
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("signature base64 decode failed: %v", err)
|
||||||
|
}
|
||||||
|
if !publicKey.Verify([]byte(data), signatureBytes) {
|
||||||
|
fmt.Println("Signature verification failed")
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Sign(t *testing.T) {
|
||||||
|
data := "123456{}测试"
|
||||||
|
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
|
||||||
|
prk, err := PrivateKeySM(prkStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
signature, err := Sign(data, prk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("signature=%s\n", signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Verify(t *testing.T) {
|
||||||
|
data := "123456{}测试"
|
||||||
|
prkStr := "zJRUcwPpKFf4nWiN9wqSO9gpGFx5BP4WviqnPsrhkpc="
|
||||||
|
pukStr := "BKbxGVVlJGWK/ScU0ebKSe4Jr4LvcBGgvt/HHBk+ODVCYnJYvvmX8cDNpf3TVYuRdz/RUH6UDgcoVpz02jXNfrM="
|
||||||
|
prk, err := PrivateKeySM(prkStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
puk, err := PublicKeySM(pukStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
signature, err := Sign(data, prk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("signature=%s\n", signature)
|
||||||
|
|
||||||
|
b, err := Verify(data, signature, puk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if assert.True(t, b) {
|
||||||
|
t.Logf("Test_sign 验签-成功 %t\n", b)
|
||||||
|
} else {
|
||||||
|
t.Errorf("Test_sign 验签-失败 %t\n", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/tjfoc/gmsm/sm4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateSM4Key() (string, error) {
|
||||||
|
key := make([]byte, sm4.BlockSize)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.EncodeToString(key), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Encode(key string, plaintextBytes []byte) (string, error) {
|
||||||
|
d, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cipherBlock, err := sm4.NewCipher(d)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSize := cipherBlock.BlockSize()
|
||||||
|
iv := make([]byte, blockSize)
|
||||||
|
for i := 0; i < blockSize; i++ {
|
||||||
|
iv[i] = 0
|
||||||
|
}
|
||||||
|
blockMode := cipher.NewCBCEncrypter(cipherBlock, iv)
|
||||||
|
|
||||||
|
padding := blockSize - len(plaintextBytes)%blockSize
|
||||||
|
for i := 0; i < padding; i++ {
|
||||||
|
plaintextBytes = append(plaintextBytes, byte(padding))
|
||||||
|
}
|
||||||
|
cipherText := make([]byte, len(plaintextBytes))
|
||||||
|
blockMode.CryptBlocks(cipherText, plaintextBytes)
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(cipherText), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(key, ciphertext string) (string, error) {
|
||||||
|
d, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherBlock, err := sm4.NewCipher(d)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSize := cipherBlock.BlockSize()
|
||||||
|
iv := make([]byte, blockSize)
|
||||||
|
for i := 0; i < blockSize; i++ {
|
||||||
|
iv[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherTextBytes, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText := make([]byte, len(cipherTextBytes))
|
||||||
|
blockMode := cipher.NewCBCDecrypter(cipherBlock, iv)
|
||||||
|
blockMode.CryptBlocks(plainText, cipherTextBytes)
|
||||||
|
|
||||||
|
plainTextLen := len(plainText)
|
||||||
|
padding := int(plainText[plainTextLen-1])
|
||||||
|
buff := plainText[:plainTextLen-padding]
|
||||||
|
|
||||||
|
return string(buff), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package sm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSM4GenerateKey(t *testing.T) {
|
||||||
|
key, err := GenerateSM4Key()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSM4(t *testing.T) {
|
||||||
|
key := "t+VxHnp+K9huhtNT84Pk7A=="
|
||||||
|
plaintextBytes := []byte("BZjU223ZBM7A8586Tm7P")
|
||||||
|
enc, err := Encode(key, plaintextBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log(enc)
|
||||||
|
dec, err := Decode(key, enc)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log(dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSM4KeyEncrypt(t *testing.T) {
|
||||||
|
key := "t+VxHnp+K9huhtNT84Pk7A=="
|
||||||
|
plaintextBytes := []byte("BZjU223ZBM7A8586Tm7P")
|
||||||
|
enc, err := Encode(key, plaintextBytes)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSM4KeyPassDecrypt(t *testing.T) {
|
||||||
|
key := "t+VxHnp+K9huhtNT84Pk7A=="
|
||||||
|
ciphertext := "NwANcXkjX79873jenLJRGhbEr39eYOwC5WQxZFXmLpw="
|
||||||
|
dec, err := Decode(key, ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Log(dec)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyValue struct {
|
||||||
|
Key string
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortStruct(data any) []KeyValue {
|
||||||
|
v := reflect.ValueOf(data).Elem()
|
||||||
|
t := v.Type()
|
||||||
|
var kv []KeyValue
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
field := v.Field(i)
|
||||||
|
key := ToSnakeCase(t.Field(i).Name)
|
||||||
|
if field.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kv = append(kv, KeyValue{Key: key, Value: field.Interface()})
|
||||||
|
}
|
||||||
|
sort.SliceStable(kv, func(i, j int) bool {
|
||||||
|
return kv[i].Key < kv[j].Key
|
||||||
|
})
|
||||||
|
return kv
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSnakeCase(s string) string {
|
||||||
|
var result []rune
|
||||||
|
for i, char := range s {
|
||||||
|
if unicode.IsUpper(char) {
|
||||||
|
if i > 0 {
|
||||||
|
result = append(result, '_')
|
||||||
|
}
|
||||||
|
result = append(result, unicode.ToLower(char))
|
||||||
|
} else {
|
||||||
|
result = append(result, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSortStruct(t *testing.T) {
|
||||||
|
data := &struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Filed1 string `json:"filed1"`
|
||||||
|
Filed2 string `json:"filed2"`
|
||||||
|
}{
|
||||||
|
Name: "test",
|
||||||
|
Filed1: "filed1",
|
||||||
|
Filed2: "filed2",
|
||||||
|
}
|
||||||
|
kv := SortStruct(data)
|
||||||
|
t.Log(kv)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue