Search code examples
bashpowershellubuntuopensslsha

Generating a SHA1 Signature with Different Results Between Ubuntu/Bash and Windows/Powershell


Why would these respective bash and powershell scripts return different signatures:

## (Ubuntu) Bash
now='Fri, 26 Jul 2019 12:32:36 -0400'
bucket="mybucket"
path="/"
awsKey="MSB0M3K06ELMOI65QXI1"
awsSsecret="706Fdj+LFKf8pf/2Wh5V8Q8jbgGUUQo3xSXr5sbt"

## Create the signature
message="GET\n\n\n${now}\n/${bucket}${path}"
messageSignature=$(echo -en "${message}" | openssl sha1 -hmac "${awsSsecret}" -binary | openssl base64)
echo $messageSignature

Returns >> 5oJM2L06LeTcbYSfN3fDFZ9yt5k=

enter image description here

## Powershell
$OutputEncoding = [Text.UTF8Encoding]::UTF8

$now='Fri, 26 Jul 2019 12:32:36 -0400'
$bucket="mybucket"
$path="/"
$awsKey="MSB0M3K06ELMOI65QXI1"
$awsSsecret="706Fdj+LFKf8pf/2Wh5V8Q8jbgGUUQo3xSXr5sbt"

## Create the signature
$message="GET\n\n\n${now}\n/${bucket}${path}"
$messageSignature=$(echo -en "${message}" | openssl sha1 -hmac "${awsSsecret}" -binary | openssl base64)
echo $messageSignature

Returns >> 77u/W8O5RwJeBsOodDddXeKUlCQD4pSUduKVrD3ilIxADQo=

enter image description here]2

On Ubuntu, my shell is running "en_US.UTF-8".

I've run into the case where the signature is different on different systems: AIX, Windows w/Ubuntu, Windows w/Powershell, etc. I'm trying to figure out why.


Solution

  • There are at least three problems here: Powershell pipelines aren't binary-safe, you have the wrong variable name, and echo isn't portable/reliable.

    1. At least according to this question, you can't pipe binary data in Powershell. The output of openssl (which is raw binary data) is getting treated as UTF-8 text (presumably due to $OutputEncoding), and mangled in the process. You can tell by decoding from base64 and looking in hex instead:

      $ echo '77u/W8O5RwJeBsOodDddXeKUlCQD4pSUduKVrD3ilIxADQo=' | base64 -D | xxd
      00000000: efbb bf5b c3b9 4702 5e06 c3a8 7437 5d5d  ...[..G.^...t7]]
      00000010: e294 9424 03e2 9494 76e2 95ac 3de2 948c  ...$....v...=...
      00000020: 400d 0a                                  @..
      

      It starts with EF BB BF, which is a UTF-8 byte order mark; and it ends with 0D 0A, which is a DOS/Windows line ending (ASCII carriage return and linefeed characters). Something else bad is happening as well, since it's much too long for a sha1 hash, even if you account for the BOM and line ending.

      The output of echo is probably getting mangled similarly, so even if the hash wasn't mangled it'd be the hash of the wrong byte sequence.

      See this question (and its answer) for an example of using Powershell's own tools to compute the HMAC-SHA1 of a string.

    2. The message to be signed is in $message, but you actually sign $string_to_sign, which is undefined. The Ubuntu result is the correct HMAC-SHA1 for the null string:

      $ </dev/null openssl sha1 -hmac "706Fdj+LFKf8pf/2Wh5V8Q8jbgGUUQo3xSXr5sbt" -binary | openssl base64
      NCRRWG4nL9sN8QMrdmCPmUvNlYA=
      
    3. As Lorinczy Zsigmond pointed out, echo -en isn't predictable. Under some implementations/OSes/shells/compile & runtime options/phases of moon/etc, it might or might not print "-en" as part of its output, and maybe also print a newline (or something) at the end. Use printf instead:

      printf 'GET\n\n\n%s\n/%s%s' "${now}" "${bucket}" "${path}" | openssl sha1 ...
      

      Or (in bash, but not all other shells):

      printf -v message 'GET\n\n\n%s\n/%s%s' "${now}" "${bucket}" "${path}"
      printf '%s' "$message" | openssl sha1 ...
      

      Or (bash again):

      nl=$'\n'
      message="GET${nl}${nl}${nl}${now}${nl}/${bucket}${path}"
      printf '%s' "$message" | openssl sha1 ...