生成具有 Ubuntu/Bash 和 Windows/Powershell 之间不同结果的 SHA1 签名

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

为什么这些各自的 bash 和 powershell 脚本 return 不同的签名:

## (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=

## 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=

]2

在Ubuntu,我的shell是运行宁"en_US.UTF-8"。

我已经 运行 处理过签名在不同系统上不同的情况:AIX、Windows w/Ubuntu、Windows w/Powershell 等.我想弄清楚为什么。

这里至少存在三个问题:Powershell 管道不是二进制安全的,变量名错误,echo 不是 portable/reliable。

  1. 至少根据 this question,您不能在 Powershell 中传输二进制数据。 openssl 的输出(原始二进制数据)被视为 UTF-8 文本(可能是由于 $OutputEncoding),并在此过程中被破坏。您可以通过从 base64 解码并查看十六进制来判断:

    $ 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                                  @..
    

    EF BB BF开头,是一个UTF-8 byte order mark;它以 0D 0A 结尾,这是一个 DOS/Windows 行结尾(ASCII 回车符 return 和换行符)。其他不好的事情也在发生,因为它对于 sha1 哈希来说太长了,即使你考虑了 BOM 和行结束。

    echo 的输出可能被类似地破坏了,所以即使散列没有被破坏,它也会是错误字节序列的散列。

    有关使用 Powershell 自己的工具计算字符串的 HMAC-SHA1 的示例,请参阅

  2. 要签名的消息在$message,但你实际签名$string_to_sign,这是未定义的。 Ubuntu 结果是空字符串的正确 HMAC-SHA1:

    $ </dev/null openssl sha1 -hmac "706Fdj+LFKf8pf/2Wh5V8Q8jbgGUUQo3xSXr5sbt" -binary | openssl base64
    NCRRWG4nL9sN8QMrdmCPmUvNlYA=
    
  3. 正如 Lorinczy Zsigmond 指出的那样,echo -en 是不可预测的。在 implementations/OSes/shells/compile 和运行时 options/phases of moon/etc 下,它可能会或可能不会打印“-en”作为其输出的一部分,并且可能还会在末尾打印换行符(或其他内容) .使用 printf 代替:

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

    或者(在 bash 中,但不是所有其他 shell):

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

    或(再次bash):

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

为偶然发现此主题的任何人提供摘要。问题是....

  1. 无法通过管道传递二进制数据,如 Gordon Davisson 所述
  2. 需要用“`n”而不是“\n”来表示换行符

最终在 Ubuntu/Bash 中复制我的结果的 Powershell 脚本是...

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

## Create the signature
$sha = [System.Security.Cryptography.KeyedHashAlgorithm]::Create("HMACSHA1")
$sha.Key = [System.Text.Encoding]::UTF8.Getbytes($awsSsecret)
[Convert]::Tobase64String($sha.ComputeHash([System.Text.Encoding]::UTF8.Getbytes($message)))

虽然在 Linux/Unix 系统上使用 printf 而不是 echo 是更好的主意,但在这个用例中没有 material 的区别。