Module: GoSecure

Defined in:
lib/go_secure.rb

Defined Under Namespace

Modules: SecureJson, SecureSerializeHelpers, SerializeClassMethods, SerializeInstanceMethods

Class Method Summary collapse

Class Method Details

.browser_tokenObject



121
122
123
124
125
126
# File 'lib/go_secure.rb', line 121

def self.browser_token
  # TODO: checks around whether it's actually a web browser??
  day = Time.now.strftime('%j')
  stamp = "#{Time.now.year}#{(Time.now.yday / 366.0 * 100.0).to_i.to_s.rjust(2, '0')}"
  stamp += '-' + GoSecure.sha512(stamp, 'browser_token')
end

.decrypt(str, salt, ref, encryption_key = nil) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/go_secure.rb', line 41

def self.decrypt(str, salt, ref, encryption_key=nil)
  require 'base64'
  c = OpenSSL::Cipher.new('aes-256-cbc')
  c.decrypt
  sha = Digest::SHA2.hexdigest(ref + "_" + (encryption_key || self.encryption_key))
  c.key = sha[0..31]
  iv = Base64.decode64(salt)

  c.iv = iv[0..15]
  d = c.update(Base64.decode64(str))
  d << c.final
  d.to_s
end

.encrypt(str, ref, encryption_key = nil, iv = nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/go_secure.rb', line 26

def self.encrypt(str, ref, encryption_key=nil, iv=nil)
  require 'base64'
  c = OpenSSL::Cipher.new('aes-256-cbc')
  c.encrypt
  sha = Digest::SHA2.hexdigest(ref + "_" + (encryption_key || self.encryption_key))
  c.key = sha[0..31]
  raise "invalid iv" if iv && iv.length != 16
  iv = iv || c.random_iv
  c.iv = iv
  e = c.update(str)
  e << c.final
  res = [Base64.encode64(e), Base64.encode64(iv)]
  res
end

.encryption_keyObject



117
118
119
# File 'lib/go_secure.rb', line 117

def self.encryption_key
  ENV['SECURE_ENCRYPTION_KEY']
end

.generate_password(password) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/go_secure.rb', line 55

def self.generate_password(password)
  raise "password required" if password == nil || password.length == 0
  pw = {}
#     pw['hash_type'] = 'sha512'
#     pw['hash_type'] = 'bcrypt'
  pw['hash_type'] = 'pbkdf2-sha256-2'
  pw['salt'] = Digest::MD5.hexdigest(OpenSSL::Random.random_bytes(4) + Time.now.to_i.to_s + self.encryption_key + "pw" + OpenSSL::Random.random_bytes(16))
#     pw['hashed_password'] = Digest::SHA512.hexdigest(self.encryption_key + pw['salt'] + password.to_s)
#     salted = Digest::SHA256.hexdigest(self.encryption_key + pw['salt'] + password.to_s)
#     pw['hashed_password'] = BCrypt::Password.create(salted)
  digest = OpenSSL::Digest::SHA512.new(self.encryption_key)
  pw['hashed_password'] = Base64.urlsafe_encode64(OpenSSL::PKCS5.pbkdf2_hmac(password.to_s, pw['salt'], 100000, digest.digest_length, digest))
  pw
end

.hmac(str, salt, level, encryption_key = nil) ⇒ Object



10
11
12
13
14
15
# File 'lib/go_secure.rb', line 10

def self.hmac(str, salt, level, encryption_key=nil)
  # level is here so we can upgrade in the future without breaking backwards compatibility
  raise "invalid level" unless level == 1
  digest = OpenSSL::Digest::SHA512.new(encryption_key || self.encryption_key)
  res = Base64.urlsafe_encode64(OpenSSL::PKCS5.pbkdf2_hmac(str.to_s, salt.to_s, 100000, digest.digest_length, digest))
end

.lite_hmac(str, salt, level, encryption_key = nil) ⇒ Object



17
18
19
20
# File 'lib/go_secure.rb', line 17

def self.lite_hmac(str, salt, level, encryption_key=nil)
  raise "invalid level" unless level == 1
  OpenSSL::HMAC.hexdigest('SHA512', OpenSSL::HMAC.hexdigest('SHA512', str.to_s, salt.to_s), encryption_key || self.encryption_key)
end

.matches_password?(attempt, password_hash) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/go_secure.rb', line 74

def self.matches_password?(attempt, password_hash)
  if password_hash && password_hash['hash_type'] == 'sha512' && password_hash['salt']
    str = Digest::SHA512.hexdigest(self.encryption_key + password_hash['salt'] + attempt.to_s)
    res = str == password_hash['hashed_password']
    if !res && password_hash['old_passwords']
      # TODO: support for migrating to new hashing algorithms
    else
      res
    end
  elsif password_hash && password_hash['hash_type'] == 'bcrypt' && password_hash['salt']
    pw = BCrypt::Password.new(password_hash['hashed_password'])
    salted = Digest::SHA256.hexdigest(self.encryption_key + password_hash['salt'] + attempt.to_s)
    res = pw == salted
  elsif password_hash && password_hash['hash_type'] == 'pbkdf2-sha256' && password_hash['salt']
    digest = OpenSSL::Digest::SHA256.new
    str = Base64.encode64(OpenSSL::PKCS5.pbkdf2_hmac(attempt.to_s, password_hash['salt'], 100000, digest.digest_length, digest))
    res = str == password_hash['hashed_password']
  elsif password_hash && password_hash['hash_type'] == 'pbkdf2-sha256-2' && password_hash['salt']
    digest = OpenSSL::Digest::SHA512.new(self.encryption_key)
    str = Base64.urlsafe_encode64(OpenSSL::PKCS5.pbkdf2_hmac(attempt.to_s, password_hash['salt'], 100000, digest.digest_length, digest))
    res = str == password_hash['hashed_password']
  else
    false
  end
end

.nonce(str) ⇒ Object



22
23
24
# File 'lib/go_secure.rb', line 22

def self.nonce(str)
  Digest::SHA512.hexdigest(str.to_s + Time.now.to_i.to_s + rand(999999).to_s + self.encryption_key)[0, 24]
end

.outdated_password?(password_hash) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/go_secure.rb', line 70

def self.outdated_password?(password_hash)
  return password_hash && password_hash['hash_type'] != 'pbkdf2-sha256-2'
end

.sha512(str, salt, encryption_key = nil) ⇒ Object



6
7
8
# File 'lib/go_secure.rb', line 6

def self.sha512(str, salt, encryption_key=nil)
  Digest::SHA512.hexdigest(str.to_s + salt.to_s + (encryption_key || self.encryption_key))
end

.valid_browser_token?(token) ⇒ Boolean

Returns:

  • (Boolean)


133
134
135
136
137
138
139
140
141
# File 'lib/go_secure.rb', line 133

def self.valid_browser_token?(token)
  return false if !token || token.length == 0 || !token.match(/-/)
  stamp, hash = token.split(/-/, 2)
  current_stamp = "#{Time.now.year}#{(Time.now.yday / 366.0 * 100.0).to_i.to_s.rjust(2, '0')}"
  if current_stamp.to_i - stamp.to_i < (14/365.0*100.0) # 14 days?!
    return valid_browser_token_signature?(token)
  end
  false
end

.valid_browser_token_signature?(token) ⇒ Boolean

Returns:

  • (Boolean)


128
129
130
131
# File 'lib/go_secure.rb', line 128

def self.valid_browser_token_signature?(token)
  stamp, hash = token.split(/-/, 2)
  return hash == GoSecure.sha512(stamp, 'browser_token')
end

.validate_encryption_keyObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/go_secure.rb', line 100

def self.validate_encryption_key
  if !self.encryption_key || self.encryption_key.length < 24
    raise "SECURE_ENCRYPTION_KEY env variable should be at least 24 characters"
  end
  return if !ActiveRecord::Base.connection.data_source_exists?('settings')
  config_hash = Digest::SHA1.hexdigest(self.encryption_key)
  stored_hash = Setting.get('encryption_hash')
  return if stored_hash == config_hash

  if stored_hash.nil?
    Setting.set('encryption_hash', config_hash);
  else
    raise "SECURE_ENCRYPTION_KEY env variable doesn't match the value stored in the database." +  
     " If this is intentional you can try DELETE FROM settings WHERE key='encryption_hash' to reset."
  end
end