加签接口
func Sign(content, privateKey string)(sign string, err error)
验签接口
func Verify(content, sign, pubKey string) (err error)
结构体、Map等转换为JSON字符串接口
// InterfaceToSortedJSONStr 结构体、Map 转 待加签的排序的json字符串
// json按照字典序排序,值为空或者为0的忽略,不序列化为json的忽略(tag中`json:"-"`),不参与加签的字段忽略(tag中`sign:"-"`)
func InterfaceToSortedJSONStr(i interface{}) (str string, err error)
package signature
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"reflect"
"strings"
)
// Sign 加签函数
type Sign func(content, privateKey string) (sign string, err error)
// Verify 验签函数
type Verify func(content, sign, pubKey string) (err error)
// NewSigner 初始化Signer,默认RSA2签名
func NewSigner(s Sign) *Signer {
if s == nil {
s = rsa2Sign
}
return &Signer{
S: s,
}
}
var ErrPemDecode = errors.New("pem.Decode failed") // pem解析失败
func rsa2Sign(content, privateKey string) (sign string, err error) {
// 1、将密钥解析成密钥实例
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
err = ErrPemDecode
return
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return
}
// 2、生成签名
hash := sha256.New()
_, err = hash.Write([]byte(content))
if err != nil {
return
}
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hash.Sum(nil))
if err != nil {
return
}
// 3、签名base64编码
sign = base64.StdEncoding.EncodeToString(signature)
return
}
// Signer
type Signer struct {
S Sign
}
// Sign 签名
func (s *Signer) Sign(content, privateKey string) (sign string, err error) {
return s.S(content, privateKey)
}
// NewVerifier 初始化Verifier,默认RSA2验签
func NewVerifier(v Verify) *Verifier {
if v == nil {
v = rsa2Verify
}
return &Verifier{
V: v,
}
}
func rsa2Verify(content, sign, pubKey string) (err error) {
// 1、签名base64解码
signature, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return
}
// 2、密钥解析成公钥实例
block, _ := pem.Decode([]byte(pubKey))
if block == nil {
err = ErrPemDecode
return
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return
}
hash := sha256.New()
_, err = hash.Write([]byte(content))
if err != nil {
return
}
// 3、验证签名
pub := key.(*rsa.PublicKey)
err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, hash.Sum(nil), signature)
return
}
// Verifier
type Verifier struct {
V Verify
}
// Verify 验签
func (s *Verifier) Verify(content, sign, pubKey string) (err error) {
return s.V(content, sign, pubKey)
}
const InvalidType = "invalid type=%v"
// InterfaceToSortedJSONStr 结构体、Map 转 待加签的排序的json字符串
// json按照字典序排序,值为空或者为0的忽略,不序列化为json的忽略(tag中`json:"-"`),不参与加签的字段忽略(tag中`sign:"-"`)
func InterfaceToSortedJSONStr(i interface{}) (str string, err error) {
// 1、数据提取,基础类型提取值,结构体、Map等转换为有序Map
if i == nil {
err = fmt.Errorf(InvalidType, i)
return
}
v, err := interfaceValExtract(i)
if err != nil {
return
}
// 2、字符串类型直接返回
if vStr, ok := v.(string); ok {
str = vStr
return
}
// 3、序列化为json
js, err := json.Marshal(v)
if err != nil {
return
}
str = string(js)
return
}
// interfaceValExtract 提取i的值,i为0值或空值时返回"",结构体、Map 转 key排序的Map[string]interface{}
func interfaceValExtract(i interface{}) (v interface{}, err error) {
// 1、构建默认返回值,反射获取i的类型与值
v = ""
typ := reflect.TypeOf(i)
val := reflect.ValueOf(i)
// 2、指针类型取出元素类型与值
if typ.Kind() == reflect.Ptr {
if val.IsNil() {
return
}
typ = typ.Elem()
val = val.Elem()
}
// 3、分类型处理
k := typ.Kind()
switch k {
case reflect.Bool:
v = val.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
// 忽略0值
if val.Int() == 0 {
return
}
v = val.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
// 忽略0值
if val.Uint() == 0 {
return
}
v = val.Uint()
case reflect.Float32, reflect.Float64:
if val.IsZero() {
return
}
v = val.Float()
case reflect.String:
v = val.String()
case reflect.Slice, reflect.Array:
if val.Len() == 0 {
return
}
v, err = sliceValExtract(val)
case reflect.Struct:
if val.IsZero() {
return
}
v, err = structValToSortedMap(typ, val)
case reflect.Map:
if val.Len() == 0 {
return
}
v, err = mapValToSortedMap(val)
// 其他类型不支持签名
default:
err = fmt.Errorf(InvalidType, k)
}
return
}
// structValToSortedMap 结构体转排序的json string,忽略空值和0值
func structValToSortedMap(typs reflect.Type, vals reflect.Value) (sc ToSignMap, err error) {
// 1、构建map
sc = make(ToSignMap)
// 2、反射遍历属性
num := vals.NumField()
for i := 0; i < num; i++ {
val := vals.Field(i)
typ := typs.Field(i)
// 判断是否为需要忽略的加签字段
if isSkippedSignField(typ.Tag) {
continue
}
// 判断属性是否可导出(私有属性不能导出)
if !val.CanInterface() {
continue
}
// 转换成排序类型
var v interface{}
v, err = interfaceValExtract(val.Interface())
if err != nil {
return
}
// 名称以结构体中的json标签名称为准
name := typ.Name
if jsonName := getJSONNameInTag(typ.Tag); jsonName != "" {
name = jsonName
}
sc[name] = v
}
// 3、元素排序、去掉空值
sc = sc.ToSortedNoZeroValue()
return
}
func isSkippedSignField(tag reflect.StructTag) bool {
// 1、忽略不序列化的字段
v, ok := tag.Lookup("json")
if ok && v == "-" {
return true
}
// 2、忽略不加签的字段
v, ok = tag.Lookup("sign")
return ok && v == "-"
}
func getJSONNameInTag(tag reflect.StructTag) string {
v, ok := tag.Lookup("json")
if ok {
return strings.Split(v, ",")[0]
}
return ""
}
// mapValToSortedMap map转排序的json string,忽略0值和空值
func mapValToSortedMap(vals reflect.Value) (sc ToSignMap, err error) {
// 1、构建map
sc = make(ToSignMap)
// 2、反射遍历属性
iter := vals.MapRange()
for iter.Next() {
// 处理key
key, er := interfaceValExtract(iter.Key().Interface())
if er != nil {
err = er
return
}
k := fmt.Sprintf("%v", key)
// 处理value
var val interface{}
val, err = interfaceValExtract(iter.Value().Interface())
if err != nil {
return
}
// 赋值
sc[k] = val
}
// 3、元素排序、去掉空值
sc = sc.ToSortedNoZeroValue()
return
}
// sliceValExtract 切片转忽略空值 或 配置了忽略签名 的切片
func sliceValExtract(vals reflect.Value) (s []interface{}, err error) {
// 1、反射遍历属性
num := vals.Len()
for i := 0; i < num; i++ {
// 类型判断
val := vals.Index(i)
k := val.Kind()
if isNotValidType(k) {
err = fmt.Errorf(InvalidType, k)
return
}
// 判断属性是否可导出(私有属性不能导出)
if !val.CanInterface() {
continue
}
// 取出值
v := val.Interface()
// 结构体/Map/切片类型进行值的提取
if k == reflect.Struct || k == reflect.Map || k == reflect.Slice || k == reflect.Array {
// 提取切片的元素
v, err = interfaceValExtract(val.Interface())
if err != nil {
return
}
}
s = append(s, v)
}
// 2、返回处理后的切片
return
}
func isNotValidType(k reflect.Kind) bool {
return k == reflect.Invalid || k == reflect.Complex64 || k == reflect.Complex128 ||
k == reflect.Chan || k == reflect.Func || k == reflect.UnsafePointer
}
package signature
import (
"testing"
"github.com/stretchr/testify/assert"
)
type ProtoTest struct {
ID int `protobuf:"varint,1,opt,name=offset,proto3" json:"id" sign:"-"`
Flag bool `protobuf:"varint,1,opt,name=offset,proto3" json:"flag"`
Dou float32 `protobuf:"varint,1,opt,name=offset,proto3" json:"dou"`
Str string `protobuf:"varint,1,opt,name=offset,proto3" json:"str"`
Val1 map[int]string `protobuf:"varint,1,opt,name=offset,proto3" json:"val1"`
Val2 map[string]string `protobuf:"varint,1,opt,name=offset,proto3" json:"val2"`
Val3 []map[string]InnerTest1 `protobuf:"varint,1,opt,name=offset,proto3" json:"val3"`
Val4 [][]map[string]InnerTest1 `protobuf:"varint,1,opt,name=offset,proto3" json:"val4"`
Arr []InnerTest1 `protobuf:"varint,1,opt,name=offset,proto3" json:"arr"`
Arr1 []int `protobuf:"varint,1,opt,name=offset,proto3" json:"arr1"`
Inner InnerTest1 `protobuf:"varint,1,opt,name=offset,proto3" json:"inner"`
Inner1 InnerTest1 `protobuf:"varint,1,opt,name=offset,proto3" json:"inner1"`
Flags []bool `protobuf:"varint,1,opt,name=offset,proto3" json:"flags"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
type InnerTest1 struct {
Val map[string]string `protobuf:"varint,1,opt,name=offset,proto3" json:"val" sign:"-"`
Inner *InnerTest2 `protobuf:"varint,1,opt,name=offset,proto3" json:"inner"`
}
type InnerTest2 struct {
ID int `protobuf:"varint,1,opt,name=offset,proto3" json:"id"`
}
var (
innerTest1 = InnerTest1{
Val: map[string]string{
"a": "a",
"b": "b",
},
Inner: &InnerTest2{
ID: 1,
},
}
pt = &ProtoTest{
ID: 1,
Val1: map[int]string{
1: "1",
2: "2",
},
Val2: map[string]string{
"a": "a",
"b": "b",
},
Val3: []map[string]InnerTest1{{"val3": innerTest1}},
Val4: [][]map[string]InnerTest1{{{"val4": innerTest1}}},
Arr: []InnerTest1{innerTest1},
Arr1: []int{1, 0, 3, 2, 4},
Inner: innerTest1,
Flags: []bool{true, false},
}
mt = map[string]interface{}{
"id": 1,
"dou": 3.14,
"pt": pt,
"str": "str",
"strEmpty": "",
"": 1,
}
)
const jsonStr = `{"dou":3.14,"id":1,"pt":{"arr":[{"inner":{"id":1}}],"flag":false,"flags":[true,false],"inner":{"inner":{"id":1}},"val1":{"1":"1","2":"2"},"val2":{"a":"a","b":"b"}},"str":"str"}`
func TestInterfaceToSortedJsonStr(t *testing.T) {
testAssert := assert.New(t)
tests := []struct {
origin interface{}
sign string
}{
{pt, `{"arr":[{"inner":{"id":1}}],"arr1":[1,0,3,2,4],"flag":false,"flags":[true,false],"inner":{"inner":{"id":1}},"val1":{"1":"1","2":"2"},"val2":{"a":"a","b":"b"},"val3":[{"val3":{"inner":{"id":1}}}],"val4":[[{"val4":{"inner":{"id":1}}}]]}`},
{mt, `{"dou":3.14,"id":1,"pt":{"arr":[{"inner":{"id":1}}],"arr1":[1,0,3,2,4],"flag":false,"flags":[true,false],"inner":{"inner":{"id":1}},"val1":{"1":"1","2":"2"},"val2":{"a":"a","b":"b"},"val3":[{"val3":{"inner":{"id":1}}}],"val4":[[{"val4":{"inner":{"id":1}}}]]},"str":"str"}`},
{jsonStr, jsonStr},
{1, "1"},
{false, "false"},
{"", ""},
}
for _, test := range tests {
sign, err := InterfaceToSortedJSONStr(test.origin)
testAssert.Equal(sign, test.sign)
testAssert.Equal(err, nil)
}
}
const (
rsaPrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCY4/TH2UpkW5pRgdmvkwGQGWFt1E2a76j9s1gmm0wOiByLQ1KQ
NuJ1c3SBpAKcIMh4841cf3t1HPTttgaK/51RGq7AN+R7naKnFWg20WGzkEpHzS4E
JM+S1bOtyz260ZhunxMA4HmmWPDq94lczfMEss/wjKL+r9R3HIeh21cKfwIDAQAB
AoGAEanYaFRay2Bn4j3JvAaUWiUMhAdQlfNVR0Y2i3NKpK0l+xLikYW9wQr/LVEY
+hexgYPF06doyH15cJMki19/uaawZLVRTv8tiTD+XHlpjFUpVlf52/be19gK+/ZL
mqjs2WQggJMyzH/OvBnvkqxEpqf5ilIUAvJWgJ6wfYUBHhUCQQC3u2Map9scywhQ
dzP4u0INvFKKrgz2O64uwf7Gn5rbXRsDTl8tLUXoiGiOGNjNtX/y4CeLjRn5ezs+
ZDm4EHddAkEA1QcHPnjzusJogGvy8iSVfqTDbby+KzhTYxMFaaA0q4r91Kz1BVP+
kc47n24G3y3Zhs5rro78loRpdJOeUfJ3iwJAFbxEUB31bOWT+Tjw3AcDHG7f8OoA
PIz44S0v/71X64WLMYvu9IA7mfOxMsY7t7I2Dbx40SiDHyF1876VmXHRPQJAVSI0
6+uMhBOTjdcWRV0HfZA9JcrrOPyOnqaIYDkNM40defQRC6sQrpZ7z3A6QNDjAPPX
pvAv07thJZylBdzflwJABTzHbnZ+R6av1Qz8zsicHAC6YG1PuprXO40X/Icl8W+D
yNwv2bKrpA9MxS2bFcC9wtVeeWgE1oyJBJD8pEQonQ==
-----END RSA PRIVATE KEY-----
`
rsaPubKey = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCY4/TH2UpkW5pRgdmvkwGQGWFt
1E2a76j9s1gmm0wOiByLQ1KQNuJ1c3SBpAKcIMh4841cf3t1HPTttgaK/51RGq7A
N+R7naKnFWg20WGzkEpHzS4EJM+S1bOtyz260ZhunxMA4HmmWPDq94lczfMEss/w
jKL+r9R3HIeh21cKfwIDAQAB
-----END PUBLIC KEY-----
`
)
func TestSign(t *testing.T) {
testAssert := assert.New(t)
sign, err := sign()
testAssert.Equal(sign, "lQuCpp3kW8udrTNtaKcGTPDeGelxIHEXqp4u3n1owDlFRQtbqKpPoLxICHt5ahEf4WvWiuoAqofJqv52/PhjPPKDWawMVZJlgP38bxkvD6Y1+pgXSvKSm+LXHpHQRExcLiHUvytWJ6U+C0geDoswdGMeHiRxT9IX6nWovKayZrk=")
testAssert.Equal(err, nil)
}
func sign() (string, error) {
str, _ := InterfaceToSortedJSONStr(mt)
return NewRSASigner().Sign(str, rsaPrivateKey)
}
func TestVerify(t *testing.T) {
testAssert := assert.New(t)
str, _ := InterfaceToSortedJSONStr(pt)
sign, _ := sign()
err := NewRSAVerifier().Verify(str, sign, rsaPubKey)
testAssert.Equal(err, nil)
}
App:访问server端的应用
package signmiddleware
import "xxx/signature"
const (
SignAppIDKey = "appID" // app ID key, http请求时设置appID到Header中, grpc请求时client拦截器自动完成 设置到context中
SignValueKey = "sign" // 签名 key, http请求时设置sign到Header中, grpc请求时client拦截器自动完成 设置到context中
ErrAppIDorSign = "app id or sign is not valid, app id=%v"
)
type SignClient struct {
AppID string // app ID
PrivateKey string // 私钥
}
type GetPublicKeysByID func(appID string) ([]string, error)
// CreateSign 生成签名
func CreateSign(request interface{}, privateKey string) (sign string, err error) {
// 1、req转有序json
toSignJSON, err := signature.InterfaceToSortedJSONStr(request)
if err != nil {
return
}
// 2、签名
sign, err = signature.NewRSASigner().Sign(toSignJSON, privateKey)
return
}
// VerifySign 验证签名
func VerifySign(request interface{}, sign string, pubKeys []string) (err error) {
// 1、req转有序json
toSignJSON, err := signature.InterfaceToSortedJSONStr(request)
if err != nil {
return
}
// 2、支持多个公钥验签,密钥升级时,兼容旧的请求
verifier := signature.NewRSAVerifier()
for _, v := range pubKeys {
err = verifier.Verify(toSignJSON, sign, v)
// 验签成功,跳出循环
if err == nil {
break
}
}
return
}
package grpcsign
import (
"context"
"fmt"
"xxx/signmiddleware"
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
"google.golang.org/grpc"
)
func SignUnaryServerInterceptor(getPubKey signmiddleware.GetPublicKeysByID) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 1、获取context中自定义的属性
appID := GetAppIDFromCtx(ctx)
sign := getSignFromCtx(ctx)
if appID == "" || sign == "" {
return nil, fmt.Errorf(signmiddleware.ErrAppIDorSign, appID)
}
// 2、根据AppID获取公钥
pubKeys, err := getPubKey(appID)
if err != nil {
return nil, err
}
// 3、验证签名
err = signmiddleware.VerifySign(req, sign, pubKeys)
// 验签失败
if err != nil {
return nil, err
}
v, err := handler(ctx, req)
return v, err
}
}
func GetAppIDFromCtx(ctx context.Context) string {
return metautils.ExtractIncoming(ctx).Get(signmiddleware.SignAppIDKey)
}
func getSignFromCtx(ctx context.Context) string {
return metautils.ExtractIncoming(ctx).Get(signmiddleware.SignValueKey)
}
func SignUnaryClientInterceptor(signC *signmiddleware.SignClient) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 1、生成签名
sign, err := signmiddleware.CreateSign(req, signC.PrivateKey)
if err != nil {
return err
}
// 3、appID及签名设置到context, grpc自定义key只能使用grpc提供的metadata接口
newCtx := metautils.ExtractOutgoing(ctx).Clone().Set(signmiddleware.SignAppIDKey, signC.AppID).Set(signmiddleware.SignValueKey, sign).ToOutgoing(ctx)
// 4、调用服务端
err = invoker(newCtx, method, req, reply, cc, opts...)
return err
}
}
package ginsign
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"xxx/log"
"xxx/signature"
"xxx/signmiddleware"
"github.com/gin-gonic/gin"
)
// SignClientToHeader http请求加签,设置签名内容到header中,客户端使用
func SignClientToHeader(header map[string]string, req interface{}, signC *signmiddleware.SignClient) (newHeader map[string]string, err error) {
// 1、生成sign
sign, err := signmiddleware.CreateSign(req, signC.PrivateKey)
if err != nil {
return
}
// 2、设置到Header中
if len(header) == 0 {
header = make(map[string]string)
}
header[signmiddleware.SignAppIDKey] = signC.AppID
header[signmiddleware.SignValueKey] = sign
newHeader = header
return
}
// SignServerVerify gin验签中间件
func SignServerVerify(c *gin.Context, getPubKeysByID signmiddleware.GetPublicKeysByID) (err error) {
// 1、从header中取出签名内容
appID := c.GetHeader(signmiddleware.SignAppIDKey)
sign := c.GetHeader(signmiddleware.SignValueKey)
if appID == "" || sign == "" {
log.Warningf(c, "client signature is invalid, appID=%v, sign=%v", appID, sign)
err = fmt.Errorf(signmiddleware.ErrAppIDorSign, appID)
return
}
// 2、根据App ID获取公钥
pubKeys, err := getPubKeysByID(appID)
if err != nil {
log.Warningf(c, "svc.GetAppNameByID failed, appID=%v, err=%v", appID, err)
return
}
// 3、初始化待签内容, request中的参数转content
var content string
switch {
case c.Request.Method == "GET":
content, err = convertURLValToSignJSON(c.Request.Form)
case c.ContentType() == "application/json":
content, err = convertBodyToSignJSON(c)
default:
content, err = convertURLValToSignJSON(c.Request.PostForm)
}
if err != nil {
return
}
// 4、验证签名
err = signmiddleware.VerifySign(content, sign, pubKeys)
// 验签失败
if err != nil {
log.Warningf(c, "sign.middleware.VerifySign failed, appID=%v, content=%v, sign=%v, err=%v", appID, content, sign, err)
}
return
}
func convertURLValToSignJSON(values url.Values) (content string, err error) {
if len(values) == 0 {
return
}
// 构建排序map
sc := make(signature.ToSignMap)
for k, v := range values {
if len(v) == 0 {
continue
}
sc[k] = v[0]
}
// 转换为JSON
content, err = sc.ToSortedNoZeroValueJSON()
return
}
func convertBodyToSignJSON(c *gin.Context) (content string, err error) {
// 1、获取body []byte
data, err := c.GetRawData()
if err != nil {
return
}
// 2、data转map
content, err = jsonToSorted(data)
// 3、重新赋值body,以便body可以被再次读取
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
return
}
func jsonToSorted(data []byte) (content string, err error) {
sc := make(signature.ToSignMap)
err = json.Unmarshal(data, &sc)
if err != nil {
return
}
// 转换为有序JSON
content, err = signature.InterfaceToSortedJSONStr(sc)
return
}