10

I have been comparing hash values between multiple systems and was surprised to find that PowerShells hash values are different than that of other terminals.

Linux terminals (CygWin, Bash for Windows, etc.) and Windows Command Prompt are all showing the same hash where as PowerShell is showing a different hash value.

Linux_Vs_PShell_Hash_Compare.png

This was tested using SHA256 but found the same issue when using other algorithms like md5.

Encoding Update:

Tried changing the PShell encoding but it did not have any effect on the returned hash values.

[Console]::OutputEncoding.BodyName 
iso-8859-1
[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8
utf-8

GitHub PowerShell Issue

https://github.com/PowerShell/PowerShell/issues/5974

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Pie
  • 856
  • 4
  • 12
  • 33
  • 2
    You know that in PowerShell, `echo -n` is actually `Write-Output -NoEnumerate` due to aliases, right? – Bacon Bits Jan 21 '18 at 20:52
  • Possible duplicate of [Changing PowerShell's default output encoding to UTF-8](https://stackoverflow.com/questions/40098771/changing-powershells-default-output-encoding-to-utf-8), [UTF-8 output from PowerShell](https://stackoverflow.com/q/22349139/608639), [Set the encoding to ANSI in PowerShell 2.0](https://stackoverflow.com/q/37700256/608639), etc. – jww Jan 21 '18 at 21:19
  • Tried changing the encoding to UTF-8 but it doesn't change the hash results. – Pie Jan 22 '18 at 00:09

2 Answers2

8

tl;dr:

When PowerShell pipes a string to an external program:

Therefore, the key is to avoid PowerShell's pipeline in favor of the native shell's, so as to prevent implicit addition of a trailing newline:

  • If you're running your command on a Unix-like platform (using PowerShell Core):
sh -c "printf %s 'string' | openssl dgst -sha256 -hmac authcode"

printf %s is the portable alternative to echo -n. If the string contains ' chars., double them or use `"...`" quoting instead.

  • In case you need to do this on Windows via cmd.exe, things get even trickier, because cmd.exe doesn't directly support echoing without a trailing newline:
cmd /c "<NUL set /p =`"string`"| openssl dgst -sha256 -hmac authcode"

Note that there must be no space before | for this to work. For an explanation and the limitations of this solution, see this answer.

Encoding issues would only arise if the string contained non-ASCII characters and you're running in Windows PowerShell; in that event, first set $OutputEncoding to the encoding that the target utility expects, typically UTF-8: $OutputEncoding = [Text.Utf8Encoding]::new()


  • PowerShell, as of Windows PowerShell v5.1 / PowerShell (Core) v7.2, invariably appends a trailing newline when you send a string without one via the pipeline to an external utility, which is the reason for the difference you're observing (that trailing newline will be a LF only on Unix platforms, and a CRLF sequence on Windows).

    • You can keep track of efforts to address this problem in GitHub issue #5974, opened by the OP.
  • Additionally, PowerShell's pipeline is invariably text-based when it comes to piping data to external programs; the internally UTF-16LE-based PowerShell (.NET) strings are transcoded based on the encoding stored in the automatic $OutputEncoding variable, which defaults to ASCII-only encoding in Windows PowerShell, and to UTF-8 encoding in PowerShell Core (both on Windows and on Unix-like platforms).

  • The fact that echo -n in PowerShell does not produce a string without a trailing newline is therefore incidental to your problem; for the sake of completeness, here's an explanation:

    • echo is an alias for PowerShell's Write-Output cmdlet, which - in the context of piping to external programs - writes text to the standard input of the program in the next pipeline segment (similar to Bash / cmd.exe's echo).
    • -n is interpreted as an (unambiguous) abbreviation for Write-Output's -NoEnumerate switch.
    • -NoEnumerate only applies when writing multiple objects, so it has no effect here.
    • Therefore, in short: in PowerShell, echo -n "string" is the same as Write-Output -NoEnumerate "string", which - because only a single string is output - is the same as Write-Output "string", which, in turn, is the same as just using "string", relying on PowerShell's implicit output behavior.
    • Write-Output has no option to suppress a trailing newline, and even if it did, using a pipeline to pipe to an external program would add it back in.
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Linux terminals and PowerShell use different encodings. So real bytes produced by echo -n "string" are different. I tried it on my Linux Mint terminal and Windows 10 PowerShell. Here what I got:

Linux Mint:

73 74 72 69 6E 67

Windows 10:

FF FE 73 00 74 00 72 00 69 00 6E 00 67 00 0D 00 0A 00

It seems that Linux terminals use UTF-8 and Windows PowerShell uses UTF-16 with a BOM. Also in PowerShell you cannot use '-n' parameter for echo. So echo places newline characters \r\n (0D 00 0A 00) at the end of the "string".

Edit: As mklement0 said below, Windows PowerShell uses ASCII by default when piping.

Igor
  • 600
  • 6
  • 13
  • I'm not a PowerShell person, but everything I have seen about it is awful for a person from a Unix/Linux background who expects it to act like shell scripting. I gave up on PowerShell once I saw what I had to do for a simple `grep` and `sed`. – jww Jan 21 '18 at 21:38
  • 1
    PowerShell does _not_ use UTF-16LE when _piping_ to external programs, at least by default. The automatic `$OutputEncoding` variable determines the encoding that is used when piping to external programs such as `openssl`, which defaults to ASCII in _Windows_ PowerShell, and UTF-8 in PowerShell _Core_. It is indeed the trailing newline that is invariably added that causes the problem here; you _can_ use `-n` with `echo` in PowerShell, but its semantics are different (`echo -n` is the equivalent of `Write-Output -NoEnumerate`). – mklement0 Jan 21 '18 at 22:58
  • 1
    @jwww: PowerShell is used to being its own world, historically speaking; with its transition to being cross-platform, it still has a few things to learn when it comes to interacting with external utilities, which are much more important in the Unix world than they are in the Windows world. I encourage you to help that process along at https://github.com/PowerShell/PowerShell/issues – mklement0 Jan 21 '18 at 23:18
  • 2
    @Pie: Thanks; to recap: the real issue is that the _PowerShell pipeline_ itself _when piping to external programs_ appends a trailing newline; it is not an _encoding_ problem (except possibly in _Windows PowerShell_ with a string that contains _non-ASCII_ characters). Because this answer focuses on encoding (and misrepresents how transcoding works with _external_ programs), it's a -1 from me. – mklement0 Jan 22 '18 at 17:25