8

When my scripts run, I read a hash, and I would like to write it to the registry. I figured out that the following command will do it:

New-ItemProperty  $RegPath -Name $AttrName -PropertyType Binary -Value $byteArray

I also found the How to set a binary registry value (REG_BINARY) with PowerShell?.

However all answers suppose that the string is in form of:

"50,33,01,00,00,00,00,00,...."

but I only can read my hash in the following form:

"F5442930B1778ED31A....."

I can not figure out, how can I convert this to a byte array, with values F5, 44 etc.

g.pickardou
  • 32,346
  • 36
  • 123
  • 268
  • is a hash supposed to be a binary object? i thot they were supposed to be hexadecimal numbers or strings ... – Lee_Dailey Feb 05 '19 at 21:21
  • 1
    Why not use reg_sz and store as a string? That being said, SANS has [good article](https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert) about bytes and hex. – vonPryz Feb 05 '19 at 21:29
  • @vonPryz it is an MS setting, format is not up to me unfortunately. – g.pickardou Feb 06 '19 at 04:30
  • @Lee_Dailey hash's are always binary, and tools like this should always make them available in that format. Hex is just one way of making that data "human" readable (really just so it doesn't corrupt the console with control characters). Other than hex you'll usually see it in base64 for transport or textfile storage, which is what I needed (I used the answers here to get it as a byte array, so I could convert byte array to base64) – Hashbrown Jan 28 '22 at 06:04
  • @Hashbrown - ah! i learned something today ... thank you! [*grin*] – Lee_Dailey Jan 28 '22 at 11:58

4 Answers4

24

vonPryz sensibly suggests simply storing the hash directly as a string (REG_SZ) in the registry.

If you really want to store the data as type REG_BINARY, i.e., as an array of bytes, you must convert back and forth between the string representation.

To convert to a [byte[]] array (using a shortened sample hash string):

PS> [byte[]] -split ('F54429' -replace '..', '0x$& ')
245 # 1st byte: decimal representation of 0xF5
68  # 2nd byte: decimal representation of 0x44
41  # ...

-replace '..', '0x$& ' prefixes each pair of characters (hex digits) - .., referenced in the replacement operand as $& - with 0x and inserts a space afterwards. -split splits the resulting string into an array of 0xHH strings (H representing a hex digit), which PowerShell's automatic type conversions recognize as the elements of a [byte[]] array in a cast. In other words: the above is the equivalent of:
[byte[]] ('0xF5', '0x44', '0x29')

The above is PowerShell's default output representation of resulting array
[byte[]] (0xF5, 0x44, 0x29).

Update: In PowerShell (Core) 7.1+ / .NET 5+ , a simpler solution is now available, via the new [System.Convert]::FromHexString() and [System.Convert]::ToHexString() methods:

# PowerShell (Core) 7.1+ / .NET 5+
# -> (0xF5, 0x44, 0x29)
[System.Convert]::FromHexString('F54429')

# -> 'F54429'
[System.Convert]::ToHexString([byte[]] (0xF5, 0x44, 0x29))

To convert from a [byte[]] array (back to a string):

[System.BitConverter]::ToString() outputs two-hex-digit representations separated with -, so to get the desired representation, all - instances must be removed, using a -replace operation here:

# -> 'F54429'
[System.BitConverter]::ToString([byte[]] (0xf5, 0x44, 0x29)) -replace '-'

To put it all together:

# Sample hash string.
$hashString = 'F54429'

# Convert the hash string to a byte array.
$hashByteArray = [byte[]] -split ($hashString -replace '..', '0x$& ')

# Create a REG_BINARY registry value from the byte array.
Set-ItemProperty -LiteralPath HKCU:\ -Name tmp -Type Binary -Value $hashByteArray

# Read the byte array back from the registry (PSv5+)
$hashByteArray2 = Get-ItemPropertyValue -LiteralPath HKCU:\ -Name tmp

# Convert it back to a string.
$hashString2 = [System.BitConverter]::ToString($hashByteArray2) -replace '-'

# (Clean up.)
Remove-ItemProperty -LiteralPath HKCU:\ -Name tmp
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

To parse a hex string without the weight of a regular expression:

# parse the hex string into a BigInt
# leading zeros avoids parsing as negative
$bytes = [bigint]::Parse("00$value",'HexNumber').ToByteArray()

# undo the reversed bytes
[array]::Reverse($bytes)

# drop the leading zero byte
$null,$bytes = $bytes
brianary
  • 8,996
  • 2
  • 35
  • 29
1

.Net has a build-in function for that. It's a bit hidden...

$x = 'F5442930B1778ED31A'
$bytes = [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::Parse($x).Value

..and the other direction goes like this:

[System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::new($bytes).ToString()
Carsten
  • 1,612
  • 14
  • 21
0

Here is another approach that doesn't use regular expressions.

function HexToByteArray([string]$hex) {
(0..([Math]::Floor( ($hex.Length+1)/2)-1)).ForEach({[Convert]::ToByte($(if ($hex.Length -ge 2*($_+1)) {$hex.Substring($_*2,2)} else {$hex.Substring($_*2,1).PadRight(2,'0')}),16)})

}

It's possibly more robust than you might want - it tries to support odd-length input strings. You might prefer to disallow odd-length input strings, in which case the conversion expression could be simplified.

  • 1
    There are two good reasons to avoid regular expressions: (a) conceptual complexity and (b) performance. Your solution appears to help in neither respect. – mklement0 Jan 30 '22 at 00:58