【问题标题】:Validate S3 signature Python验证 S3 签名 Python
【发布时间】:2022-06-13 19:33:41
【问题描述】:

我想通过 Python 验证 S3(托管在 digitalocean)预签名 URL 的签名。据我所知,签名由完整的 URL 和密钥组成。

我已经尝试过AWS S3 presigned urls with boto3 - Signature mismatch 之类的方法,但这会导致签名不同。

我想通过使用散列算法重新创建 URL 中给出的签名(例如图像)来检查它。

我该怎么做呢?

【问题讨论】:

    标签: python-3.x amazon-s3


    【解决方案1】:

    我遇到了同样的问题,希望 boto 包能提供一种简单的方法来解决这个问题,但不幸的是它没有。

    我也尝试使用boto在url上创建相同的签名库,但问题是时间戳(url中的X-Amz-Date) 要获得完全相同的签名,需要使用 url 中提供的时间戳来生成。 我掉进了兔子洞,试图“覆盖”日期时间,但这似乎是不可能的。

    所以剩下的就是从头开始生成签名,就像你说你尝试过的那样。您链接的问题中的代码确实有效,但并不简单。

    受该链接和boto3 源的启发,这是我创建的,并且似乎有效:

    from urllib.parse import urlparse, parse_qs, urlencode, quote
    import hashlib
    import hmac
    from django.conf import settings
    
    def validate_s3_url(url, method='GET'):
        """
        This check whether the signature in the given S3 url is valid,
        considering the other parts of the url.
        This requires that we have access to the (secret) access key 
        that was used to sign the request (the access key ID is 
        available in the url).
        """
        parts = urlparse(url)
        querydict = parse_qs(parts.query)
        # get relevant query parameters
        url_signature = querydict['X-Amz-Signature'][0]
        credentials = querydict['X-Amz-Credential'][0]
        algorithm = querydict['X-Amz-Algorithm'][0]
        timestamp = querydict['X-Amz-Date'][0]
        signed_headers = querydict['X-Amz-SignedHeaders'][0]
    
        # if we have multiple access keys we could use access_key_id to get the right one
        access_key_id, credential_scope = credentials.split("/", maxsplit=1)
        host = parts.netloc
    
        # important: in Python 3 this dict is sorted which is essential
        canonical_querydict = {
            'X-Amz-Algorithm': [algorithm],
            'X-Amz-Credential': [credentials],
            'X-Amz-Date': [timestamp],
            'X-Amz-Expires': querydict['X-Amz-Expires'],
            'X-Amz-SignedHeaders': [signed_headers],
        }
        # this is optional (to force download with specific name)
        # if used, it's passed in as 'ResponseContentDisposition' Param when signing.
        if 'response-content-disposition' in querydict:
            canonical_querydict['response-content-disposition'] = querydict['response-content-disposition']
        canonical_querystring = urlencode(canonical_querydict, doseq=True, quote_via=quote)
    
        # build the request, hash it and build the string to sign
        canonical_request = f"{method}\n{parts.path}\n{canonical_querystring}\nhost:{host}\n\n{signed_headers}\nUNSIGNED-PAYLOAD"
        hashed_request = hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
        string_to_sign = f"{algorithm}\n{timestamp}\n{credential_scope}\n{hashed_request}"
    
        # generate signing key from credential scope.
        signing_key = f"AWS4{settings.AWS_SECRET_ACCESS_KEY}".encode('utf-8')
        for message in credential_scope.split("/"):
            signing_key = hmac.new(signing_key, message.encode('utf-8'), hashlib.sha256).digest()
    
        # sign the string with the key and check if it's the same as the one provided in the url
        signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
    
        return url_signature == signature
    

    这使用 django 设置来获取密钥,但实际上它可能来自任何地方。

    【讨论】:

      猜你喜欢
      • 2021-10-18
      • 2017-01-06
      • 2021-04-05
      • 2014-02-08
      • 1970-01-01
      • 2018-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多