def self.crypt(password, algo = :md5, salt = nil, magic='$1$')
salt ||= generate_salt(8)
case algo
when :md5
require "digest/md5"
when :sha1
require "digest/sha1"
when :rmd160
require "digest/rmd160"
when :sha256, :sha384, :sha512
require "digest/sha2"
else
raise(ArgumentError, "unknown algorithm")
end
digest_class = Digest.const_get(algo.to_s.upcase)
m = digest_class.new
m.update(password + magic + salt)
mixin = digest_class.new.update(password + salt + password).digest
password.length.times do |i|
m.update(mixin[i % 16].chr)
end
i = password.length
while i != 0
if (i & 1) != 0
m.update("\x00")
else
m.update(password[0].chr)
end
i >>= 1
end
final = m.digest
1000.times do |i|
m2 = digest_class.new
if (i & 1) != 0
m2.update(password)
else
m2.update(final)
end
if (i % 3) != 0
m2.update(salt)
end
if (i % 7) != 0
m2.update(password)
end
if (i & 1) != 0
m2.update(final)
else
m2.update(password)
end
final = m2.digest
end
rearranged = ""
[ [0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5] ].each do |a, b, c|
v = final[a] << 16 | final[b] << 8 | final[c]
4.times do
rearranged += ITOA64[v & 0x3f].chr
v >>= 6
end
end
v = final[11]
2.times do
rearranged += ITOA64[v & 0x3f].chr
v >>= 6
end
magic + salt + '$' + rearranged
end