How to Create a Sha1 Hash in Ruby

How do I create a SHA1 hash in ruby?

require 'digest/sha1'
Digest::SHA1.hexdigest 'foo'

Generating an SHA1 hash under Ruby

false does not mean a failure to require the specified file... it simply means that the file has already been required successfully, and is available.

This should be what happens when you try the command:

crypto_hash = Digest::SHA1.hexdigest("hello")
=> "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"

SHA1 hashing in Rails

SHA is not encryption but rather it creates a cryptographic hash. If that is still what you want to do, my guess is that id and q_id are Fixnums and needs to be converted to strings.

@w = Digest::SHA1.hexdigest(ans.id.to_s + ans.q_id.to_s + ans.text + ans.session)

I also kind of like to use String literals because it makes it very obvious that we are dealing with a string

@w = Digest::SHA1.hexdigest("#{id}#{q_id}#{text}#{session}")

Create a SHA1 hash in Ruby via .net technique

A SHA1 hash of a specific piece of data is always the same hash (effectively just a large number), the only variation should be how you need to format it, to e.g. send to the other system. Although particularly obscure systems might post-process the data, truncate it or only take alternate bytes etc.

At a very rough guess from reading the C# code, this ends up a standard looking 40 character hex string. So my initial thought in Ruby is:

require 'digest/sha1'
Digest::SHA1.hexdigest("Some value to hash").upcase

. . . I don't know however what the force to ascii format in C# would do when it starts with e.g. a Latin-1 or UTF-8 string. They would be useful example inputs, if only to see C# throw an exception - you know then whether you need to worry about character encoding for your Ruby version. The data input to SHA1 is bytes, not characters, so which encoding to use and how to convert are parts of your problem.

My current understanding is that Encoding.ASCII.GetBytes will force anything over character number 127 to a '?', so you may need to emulate that in Ruby using a .gsub or similar, especially if you are actually expecting that to come in from the data source.

Rails sha1 hash generator

Sounds like you may want to use a before_save filter to invoke the hash class method on the Sdel model prior to saving when the attribute has been modified. Perhaps something along the lines of this:

require 'digest/sha1'

class Sdel < ActiveRecord::Base
attr_accessible :hashed_sdel

before_save { self.hashed_sdel = self.class.hash(hashed_sdel) if hashed_sdel_changed? }

def self.hash(sdel="")
Digest::SHA1.hexdigest(sdel)
end
end

