title: Signature mismatch when using S3 signed URLs
body: <p>I have a bucket in S3 that I linked up to a CNAME alias. Let's assume for now that the domain is <strong>media.mycompany.com</strong>. In the bucket are image files that are all set to private. Yet they are publicly used on my website using URL signing. A signed URL may look like this:</p>
  
  <p><a href="http://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D" rel="nofollow">http://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D</a></p>
  
  <p>This works fine as it is. I'm using a S3 helper library in PHP to generate such URLs. Here's the identifier of that library:</p>
  
  <pre><code>$Id: S3.php 44 2008-12-23 15:38:38Z don.schonknecht $
  </code></pre>
  
  <p>I know that it is old, but I'm relying on a lot of methods in this library, so it's not trivial to upgrade, and as said, it works well for me. Here's the relevant method in this library:</p>
  
  <pre><code>public static function getAuthenticatedURL($bucket, $uri, $lifetime, $hostBucket = false, $https = false) {
      $expires = time() + $lifetime;
      $uri = str_replace('%2F', '/', rawurlencode($uri)); // URI should be encoded (thanks Sean O'Dea)
      return sprintf(($https ? 'https' : 'http').'://%s/%s?AWSAccessKeyId=%s&amp;Expires=%u&amp;Signature=%s',
      $hostBucket ? $bucket : $bucket.'.s3.amazonaws.com', $uri, self::$__accessKey, $expires,
      urlencode(self::__getHash("GET\n\n\n{$expires}\n/{$bucket}/{$uri}")));
  }
  </code></pre>
  
  <p>In my normal, working setup, I'd call this method like this:</p>
  
  <pre><code>$return = $this-&gt;s3-&gt;getAuthenticatedURL('media.mycompany.com', $dir . '/' . $filename,
                      $timestamp, true, false);
  </code></pre>
  
  <p>This returns the correctly signed URL as shared earlier in this post, and all is good.</p>
  
  <p>However, I'd now like to generate HTTPS URLs, and this is where I'm running into issues. Simply adding HTTPs to the current URL (by setting the last param of the method to true) will not work, it will generate a URL like this:</p>
  
  <p><a href="https://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D" rel="nofollow">https://media.mycompany.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D</a></p>
  
  <p>This will obviously not work, since my SSL certificate (which is created from letsencrypt) is not installed on Amazon's domain, and as far as I know, there's no way to do so. </p>
  
  <p>I've learned of an alternative URL format to access the bucket over SSL:</p>
  
  <p><a href="https://media.mycompany.com.s3.amazonaws.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D" rel="nofollow">https://media.mycompany.com.s3.amazonaws.com/images/651/38935_small.JPG?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Uk3K7qVNRFHuIUnMaDadCOPjV%2BM%3D</a></p>
  
  <p>This apparently works for some people, but not for me, from what I know, it's due to having a dot (.) character in my bucket name. I cannot change the bucket name, it would have large consequences in my setup. </p>
  
  <p>Finally, there's this format:</p>
  
  <p><a href="https://s3.amazonaws.com/media.mycompany.com/images/2428/39000_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=6p3W6GHQtddJNnCoUXaNl970x9s%3D" rel="nofollow">https://s3.amazonaws.com/media.mycompany.com/images/2428/39000_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=6p3W6GHQtddJNnCoUXaNl970x9s%3D</a></p>
  
  <p>And here I am getting very close. If I take a working non-secure URL, and edit the URL to take on this format, it works. The image is shown. </p>
  
  <p>Now I'd like to have it working in the automated way, from the signing method I showed earlier. I'm calling it like this:</p>
  
  <pre><code>$return = $this-&gt;s3-&gt;getAuthenticatedURL("s3.amazonaws.com/media.mycompany.com", $dir . '/' . $filename,
                      $timestamp, true, true);
  </code></pre>
  
  <p>The change here is the alternative bucket name format, and the last parameter being set to true, indicating HTTPs. This leads to an output like this:</p>
  
  <p><a href="https://s3.amazonaws.com/media.mycompany.com/images/2784/38965_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Db2ynwWOV852Mn4rpcWA0Q1DrH0%3D" rel="nofollow">https://s3.amazonaws.com/media.mycompany.com/images/2784/38965_small.jpg?AWSAccessKeyId=05GMT0V3GWVNE7GGM1R2&amp;Expires=1466035210&amp;Signature=Db2ynwWOV852Mn4rpcWA0Q1DrH0%3D</a></p>
  
  <p>As you can see, it has the same format as the URL I manually crafted to work. But unfortunately, I'm getting signature errors:</p>
  
  <pre><code>&lt;Code&gt;SignatureDoesNotMatch&lt;/Code&gt;
  &lt;Message&gt;
  The request signature we calculated does not match the signature you provided. Check your key and signing method.
  &lt;/Message&gt;
  </code></pre>
  
  <p>I'm stuck figuring out why these signatures are incorrect. I tried setting the 4th parameter of the signing method to true and false, but it makes no difference.</p>
  
  <p>What am I missing?</p>
  
  <p><strong>Edit</strong></p>
  
  <p>Based on Michael's answer below I tried to do the simple string replace after the call to the S3 library, which works. Quick and dirty code:</p>
  
  <pre><code>                $return = $this-&gt;s3-&gt;getAuthenticatedURL("media.mycompany.com", $dir . '/' . $filename, $timestamp, true, true);
                  $return = substr_replace($return, "s3.amazonaws.com/", strpos($return, "media.mycompany.com"), 0);
  </code></pre>