overview
First, synchronize the following project overview:
The last article shared that Jaeger link tracking, a routing middleware, had an unexpected response. The "Go China" public account was also forwarded. Many friends joined me to communicate with each other and called me "God". In fact, what God am I? It's just a local practice. I'm still a newcomer to the use of Go language. Thank you for your kindness. Love!
This article we share: routing middleware signature verification.
Why signature verification?
Needless to say, this is mainly to ensure the security of the interface and identify the caller. Based on these two points, let's design a signature together.
The caller needs to apply for App Key and App Secret. App Key is used to identify the caller and App Secret is used to encrypt and generate signature.
Of course, the generated signature also needs to meet the following requirements:
- Variability: each signature must be different.
- Timeliness: timeliness of each request, expired and invalid.
- Uniqueness: each signature is unique.
- Integrity: it can verify the incoming data and prevent tampering.
for instance:
/API? Param UU 1 = XXX & param UU 2 = XXX, where param UU 1 and param UU 2 are two parameters.
If signature verification is added, several more parameters need to be passed:
- ak represents App Key, which is used to identify the caller.
- ts represents the time stamp used to verify the timeliness of the interface.
- sn represents the signature encryption string, which is used to verify the integrity of data and prevent data tampering.
sn is encrypted through App Secret and passed parameters.
The final parameters passed are as follows:
/api?param_1=xxx¶m_2=xxx&ak=xxx&ts=xxx&sn=xxx
It's a debugging skill. It's too troublesome to generate ts and sn parameters manually every time. When debug=1 is passed, TS and sn will be returned. See the code for details.
This article shares three ways to implement signature: MD5 combined encryption, AES symmetric encryption and RSA asymmetric encryption.
Don't talk too much, get into the subject.
MD5 combination
Generate signature
First, encapsulate an MD5 method of Go:
func MD5(str string) string { s := md5.New() s.Write([]byte(str)) return hex.EncodeToString(s.Sum(nil)) }
To encrypt:
appKey = "demo" appSecret = "xxx" encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx" // Custom validation rules sn = MD5(appSecret + encryptStr + appSecret)
Verifying signature
By passing the parameters, the signature is generated again. If the passed signature is compared with the generated signature.
Same, indicating that the signature verification is successful.
Different, indicating that signature verification failed.
Middleware code implementation
var AppSecret string // MD5 combined encryption func SetUp() gin.HandlerFunc { return func(c *gin.Context) { utilGin := util.Gin{Ctx: c} sign, err := verifySign(c) if sign != nil { utilGin.Response(-1, "Debug Sign", sign) c.Abort() return } if err != nil { utilGin.Response(-1, err.Error(), sign) c.Abort() return } c.Next() } } // Verifying signature func verifySign(c *gin.Context) (map[string]string, error) { _ = c.Request.ParseForm() req := c.Request.Form debug := strings.Join(c.Request.Form["debug"], "") ak := strings.Join(c.Request.Form["ak"], "") sn := strings.Join(c.Request.Form["sn"], "") ts := strings.Join(c.Request.Form["ts"], "") // Source of validation value, ok := config.ApiAuthConfig[ak] if ok { AppSecret = value["md5"] } else { return nil, errors.New("ak Error") } if debug == "1" { currentUnix := util.GetCurrentUnix() req.Set("ts", strconv.FormatInt(currentUnix, 10)) res := map[string]string{ "ts": strconv.FormatInt(currentUnix, 10), "sn": createSign(req), } return res, nil } // Validation expiration time timestamp := time.Now().Unix() exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64) tsInt, _ := strconv.ParseInt(ts, 10, 64) if tsInt > timestamp || timestamp - tsInt >= exp { return nil, errors.New("ts Error") } // Verifying signature if sn == "" || sn != createSign(req) { return nil, errors.New("sn Error") } return nil, nil } // Create signature func createSign(params url.Values) string { // Custom MD5 combination return util.MD5(AppSecret + createEncryptStr(params) + AppSecret) } func createEncryptStr(params url.Values) string { var key []string var str = "" for k := range params { if k != "sn" && k != "debug" { key = append(key, k) } } sort.Strings(key) for i := 0; i < len(key); i++ { if i == 0 { str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) } else { str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) } } return str }
AES symmetric encryption
Before we use it, let's understand what symmetric encryption is.
Symmetric encryption is to use the same key that can be encrypted or decrypted. This method is called symmetric encryption.
Common algorithms: DES, AES.
AES is the upgraded version of DES, with longer key length, more options, more flexibility, higher security and faster speed. Let's directly start AES encryption.
Advantage
The algorithm is open, the computation is small, the encryption speed is fast, and the encryption efficiency is high.
shortcoming
The sender and the receiver must agree on the key, and then both parties can save the key, so the key management becomes the burden of both parties.
Application scenario
A relatively large amount of data or encryption of critical data.
Generate signature
First, encapsulate the AesEncrypt encryption method and AesDecrypt decryption method of Go.
// Encryption AES? 128? CBC func AesEncrypt (encryptStr string, key []byte, iv string) (string, error) { encryptBytes := []byte(encryptStr) block, err := aes.NewCipher(key) if err != nil { return "", err } blockSize := block.BlockSize() encryptBytes = pkcs5Padding(encryptBytes, blockSize) blockMode := cipher.NewCBCEncrypter(block, []byte(iv)) encrypted := make([]byte, len(encryptBytes)) blockMode.CryptBlocks(encrypted, encryptBytes) return base64.URLEncoding.EncodeToString(encrypted), nil } // Decrypt func AesDecrypt (decryptStr string, key []byte, iv string) (string, error) { decryptBytes, err := base64.URLEncoding.DecodeString(decryptStr) if err != nil { return "", err } block, err := aes.NewCipher(key) if err != nil { return "", err } blockMode := cipher.NewCBCDecrypter(block, []byte(iv)) decrypted := make([]byte, len(decryptBytes)) blockMode.CryptBlocks(decrypted, decryptBytes) decrypted = pkcs5UnPadding(decrypted) return string(decrypted), nil } func pkcs5Padding (cipherText []byte, blockSize int) []byte { padding := blockSize - len(cipherText)%blockSize padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(cipherText, padText...) } func pkcs5UnPadding (decrypted []byte) []byte { length := len(decrypted) unPadding := int(decrypted[length-1]) return decrypted[:(length - unPadding)] }
To encrypt:
appKey = "demo" appSecret = "xxx" encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx" sn = AesEncrypt(encryptStr, appSecret)
Verifying signature
decryptStr = AesDecrypt(sn, app_secret)
Compare the encrypted string with the decrypted one.
Same, indicating that the signature verification is successful.
Different, indicating that signature verification failed.
Middleware code implementation
var AppSecret string // AES symmetric encryption func SetUp() gin.HandlerFunc { return func(c *gin.Context) { utilGin := util.Gin{Ctx: c} sign, err := verifySign(c) if sign != nil { utilGin.Response(-1, "Debug Sign", sign) c.Abort() return } if err != nil { utilGin.Response(-1, err.Error(), sign) c.Abort() return } c.Next() } } // Verifying signature func verifySign(c *gin.Context) (map[string]string, error) { _ = c.Request.ParseForm() req := c.Request.Form debug := strings.Join(c.Request.Form["debug"], "") ak := strings.Join(c.Request.Form["ak"], "") sn := strings.Join(c.Request.Form["sn"], "") ts := strings.Join(c.Request.Form["ts"], "") // Source of validation value, ok := config.ApiAuthConfig[ak] if ok { AppSecret = value["aes"] } else { return nil, errors.New("ak Error") } if debug == "1" { currentUnix := util.GetCurrentUnix() req.Set("ts", strconv.FormatInt(currentUnix, 10)) sn, err := createSign(req) if err != nil { return nil, errors.New("sn Exception") } res := map[string]string{ "ts": strconv.FormatInt(currentUnix, 10), "sn": sn, } return res, nil } // Validation expiration time timestamp := time.Now().Unix() exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64) tsInt, _ := strconv.ParseInt(ts, 10, 64) if tsInt > timestamp || timestamp - tsInt >= exp { return nil, errors.New("ts Error") } // Verifying signature if sn == "" { return nil, errors.New("sn Error") } decryptStr, decryptErr := util.AesDecrypt(sn, []byte(AppSecret), AppSecret) if decryptErr != nil { return nil, errors.New(decryptErr.Error()) } if decryptStr != createEncryptStr(req) { return nil, errors.New("sn Error") } return nil, nil } // Create signature func createSign(params url.Values) (string, error) { return util.AesEncrypt(createEncryptStr(params), []byte(AppSecret), AppSecret) } func createEncryptStr(params url.Values) string { var key []string var str = "" for k := range params { if k != "sn" && k != "debug" { key = append(key, k) } } sort.Strings(key) for i := 0; i < len(key); i++ { if i == 0 { str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) } else { str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) } } return str }
RSA asymmetric encryption
As above, before using, let's understand what is asymmetric encryption?
Asymmetric encryption requires two keys for encryption and decryption. The two keys are public key and private key. This method is called asymmetric encryption.
Common algorithm: RSA.
Advantage
Compared with symmetric encryption, it has better security. Different keys are needed for encryption and decryption. Both public key and private key can encrypt and decrypt each other.
shortcoming
Encryption and decryption take a long time, slow speed, only suitable for a small amount of data encryption.
Application scenario
It is suitable for scenarios with high security requirements, and for encrypting a small amount of data, such as payment data, login data, etc.
Create signature
First, we encapsulate the RSA public key encryption method and the RSA private decrypt decryption method of Go.
// Public key encryption func RsaPublicEncrypt(encryptStr string, path string) (string, error) { // Open file file, err := os.Open(path) if err != nil { return "", err } defer file.Close() // Read file contents info, _ := file.Stat() buf := make([]byte,info.Size()) file.Read(buf) // pem decoding block, _ := pem.Decode(buf) // x509 decoding publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return "", err } // Type Asserts publicKey := publicKeyInterface.(*rsa.PublicKey) //Encrypt plaintext encryptedStr, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte(encryptStr)) if err != nil { return "", err } //Return ciphertext return base64.URLEncoding.EncodeToString(encryptedStr), nil } // Private key decryption func RsaPrivateDecrypt(decryptStr string, path string) (string, error) { // Open file file, err := os.Open(path) if err != nil { return "", err } defer file.Close() // Get file content info, _ := file.Stat() buf := make([]byte,info.Size()) file.Read(buf) // pem decoding block, _ := pem.Decode(buf) // X509 decoding privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return "", err } decryptBytes, err := base64.URLEncoding.DecodeString(decryptStr) //Decrypt the ciphertext decrypted, _ := rsa.DecryptPKCS1v15(rand.Reader,privateKey,decryptBytes) //Return plaintext return string(decrypted), nil }
The caller applies for a public key and encrypts it:
appKey = "demo" appSecret = "Public key" encryptStr = "param_1=xxx¶m_2=xxx&ak="+appKey+"&ts=xxx" sn = RsaPublicEncrypt(encryptStr, appSecret)
Verifying signature
decryptStr = RsaPrivateDecrypt(sn, app_secret)
Compare the encrypted string with the decrypted one.
Same, indicating that the signature verification is successful.
Different, indicating that signature verification failed.
Middleware code implementation
var AppSecret string // RSA asymmetric encryption func SetUp() gin.HandlerFunc { return func(c *gin.Context) { utilGin := util.Gin{Ctx: c} sign, err := verifySign(c) if sign != nil { utilGin.Response(-1, "Debug Sign", sign) c.Abort() return } if err != nil { utilGin.Response(-1, err.Error(), sign) c.Abort() return } c.Next() } } // Verifying signature func verifySign(c *gin.Context) (map[string]string, error) { _ = c.Request.ParseForm() req := c.Request.Form debug := strings.Join(c.Request.Form["debug"], "") ak := strings.Join(c.Request.Form["ak"], "") sn := strings.Join(c.Request.Form["sn"], "") ts := strings.Join(c.Request.Form["ts"], "") // Source of validation value, ok := config.ApiAuthConfig[ak] if ok { AppSecret = value["rsa"] } else { return nil, errors.New("ak Error") } if debug == "1" { currentUnix := util.GetCurrentUnix() req.Set("ts", strconv.FormatInt(currentUnix, 10)) sn, err := createSign(req) if err != nil { return nil, errors.New("sn Exception") } res := map[string]string{ "ts": strconv.FormatInt(currentUnix, 10), "sn": sn, } return res, nil } // Validation expiration time timestamp := time.Now().Unix() exp, _ := strconv.ParseInt(config.AppSignExpiry, 10, 64) tsInt, _ := strconv.ParseInt(ts, 10, 64) if tsInt > timestamp || timestamp - tsInt >= exp { return nil, errors.New("ts Error") } // Verifying signature if sn == "" { return nil, errors.New("sn Error") } decryptStr, decryptErr := util.RsaPrivateDecrypt(sn, config.AppRsaPrivateFile) if decryptErr != nil { return nil, errors.New(decryptErr.Error()) } if decryptStr != createEncryptStr(req) { return nil, errors.New("sn Error") } return nil, nil } // Create signature func createSign(params url.Values) (string, error) { return util.RsaPublicEncrypt(createEncryptStr(params), AppSecret) } func createEncryptStr(params url.Values) string { var key []string var str = "" for k := range params { if k != "sn" && k != "debug" { key = append(key, k) } } sort.Strings(key) for i := 0; i < len(key); i++ { if i == 0 { str = fmt.Sprintf("%v=%v", key[i], params.Get(key[i])) } else { str = str + fmt.Sprintf("&%v=%v", key[i], params.Get(key[i])) } } return str }
How to call?
Like other middleware invocation methods, it can be freely selected according to its own needs.
For example, use MD5 combination:
.Use(sign_md5.SetUp())
Use AES symmetric encryption:
.Use(sign_aes.SetUp())
Use RSA asymmetric encryption:
.Use(sign_rsa.SetUp())
performance testing
Since RSA asymmetric encryption is the most secure, use it uniformly.
NO! NO! NO! Absolutely not!
Why should I be excited? Because I've met this pit before. It's all a tearful lesson...
Let's test the performance one by one:
MD5
func Md5Test(c *gin.Context) { startTime := time.Now() appSecret := "IgkibX71IEf382PT" encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" count := 1000000 for i := 0; i < count; i++ { // Generate signature util.MD5(appSecret + encryptStr + appSecret) // Verifying signature util.MD5(appSecret + encryptStr + appSecret) } utilGin := util.Gin{Ctx: c} utilGin.Response(1, fmt.Sprintf("%v second - %v", count, time.Since(startTime)), nil) }
Simulate one million requests, and the execution time is about 1.1s ~ 1.2s.
AES
func AesTest(c *gin.Context) { startTime := time.Now() appSecret := "IgkibX71IEf382PT" encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" count := 1000000 for i := 0; i < count; i++ { // Generate signature sn, _ := util.AesEncrypt(encryptStr, []byte(appSecret), appSecret) // Verifying signature util.AesDecrypt(sn, []byte(appSecret), appSecret) } utilGin := util.Gin{Ctx: c} utilGin.Response(1, fmt.Sprintf("%v second - %v", count, time.Since(startTime)), nil) }
Simulate one million requests, and the execution time is about 1.8s ~ 1.9s.
RSA
func RsaTest(c *gin.Context) { startTime := time.Now() encryptStr := "param_1=xxx¶m_2=xxx&ak=xxx&ts=1111111111" count := 500 for i := 0; i < count; i++ { // Generate signature sn, _ := util.RsaPublicEncrypt(encryptStr, "rsa/public.pem") // Verifying signature util.RsaPrivateDecrypt(sn, "rsa/private.pem") } utilGin := util.Gin{Ctx: c} utilGin.Response(1, fmt.Sprintf("%v second - %v", count, time.Since(startTime)), nil) }
I dare not simulate a million requests. I don't know when I can handle it. Let's simulate 500 times.
Simulate 500 requests, and the execution time is about 1s.
The above is my local execution effect. You can question my computer's poor performance and the packaging method.
You can also try to see if the performance gap is so large.
How do PHP and Go encryption methods communicate?
I write PHP. Can I use PHP to generate signatures?
Definitely!
I also implemented the above three methods in PHP. There may be some small adjustments. The overall problem is not big. The relevant Demo has been uploaded to github:
https://github.com/xinliangno...
OK, that's it.
Source address
https://github.com/xinliangno...