設計目標
- 支持RSA2加簽驗籤(解析密鑰方式:PKCS1 數字簽名算法:SHA256)
- 支持grpc攔截器加簽驗籤,對業務代碼無侵入
- 支持gin框架中間件驗籤,支持客戶端發送http請求設置加簽信息到Header中
- 支持服務端對接多語言客戶端(簽名原文爲:有序JSON(ASCII碼序排序Key,忽略結構體/Map中的0值和空值),RSA2加簽(PKCS1+SHA256))
簽名
簽名接口
加簽接口
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"
"sort"
"strings"
)
type SignType string
const (
RSA2 SignType = "SHA256WithRSA"
)
const InvalidType = "invalid type=%v"
var ErrPemDecode = errors.New("pem.Decode failed")
func NewRSASigner() *Signer {
return &Signer{
Type: RSA2,
}
}
type Signer struct {
Type SignType
}
func (s *Signer) Sign(content, privateKey string) (sign string, err error) {
if s.Type == RSA2 {
return rsa2Sign(content, privateKey)
}
return
}
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
}
func NewRSAVerifier() *Verifier {
return &Verifier{
Type: RSA2,
}
}
type Verifier struct {
Type SignType
}
func (s *Verifier) Verify(content, sign, pubKey string) (err error) {
if s.Type == RSA2 {
return rsa2Verify(content, sign, pubKey)
}
return
}
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
}
type ToSignMap map[string]interface{}
func (sc ToSignMap) ToSortedNoZeroValue() ToSignMap {
if len(sc) == 0 {
return sc
}
// 1、取出sc的值不爲空的key
var keys []string
for k, v := range sc {
// 忽略空值
if k == "" || v == "" {
continue
}
keys = append(keys, k)
}
// 2、排序
sort.Strings(keys)
// 3、重組爲排序的map
sorted := make(ToSignMap)
for _, v := range keys {
sorted[v] = sc[v]
}
return sorted
}
func (sc ToSignMap) ToSortedNoZeroValueJSON() (content string, err error) {
sorted := sc.ToSortedNoZeroValue()
if len(sorted) == 0 {
return
}
// 轉換爲Json
js, err := json.Marshal(sorted)
if err != nil {
return
}
content = string(js)
return
}
// 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
}
GRPC中間件
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
}
}
GIN中間件
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
}