Go in API routing middleware signature verification

Keywords: Go PHP Unix github

overview

First, synchronize the following project overview:


The last article shared the routing Middleware - Jaeger link tracking (actual combat). The response is really unexpected. The "Go China" public number has also been forwarded. Many friends have exchanged with my friends, calling me a big God. Actually, I am a big God, but I have only practised in the locality. I am still a newcomer to the use of Go language. Here I would like to 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.

App Secret is used to encrypt and generate signatures.

Of course, the generated signature also needs to meet the following requirements:

Variability: each signature must be different.

Timeliness: the timeliness of each request. It is invalid after expiration.

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 the App Key, which is used to identify the caller.

ts is the time stamp used to verify the timeliness of the interface.

sn means 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&param_2=xxx&ak=xxx&ts=xxx&sn=xxx

This is a debugging skill. It's too troublesome to generate ts and sn parameters manually every time. When passing debug=1, 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&param_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&param_2=xxx&ak="+appKey+"&ts=xxx"    
    sn = AesEncrypt(encryptStr, appSecret)

 




Verifying signature

decryptStr = AesDecrypt(sn, app_secret)

 



Compare the encrypted string with the decrypted string.

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&param_2=xxx&ak="+appKey+"&ts=xxx"    
    sn = RsaPublicEncrypt(encryptStr, appSecret)

 




Verifying signature

decryptStr = RsaPrivateDecrypt(sn, app_secret)

 



Compare the encrypted string with the decrypted string.

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&param_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&param_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&param_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?

If I write PHP, can I generate signatures in PHP?

Definitely!

I have 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/xinliangnote/Encrypt

OK, that's it.

Posted by bigwatercar on Sat, 09 Nov 2019 05:41:11 -0800