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¶m_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¶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 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¶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 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¶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?
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.