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
Your data is different in both cases, note the spaces preceding
Directory
andControlCase
in your first example but not present in the second.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 valuesmap{ |elem| elem.is_a?(Hash) ? elem.custom_flatten : elem }
calls backfully_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
How to Use Global Variables or Constant Values in Ruby
Uploading Multiple Files With Paperclip
Haml: Append Class If Condition Is True in Haml
Unzip (Zip, Tar, Tag.Gz) Files With Ruby
What Are the Magic $-Prefixed Variables in Ruby
Read and Write Yaml Files Without Destroying Anchors and Aliases
Rails Sends 0 Byte Files Using Send_File
Gemfile.Lock Write Error, Permissions
Ruby: How to Turn a Hash into Http Parameters
Getting Fields_For and Accepts_Nested_Attributes_For to Work With a Belongs_To Relationship
Using Www:Mechanize to Download a File to Disk Without Loading It All in Memory First
One-Liner to Recursively List Directories in Ruby
In Ruby, Is There an Array Method That Combines 'Select' and 'Map'
How to Access a Variable Defined in a Ruby File I Required in Irb