You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkins Pipeline生成AWS4-HMAC-SHA256签名时匹配失败问题排查

问题:Jenkins Pipeline生成AWS V4签名访问Scaleway S3桶时签名不匹配

我尝试在Jenkins Pipeline中生成AWS V4认证签名以访问Scaleway的S3存储桶,但执行后收到「签名不匹配」的响应。已排查:

  • Postman使用相同凭证可正常访问目标桶;
  • SHA256Hash和sign函数生成的哈希值与在线工具结果一致;
  • 已对照Scaleway官方文档验证格式、换行符等规范。

以下是我的Pipeline脚本:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;

def SHA256Hash(String input) {
    // Create a MessageDigest object for SHA-256 hashing
    def digest = MessageDigest.getInstance('SHA-256')
    
    // Convert the input string to bytes and hash it
    def hashedBytes = digest.digest(input.getBytes('UTF-8'))
    
    // Convert the hashed bytes to a hexadecimal string
    def hexString = new StringBuilder()
    hashedBytes.each { b ->
        def hex = Integer.toHexString(0xff & b)
        if (hex.length() == 1) hexString.append('0')
        hexString.append(hex)
    }
    return hexString.toString()
}

def sign(key, msg) {
    SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(keySpec);
    byte[] signatureBytes = mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
    echo "thats from function ${signatureBytes}"
    return Hex.encodeHexString(signatureBytes);
}

pipeline {
    agent any
    environment {
        AWS_ACCESS_KEY_ID = credentials('my-bucket-api-access-key')
        AWS_SECRET_ACCESS_KEY = credentials('my-bucket-api-secret-key')
        AWS_DEFAULT_REGION = 'fr-par'
        SERVICE = 's3'
        S3_BUCKET_NAME = "NAME_OF_BUCKET"
        HOST = "${S3_BUCKET_NAME}.s3.${AWS_DEFAULT_REGION}.scw.cloud"
    }
    stages {
        stage('AWS Version 4 signing') {
            steps {
                script {
                    def date_stamp = new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
                    //generate signing key
                    def signing_key = "AWS4${AWS_SECRET_ACCESS_KEY}"
                    signing_key =sign(sign(sign(sign(signing_key, date_stamp), AWS_DEFAULT_REGION), SERVICE), 'aws4_request')
                    echo " signing_key ${signing_key}"

                    //generate string to sign
                    def method = 'GET'
                    def amz_date = new Date().format("yyyyMMdd'T'HHmmss'Z'", TimeZone.getTimeZone("UTC"))
                    def canonical_uri = '/'
                    def canonical_querystring = ''
                    def Amz_Content_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
                    def payload_hash = Amz_Content_SHA256
                    def canonical_headers = "host:${HOST}\nx-amz-content-sha256:${Amz_Content_SHA256}\nx-amz-date:${amz_date}"
                    def signed_headers = 'host;x-amz-content-sha256;x-amz-date'
                    def canonical_request = "${method}\n${canonical_uri}\n${canonical_querystring}\n${canonical_headers}\n${signed_headers}\n${payload_hash}"
                    echo " canonical_request ${canonical_request}"
                    def algorithm = 'AWS4-HMAC-SHA256'
                    def credential_scope = "${date_stamp}/${AWS_DEFAULT_REGION}/${SERVICE}/aws4_request"
                    def canonicalRequestHash = SHA256Hash(canonical_request)
                    def string_to_sign = "${algorithm}\n${amz_date}\n${credential_scope}\n${canonicalRequestHash}"
                    echo " string_to_sign ${string_to_sign}"

                    //create signature
                    def signature = sign(signing_key, string_to_sign)
                    echo " signature ${signature}"

                    //create authorization header
                    def authorization_header = "${algorithm} Credential=${AWS_ACCESS_KEY_ID}/${credential_scope},SignedHeaders=${signed_headers},Signature=${signature}"
                    echo " authorization_header ${authorization_header}"

                    def s3ListObjectsUrl = "http://${S3_BUCKET_NAME}.s3.${AWS_DEFAULT_REGION}.scw.cloud"
                    def s3Host = "${S3_BUCKET_NAME}.s3.${AWS_DEFAULT_REGION}.scw.cloud"

                    sh """
                        curl -X GET "${s3ListObjectsUrl}" \
                        -H "Host: ${s3Host}" \
                        -H "X-Amz-Content-Sha256: ${Amz_Content_SHA256}" \
                        -H "X-Amz-Date: ${amz_date}" \
                        -H "Authorization: ${authorization_header}"
                    """
                }
            }
        }
    }
}

解决方案

1. 核心错误:签名密钥生成流程错误

AWS V4签名要求每一步HMAC操作都基于前一步的原始字节数组,而非其十六进制字符串表示。你的sign函数返回的是HMAC结果的十六进制字符串,后续调用时将该字符串作为密钥传入,相当于把十六进制字符的字节值作为密钥,而非原始HMAC字节数组,导致后续签名计算完全错误。

修正方式:
修改sign函数返回字节数组,调整签名密钥生成逻辑:

// 重新定义sign函数,返回字节数组而非十六进制字符串
def sign(byte[] key, String msg) {
    SecretKeySpec keySpec = new SecretKeySpec(key, "HmacSHA256");
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(keySpec);
    return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}

// 生成签名密钥的正确流程
def signing_key = ("AWS4${AWS_SECRET_ACCESS_KEY}").getBytes(StandardCharsets.UTF_8)
signing_key = sign(signing_key, date_stamp)
signing_key = sign(signing_key, AWS_DEFAULT_REGION)
signing_key = sign(signing_key, SERVICE)
signing_key = sign(signing_key, 'aws4_request')

// 生成最终签名时,再将字节数组转十六进制
def signature = Hex.encodeHexString(sign(signing_key, string_to_sign))

2. 统一时间戳生成,避免时间偏差

两次调用new Date()可能导致date_stamp和amz_date的时间不一致(比如跨秒),统一用同一个时间对象更严谨:

def now = new Date(TimeZone.getTimeZone("UTC"))
def date_stamp = now.format("yyyyMMdd")
def amz_date = now.format("yyyyMMdd'T'HHmmss'Z'")

3. 使用HTTPS请求Scaleway S3端点

Scaleway S3默认要求HTTPS访问,你的脚本用HTTP可能导致签名验证失败(Postman大概率使用的是HTTPS):

def s3ListObjectsUrl = "https://${S3_BUCKET_NAME}.s3.${AWS_DEFAULT_REGION}.scw.cloud"

4. 避免重复发送Host头

curl请求URL中已包含主机信息,手动添加-H "Host: ${s3Host}"可能导致重复Host头,可移除该参数,让curl自动生成正确的Host头。


内容的提问来源于stack exchange,提问作者selmin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 10:52:02