---
title: "加签&验签"

doc_type: static
doc_category: static
doc_version: v20270301
source_url:
  html: 'https://developer.shopline.com/zh-hans-cn/docs/apps/api-instructions-for-use/generate-and-verify-signatures'
  md: 'https://developer.shopline.com/zh-hans-cn/docs/apps/api-instructions-for-use/generate-and-verify-signatures.md'
---
# 加签和验签

为了确保你的应用与 SHOPLINE 平台之间的数据交互是安全且未被篡改的，你可能需要在[特定的场景](#appScenarios)下对请求进行签名或验证签名：

- 当向 SHOPLINE 发起 HTTP 请求时，对请求进行签名。
- 当 SHOPLINE 向你发起 HTTP 请求时，对请求进行验证签名。

通过阅读本文，你将了解到具体的签名算法以及适用的场景。

## 应用场景{#appScenarios}
### Admin API 授权
在你使用 Admin Rest API 或 Admin Graphql API 之前，你需要完成授权流程的对接。在授权过程中，涉及到签名和验签操作。详细内容请参阅[应用授权](https://developer.shopline.com/zh-hans-cn/docs/apps/api-instructions-for-use/app-authorization.md)。

### Webhooks验签
当 Webhook 被触发后，SHOPLINE 将向你配置的地址推送 HTTP 请求。在推送数据时，SHOPLINE 会对请求进行签名处理。你收到请求后，可通过验证签名来验证数据的合法性。详细信息请参阅[Webhook 事件推送规约](https://developer.shopline.com/zh-hans-cn/docs/apps/api-instructions-for-use/webhooks/overview.md)。

## 签名算法{#signatureAlgorithm}
### HMAC-SHA256
HMAC-SHA256 是一种数据验证代码，它采用了 SHA-256 哈希算法并结合了密钥和消息进行加密。它是基于散列函数的消息认证码，由以下两个算法组成：

-  HMAC（Hash-based Message Authentication Code）

如需了解更多算法内容，请参考: [HMAC](https://en.wikipedia.org/wiki/HMAC)

-  SHA-256（Secure Hash Algorithm 256-bit）

如需了解更多算法内容，请参考：[SHA-2](https://en.wikipedia.org/wiki/SHA-2)。

## 操作步骤
### 前提条件:

- 确认请求的场景是否用到了签名算法,请查阅: [应用场景](#appScenarios)
- 获取签名所需要的秘钥,获取步骤参考:[算法密钥获取步骤](#AlgorithmKeyAcquisitionSteps)
- 确认自己的请求方法 选择对应的操作步骤
- 如果你的应用可以更新了密钥，请及时更新算法用到的密钥，否则可能会造成加验签失败

### GET 请求操作步骤
#### 加签

1. 组装请求原始的Query参数后，若你的query参数没有经过URLEncode,需要先进行URLEncode,然后对 Query参数进行字典序排列,得到待签名的字符串（source）。
2. 使用待签名的字符串（source）和应用密钥（APP Secret）作为参数调用 [HMAC-SHA256](#signatureAlgorithm) 算法加签得到签名（sign）。
3. 把签名(sign)拼接到原始Query参数的后面，得到最终请求的 Query参数

![sign2_zh.png](https://s2cdn.myshopline.com/slfs/op-new/171630704065442/sign2_zh.png)

#### 验签

1. 得到请求原始的 Query 参数后，提取 "sign" 这个 Field 后获得签名(sign)，对剩余 Query 参数的 Fields 进行字典序排列，得到待签名的字符串（source）。
2. 使用待签名的字符串（source）和应用密钥（APP Secret）作为参数调用 [HMAC-SHA256](#signatureAlgorithm) 算法加签得到签名（hmacsha256Sign）。
3. 签名(sign)与签名(hmacsha256Sign)进行比较，如果相等则认为签名合法，否则认为签名非法


![sign_zh.drawio.png](https://s2cdn.myshopline.com/slfs/op-new/171630712374947/sign_zh.drawio.png)



> **note**
>
> 在判断签名合法时，即便签名相等，此时如果该接口的 Query 参数中含有 `timestamp` Field，可用当前系统时间戳（13位）减去 `timestamp` Field 的值，如果大于一定的时间间隔(SHOPLINE使用的时间间隔为`10`分钟)，可判定为签名非法，因为该请求可能被泄露后被用于非法模拟请求，添加该额外校验可让程序更加安全。

### POST 请求操作步骤
#### 加签

1. 组装请求 Body 字符串后(如果请求Body为空,可以使用空字符串)，在 Body 字符串后拼接当前系统时间戳（timestamp），长度13位，得到待签名的字符串（source）。
2. 使用待签名的字符串（source）和应用密钥（APP Secret）作为参数调用 [HMAC-SHA256](#signatureAlgorithm) 算法加签得到签名（sign）。
3. 把签名（sign）和当前系统时间戳（timestamp）这两个 Fields 添加到请求 Header 中，请求签名信息组装完成


![sign4_zh.drawio.png](https://s2cdn.myshopline.com/slfs/op-new/171630727176051/sign4_zh.drawio.png)


#### 验签
在需要[验证签名的场景下](#appScenarios) SHOPLINE 向你发起 HTTP POST 请求

1. 原始请求的 Header 中存在 "timestamp" Field 时，取该值并拼接到原始请求的 Body 后，得到待签名的字符串（source）。
2. 使用待签名的字符串（source）和应用密钥（APP Secret）作为参数调用 [HMAC-SHA256](#signatureAlgorithm) 算法加签得到签名（hmacsha256Sign）。
3. 从原始请求的 Header 中获取 "sign" 这个 Field 的值，使用该值与签名（hmacsha256Sign）的值进行比较，如果相等则认为签名合法，否则认为签名非法


> **note**
>
> 在判断签名合法时，即便签名相等，此时如果该接口的 Header 参数中含有 `timestamp` Field，可用当前系统时间戳（13位）减去 `timestamp` Field 的值，如果大于一定的时间间隔(SHOPLINE使用的时间间隔为`10`分钟)，可判定为签名非法，因为该请求可能被泄露后被用于非法模拟请求，添加该额外校验可让程序更加安全。

![sign3_zh.jpg](https://s2cdn.myshopline.com/slfs/op-new/171644550018039/sign3_zh.jpg)


## 算法示例
采用 [HMAC-SHA256](#signatureAlgorithm) 算法配合算法密钥： APP Secret，对请求报文或请求参数进行加签，以下是几种常用语言加签的实现

### JAVA
```java
package com.shopline.demo

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class HmacDemo {

    public static void main(String[] args) {
        System.out.println(hmacSha256("The content to be signed", "Your APP Secret"));
    }

    /**
     * hmacSha256
     *
     * @param source The content to be signed
     * @param secret Your APP Secret
     * @return
     */
    public static String hmacSha256(String source, String secret) {
        if (StringUtils.isNotEmpty(secret) && StringUtils.isNotEmpty(source)) {
            try {
                Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
                SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
                sha256_HMAC.init(secret_key);
                byte[] bytes = sha256_HMAC.doFinal(source.getBytes(StandardCharsets.UTF_8));
                return new String(Hex.encodeHex(bytes));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }


```
### PHP
```php
<?php

class HmacDemo {

    /**
     * Generate HMAC-SHA256 signature.
     *
     * @param string $source The content to be signed.
     * @param string $secret Your APP Secret.
     * @return string HMAC-SHA256 signature as a hex string or an empty string if an error occurs.
     */
    public static function hmacSha256($source, $secret) {
        if (!empty($secret) && !empty($source)) {
            try {
                return hash_hmac('sha256', $source, $secret);
            } catch (Exception $e) {
                error_log("Error generating HMAC-SHA256 signature: " . $e->getMessage());
                return '';
            }
        }
        return '';
    }
}

// Example usage
$source = "The content to be signed";
$secret = "Your APP Secret";
echo "HMAC-SHA256 Signature: " . HmacDemo::hmacSha256($source, $secret);

?>
```
### Python
```python
#python 3
import hmac
import hashlib

def generate_hmac_sha256(source, secret):
    if not source or not secret:
        raise ValueError("Source and secret must not be empty.")
    try:
        # Create HMAC-SHA256 signature
        signature = hmac.new(bytes(secret, 'utf-8'), msg=bytes(source, 'utf-8'), digestmod=hashlib.sha256).hexdigest()
        return signature
    except Exception as e:
        print(f"Error generating HMAC-SHA256 signature: {e}")
        return None

#Example usage
source = 'The content to be signed'
secret = 'Your APP Secret'

signature = generate_hmac_sha256(source, secret)
if signature:
	print(f"HMAC-SHA256 Signature: {signature}")
else:
	print("Failed to generate HMAC-SHA256 signature.")
```

### Golang

```
package signature

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
)


// Generate a signature (POST request)
func GenerateSign(appKey, body, timestamp, appSecret string) string {
	source := body + timestamp

	// Create an HMAC object
	hash := hmac.New(sha256.New, []byte(appSecret))

	// Write data
	hash.Write([]byte(source))

	// Compute the hash value and convert it to hexadecimal
	return hex.EncodeToString(hash.Sum(nil))
}

// Verify the signature for callback URL parameters (GET request)
func VerifySign(appSecret string, params url.Values, receivedSign string) bool {
	// Copy the query parameters and remove the 'sign' parameter
	paramCopy := make(map[string]string)
	for k, v := range params {
		if k != "sign" {
			paramCopy[k] = v[0]
		}
	}

	// Sort the remaining query parameters in alphabetical order
	keys := make([]string, 0, len(paramCopy))
	for k := range paramCopy {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// Build query parameters
	var paramStrBuilder strings.Builder
	for _, k := range keys {
		if paramStrBuilder.Len() > 0 {
			paramStrBuilder.WriteByte('&')
		}
		paramStrBuilder.WriteString(k)
		paramStrBuilder.WriteByte('=')
		paramStrBuilder.WriteString(paramCopy[k])
	}
	paramStr := paramStrBuilder.String()

	// Compute the HMAC-SHA256 signature
	hash := hmac.New(sha256.New, []byte(appSecret))
	hash.Write([]byte(paramStr))
	expectedSign := hex.EncodeToString(hash.Sum(nil))

	// Compare the signatures 
	return hmac.Equal([]byte(expectedSign), []byte(receivedSign))
}

// Verify the Webhook POST request, sent by SHOPLINE
// The body of the request is still readable after invoking the method.
func VerifyWebhookRequest(appSecret string, httpRequest *http.Request) bool {
	shoplineSha256 := httpRequest.Header.Get("X-Shopline-Hmac-Sha256")
	actualMac := []byte(shoplineSha256)

	mac := hmac.New(sha256.New, []byte(appSecret))
	requestBody, _ := ioutil.ReadAll(httpRequest.Body)
	httpRequest.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
	mac.Write(requestBody)
	macSum := mac.Sum(nil)
	expectedMac := []byte(hex.EncodeToString(macSum))

	return hmac.Equal(actualMac, expectedMac)
}

// Verify a Webhook message by comparing its HMAC against a computed HMAC using SHA-256
func VerifyWebhookMessage(appSecret, message, messageMAC string) bool {
	mac := hmac.New(sha256.New, []byte(appSecret))
	mac.Write([]byte(message))
	expectedMAC := mac.Sum(nil)

	// Decode the hexadecimal HMAC
	actualMac, _ := hex.DecodeString(messageMAC)

	return hmac.Equal(actualMac, expectedMAC)
}


```

## 算法密钥获取步骤{#AlgorithmKeyAcquisitionSteps}
应用密钥（App Secret）可在【合作伙伴控制面板】中获取。在[应用列表](https://developer.myshopline.com/app/list)中，点击应用名称即可查看应用的 App Secret。

![applist.png](https://s2cdn.myshopline.com/slfs/op-new/171630744932221/applist.png)
