97 lines
2.4 KiB
Ruby
97 lines
2.4 KiB
Ruby
|
require 'digest'
|
||
|
|
||
|
# Constants
|
||
|
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||
|
|
||
|
def to64(value, length)
|
||
|
result = ""
|
||
|
length.times do
|
||
|
result << ITOA64[value & 0x3f]
|
||
|
value >>= 6
|
||
|
end
|
||
|
result
|
||
|
end
|
||
|
|
||
|
def gossamer_md5_crypt(password, legacy_hash)
|
||
|
# Extract the salt from the legacy hash
|
||
|
parts = legacy_hash.split('$')
|
||
|
salt = parts[2]
|
||
|
|
||
|
# Limit the salt to 8 characters
|
||
|
salt = salt[0, 8]
|
||
|
|
||
|
magic = "$GT$"
|
||
|
|
||
|
ctx = Digest::MD5.new
|
||
|
ctx.update(password)
|
||
|
ctx.update(magic)
|
||
|
ctx.update(salt)
|
||
|
|
||
|
final = Digest::MD5.new
|
||
|
final.update(password)
|
||
|
final.update(salt)
|
||
|
final.update(password)
|
||
|
final_digest = final.digest
|
||
|
|
||
|
password_length = password.length
|
||
|
|
||
|
while password_length > 0
|
||
|
ctx.update(final_digest[0, [password_length, 16].min])
|
||
|
password_length -= 16
|
||
|
end
|
||
|
|
||
|
password_length = password.length
|
||
|
while password_length > 0
|
||
|
if password_length & 1 != 0
|
||
|
ctx.update("\x00")
|
||
|
else
|
||
|
ctx.update(password[0, 1])
|
||
|
end
|
||
|
password_length >>= 1
|
||
|
end
|
||
|
|
||
|
final_digest = ctx.digest
|
||
|
|
||
|
1000.times do |i|
|
||
|
ctx1 = Digest::MD5.new
|
||
|
if i & 1 != 0
|
||
|
ctx1.update(password)
|
||
|
else
|
||
|
ctx1.update(final_digest)
|
||
|
end
|
||
|
ctx1.update(salt) if i % 3 != 0
|
||
|
ctx1.update(password) if i % 7 != 0
|
||
|
if i & 1 != 0
|
||
|
ctx1.update(final_digest)
|
||
|
else
|
||
|
ctx1.update(password)
|
||
|
end
|
||
|
final_digest = ctx1.digest
|
||
|
end
|
||
|
|
||
|
result = ''
|
||
|
result << to64((final_digest[0].ord << 16) | (final_digest[6].ord << 8) | final_digest[12].ord, 4)
|
||
|
result << to64((final_digest[1].ord << 16) | (final_digest[7].ord << 8) | final_digest[13].ord, 4)
|
||
|
result << to64((final_digest[2].ord << 16) | (final_digest[8].ord << 8) | final_digest[14].ord, 4)
|
||
|
result << to64((final_digest[3].ord << 16) | (final_digest[9].ord << 8) | final_digest[15].ord, 4)
|
||
|
result << to64((final_digest[4].ord << 16) | (final_digest[10].ord << 8) | final_digest[5].ord, 4)
|
||
|
result << to64(final_digest[11].ord, 2)
|
||
|
|
||
|
"#{magic}#{salt}$#{result}"
|
||
|
end
|
||
|
|
||
|
def verify_gossamer_password(password, legacy_hash)
|
||
|
generated_hash = gossamer_md5_crypt(password, legacy_hash)
|
||
|
generated_hash == legacy_hash
|
||
|
end
|
||
|
|
||
|
# Example usage
|
||
|
legacy_hash = "$GT$i5ZNZdfX$077FK6JU70HIr2pR1/uLP1"
|
||
|
password = "polyflex80"
|
||
|
|
||
|
resulting_hash = gossamer_md5_crypt(password, legacy_hash)
|
||
|
puts "Generated hash: #{resulting_hash}"
|
||
|
puts "Legacy hash: #{legacy_hash}"
|
||
|
puts "Password verification: #{verify_gossamer_password(password, legacy_hash)}"
|
||
|
|