This way, if you have a form that has a text_field for your hashed_sdel attribute, it will automatically get run through the hash class method you have before it the record gets saved (assuming the attribute has changed from it's previous value).

Different SHA1 hash result in Powershell and Ruby

The encoding makes no difference in this situation, as we're only dealing with ASCII characters, and they both encode ASCII the same way. The problem is

  1. Your data is different in both cases, note the spaces preceding Directory and ControlCase in your first example but not present in the second.

  2. You need to escape the backslashes in the ruby string, or it interprets them as escape characters

Once you resolve these two issues, you will get the same result:

PS:


PS H:\> $String = "
>>
>> Directory: D:\OneDrive -
>> ControlCase\jt-work\evidance-collection\evidances-text\PCI_Evidences_CCIN-CAS-VKAUS\evidences
>>
>>
>> Mode LastWriteTime Length Name
>> ---- ------------- ------ ----
>> -a--- 2019-01-16 1:14 PM 7073 21_to_calc_hash.ps1
>> -a--- 2019-01-16 1:15 PM 9973 CCIN-CAS-VKAUS_pci_evidence_Q21.txt
>> -a--- 2019-01-15 9:37 PM 67399 CCIN-CAS-VKAUS_pci_evidence_Q23.txt
>> -a--- 2019-01-15 9:37 PM 5055 CCIN-CAS-VKAUS_pci_evidence_Q34.txt
>> -a--- 2019-01-15 9:38 PM 10820 CCIN-CAS-VKAUS_pci_evidence_Q45.txt
>> -a--- 2019-01-15 9:38 PM 13129 CCIN-CAS-VKAUS_pci_evidence_Q50.txt
>> -a--- 2019-01-15 9:38 PM 7163 CCIN-CAS-VKAUS_pci_evidence_Q67.txt
>> -a--- 2019-01-15 9:39 PM 4301 CCIN-CAS-VKAUS_pci_evidence_Q69.txt
>> -a--- 2019-01-15 9:39 PM 2900 CCIN-CAS-VKAUS_pci_evidence_Q81.txt
>>
>> "
PS H:\> Get-Hash($string)
6454c0ecf1700448fb2496037a1e9ce496b185cd

Ruby:


>> varj = "
..
.. Directory: D:\\OneDrive -
.. ControlCase\\jt-work\\evidance-collection\\evidances-text\\PCI_Evidences_CCIN-CAS-VKAUS\\evidences
..
..
.. Mode LastWriteTime Length Name
.. ---- ------------- ------ ----
.. -a--- 2019-01-16 1:14 PM 7073 21_to_calc_hash.ps1
.. -a--- 2019-01-16 1:15 PM 9973 CCIN-CAS-VKAUS_pci_evidence_Q21.txt
.. -a--- 2019-01-15 9:37 PM 67399 CCIN-CAS-VKAUS_pci_evidence_Q23.txt
.. -a--- 2019-01-15 9:37 PM 5055 CCIN-CAS-VKAUS_pci_evidence_Q34.txt
.. -a--- 2019-01-15 9:38 PM 10820 CCIN-CAS-VKAUS_pci_evidence_Q45.txt
.. -a--- 2019-01-15 9:38 PM 13129 CCIN-CAS-VKAUS_pci_evidence_Q50.txt
.. -a--- 2019-01-15 9:38 PM 7163 CCIN-CAS-VKAUS_pci_evidence_Q67.txt
.. -a--- 2019-01-15 9:39 PM 4301 CCIN-CAS-VKAUS_pci_evidence_Q69.txt
.. -a--- 2019-01-15 9:39 PM 2900 CCIN-CAS-VKAUS_pci_evidence_Q81.txt
..
.. "
>> puts Digest::SHA1.hexdigest(varj.encode(Encoding::UTF_8))
=> 6454c0ecf1700448fb2496037a1e9ce496b185cd
>> puts Digest::SHA1.hexdigest(varj.encode(Encoding::ISO_8859_1))
=> 6454c0ecf1700448fb2496037a1e9ce496b185cd

Edit:

If you're still not able to match, I think the best approach is to compare the byte values of each string to identify differences.

PS:


PS H:\> $enc = [system.Text.Encoding]::UTF8
PS H:\> $enc.GetBytes($String)
10
10
32
32
...

Ruby:


>> varj_Encoded.bytes.to_a
=> [10, 10, 32, 32, ...

Flatten deep nested hash to array for sha1 hashing

EDIT : As you detailed, two hashes with keys in different order should give the same string. I would reopen the Hash class to add my new custom flatten method :

class Hash
def custom_flatten()
self.sort.map{|pair| ["key: #{pair[0]}", pair[1]]}.flatten.map{ |elem| elem.is_a?(Hash) ? elem.custom_flatten : elem }.flatten
end
end

Explanation :

  • sort converts the hash to a sorted array of pairs (for the comparison of hashes with different keys order)
  • .map{|pair| ["key: #{pair[0]}", pair[1]]} is a trick to differentiate keys from values in the final flatten array, to avoid the problem of {a: {b: {c: :d}}}.custom_flatten == {a: :b, c: :d}.custom_flatten
  • flatten converts an array of arrays into a single array of values
  • map{ |elem| elem.is_a?(Hash) ? elem.custom_flatten : elem } calls back fully_flatten on any sub-hash left.

Then you just need to use :

require 'digest/sha1'
Digest::SHA1.hexdigest hash.custom_flatten.to_s

How can I with Ruby check if SHA1 username, password and verifier/salt is correct?

The docs you link to describe the algorithm to calculate the verifier as:

verifier

verifier is derived from salt, as well as the user's username (all
uppercase) and their password (all uppercase).

To obtain the verifier you need to calculate:

Calculate h1 = SHA1("USERNAME:PASSWORD"), substituting the user's
username and password converted to uppercase.

Calculate h2 = SHA1(salt || h1), where || is concatenation (the .
operator in PHP).

NOTE: Both salt and h1 are binary, not hexadecimal strings!

Treat h2 as an integer in little-endian order (the first byte is the
least significant).

Calculate (g ^ h2) % N.

NOTE: g and N are parameters, which are fixed in the WoW
implementation.

g = 7

N = 0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7

Convert the result back to a byte array in little-endian order.

Based on the links to sample implementations, this appears to be the verifier from SRP6, which you might be able to use this gem for: https://github.com/grempe/sirp. However, it doesn't line up exactly with the docs, and I think this might be interesting, so I'll try to work through it anyway.

First, you've got a start on finding h1 and h2, but as the NOTE says, Both salt and h1 are binary, not hexadecimal strings!. So, you'll want to replace hexdigest with digest. Also, the uppercase method in Ruby is upcase and you'll need to put a colon between the two:

h1 = Digest::SHA1.digest("#{username.upcase}:#{password.upcase}")
h2 = Digest::SHA1.digest(account.salt + h1)

Next, it says to turn h2 into an integer as if it was stored in little-endian. Remember that integers are stored as sequences of bytes, each byte being 8 bits; so a 32-bit integer is 4 bytes. Endianness describes if the first byte maps to the first 8 bits or the last 8 bits of the number. Here, the comment makes it clear it'll be the last. Now, SHA1 produces a 20-byte hash, so we'll use unpack method plus the H directive (which matches each hex byte) to get it all out.

h2_int = h2.reverse.unpack("H*").first.to_i(16)

Lastly, we do some math with the given constants and convert it back to a string. The ^ % construction must be modular exponentiation, which you can do in Ruby 2.5+ with just Integer#pow, or in Ruby 2.4 below with openssl's mod_exp:

g = 7
n = 0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7

verifier_int = g.pow(h2_int, n)

# ruby 2.4 or below:
#
# require 'openssl'
# verifier_int = g.to_bn.mod_exp(h2_int, n).to_i

verifier = [verifier_int.to_s(16)].pack('H*').reverse

Put that all together:

h1 = Digest::SHA1.digest("#{username.upcase}:#{password.upcase}")
h2 = Digest::SHA1.digest(account.salt + h1)

h2_int = h2.reverse.unpack("H*").first.to_i(16)

g = 7
n = 0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7

verifier_int = g.pow(h2_int, n)

# ruby 2.4 or below:
#
# require 'openssl'
# verifier_int = g.to_bn.mod_exp(h2_int, n).to_i

verifier = [verifier_int.to_s(16)].pack('H*').reverse

With this code, I was able to verify that for the values:

username = "testaccount"
password = "testaccount"
account.salt = "\xB1V\x940(|\x0F\xA0\xD6|\x7F\x86\xADO'\x82':(\xCCW\xA0\x85\xE1\xB2\xE20\x1A|3g\x1C" # [0xb1569430287c0fa0d67c7f86ad4f2782273a28cc57a085e1b2e2301a7c33671c.to_s(16)].unpack('H*')

The produced verifier matches the expected verifier of:

"\xB6\x95\xFF\xEB\x8E\xA76u\x8F\xFB\x0F:\xE34M\t\xC0?\xE8\xD2\xF1\xD1\x8C\x058P\x8F\xCDyQ H"

In Rails

After testing this out, the following code works for me. Note that I've casted the encoding of salt to ascii-8bit here, instead of verifier to utf-8, but it doesn't change the result. However, for the final comparison, the encodings do need to be the same.

class Account < ApplicationRecord
def verify(password)
h1 = Digest::SHA1.digest("#{username.upcase}:#{password.upcase}")
h2 = Digest::SHA1.digest(salt.force_encoding('ascii-8bit') + h1)

h2_int = h2.reverse.unpack("H*").first.to_i(16)

g = 7
n = 0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7

verifier_int = g.pow(h2_int, n)
verifier = [verifier_int.to_s(16)].pack('H*').reverse

verifier == self.verifier
end
end

How to truncate a SHA1 hashed string into a 32 bit string

If you want a string with 32 bits out of your (weak) password :

Digest::SHA1.digest('I am a string').unpack('B32').first
#=> "10111101100000101111101100001110"

The same amount of information can also be displayed with 8 hexadecimal digits :

Digest::SHA1.hexdigest('I am a string')[0,8]
#=> "bd82fb0e"

or 4 ascii chars :

Digest::SHA1.digest('I am a string')[0,4]
#=> "\xBD\x82\xFB\x0E"


Related Topics



Leave a reply



Submit