Class: Rypt::Sha512
- Inherits:
-
Object
- Object
- Rypt::Sha512
- Defined in:
- lib/rypt/sha512.rb
Overview
Implements the standard crypt(3) call with SHA-512 and a salt. It consists of 3 main steps:
1. Calculate an initial set of 'special' values from the salt and password
2. Perform repeated stretching rounds using the 'special' values
3. Finalize the hash result by reordering the bytes and base-64 encoding them using a custom mapping scheme
At the end we are going to take the result of #3 and spit it out concatenated with the seed as the final result.
Class Method Summary collapse
Instance Method Summary collapse
- #encrypt(plain) ⇒ Object
- #extract_salt(encrypted) ⇒ Object
-
#finalize_block(hash_val) ⇒ Object
Don’t ask me how or why this works.
- #generate_salt ⇒ Object
- #init_hash(salt, pass) ⇒ Object
- #round(i, s, p, intermediate) ⇒ Object
- #run(salt_val, pass_val) ⇒ Object
Class Method Details
.compare(encrypted, plain) ⇒ Object
17 18 19 20 21 22 |
# File 'lib/rypt/sha512.rb', line 17 def self.compare(encrypted, plain) encryptor = self.new salt = encryptor.extract_salt(encrypted) return false unless salt encrypted == self.new.run(salt, plain) end |
.encrypt(plain) ⇒ Object
13 14 15 |
# File 'lib/rypt/sha512.rb', line 13 def self.encrypt(plain) self.new.encrypt(plain) end |
Instance Method Details
#encrypt(plain) ⇒ Object
95 96 97 |
# File 'lib/rypt/sha512.rb', line 95 def encrypt(plain) run(generate_salt, plain) end |
#extract_salt(encrypted) ⇒ Object
24 25 26 |
# File 'lib/rypt/sha512.rb', line 24 def extract_salt(encrypted) encrypted.split("$")[2] end |
#finalize_block(hash_val) ⇒ Object
Don’t ask me how or why this works. I don’t actually understand it. All I know is that it gives the right answer according to the tests cases. The original instructions on how to implement this algorithm are kind of hairy and not well explained, you’re welcome to injure yourself trying to understand them here:
http://www.akkadia.org/drepper/SHA-crypt.txt
Follow item #22 on, and be amazed at the pain.
72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rypt/sha512.rb', line 72 def finalize_block(hash_val) index_hash = [63, 62, 20, 41, 40, 61, 19, 18, 39, 60, 59, 17, 38, 37, 58, 16, 15, 36, 57, 56, 14, 35, 34, 55, 13, 12, 33, 54, 53, 11, 32, 31, 52, 10, 9, 30, 51, 50, 8, 29, 28, 49, 7, 6, 27, 48, 47, 5, 26, 25, 46, 4, 3, 24, 45, 44, 2, 23, 22, 43, 1, 0, 21, 42].reverse b64="./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" reordered_hash = (0..63).to_a.map { |i| hash_val[index_hash[i]] } char_array = [] reordered_hash.join.each_char { |e| char_array << e.unpack("b*") } ((char_array.join + "0000").reverse.scan(/.{1,6}/).map { |e| ((e).to_i 2) }.reverse.map { |i| b64[i] }).join end |
#generate_salt ⇒ Object
99 100 101 |
# File 'lib/rypt/sha512.rb', line 99 def generate_salt SecureRandom.hex(8) end |
#init_hash(salt, pass) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/rypt/sha512.rb', line 28 def init_hash(salt, pass) alt_sum = Digest::SHA2.new(512).digest(pass + salt + pass) s_length = [salt.length, 16].min p_length = [pass.length, 64].min int_sum_input = pass + salt + alt_sum[0..(p_length - 1)] p_length_array = p_length.to_s(2) p_length_array.reverse.each_char do |c| if (c == "1") int_sum_input << alt_sum else int_sum_input << pass end end intermediate_0 = Digest::SHA2.new(512).digest(int_sum_input) s_factor = 16 + intermediate_0[0].ord s_bytes_input = "" s_factor.times do s_bytes_input << salt end p_bytes_input = "" p_length.times do p_bytes_input << pass end s_bytes = Digest::SHA2.new(512).digest(s_bytes_input)[0..(s_length - 1)] p_bytes = Digest::SHA2.new(512).digest(p_bytes_input)[0..(p_length - 1)] [s_bytes, p_bytes, intermediate_0] end |
#round(i, s, p, intermediate) ⇒ Object
56 57 58 59 60 61 62 63 64 65 |
# File 'lib/rypt/sha512.rb', line 56 def round(i, s, p, intermediate) hash_input = "" hash_input << intermediate if ((i % 2) == 0) hash_input << p if ((i % 2) == 1) hash_input << s if ((i % 3) != 0) hash_input << p if ((i % 7) != 0) hash_input << p if ((i % 2) == 0) hash_input << intermediate if ((i % 2) == 1) (Digest::SHA2.new(512).digest(hash_input)) end |
#run(salt_val, pass_val) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/rypt/sha512.rb', line 83 def run(salt_val, pass_val) salt_trunc = salt_val[0..15] pass_trunc = pass_val sb, pb, i0 = init_hash(salt_trunc.force_encoding(Encoding::UTF_8), pass_trunc.force_encoding(Encoding::UTF_8)) hash_val = (0..4999).to_a.inject(i0) do |acc, i| val = round(i, sb, pb, acc) end "$6$" + salt_trunc + "$" + finalize_block(hash_val) end |