Module: Cryptdoh

Defined in:
lib/cryptdoh.rb

Constant Summary collapse

MIN_PASSWORD_LENGTH =
8
IV_LENGTH =
16
SALT_LENGTH =
16
ITERATIONS =
100 * 1000
KEY_LENGTH =
32
DIGEST =
OpenSSL::Digest::SHA256.new
VERSION =
"1"

Class Method Summary collapse

Class Method Details

._check_password_length(password) ⇒ Object

Raises:



66
67
68
# File 'lib/cryptdoh.rb', line 66

def self._check_password_length(password)
  raise UserError, "Crappy password: too short. Must be at least 8 bytes" unless password.size >= MIN_PASSWORD_LENGTH
end

._check_password_strength(password) ⇒ Object

Raises:



61
62
63
64
# File 'lib/cryptdoh.rb', line 61

def self._check_password_strength(password)
  c = CrackLib::Fascist(password)
  raise UserError, "Crappy password: #{c.reason}" unless c.ok?
end

._decode(data) ⇒ Object



86
87
88
89
90
# File 'lib/cryptdoh.rb', line 86

def self._decode(data)
  Base64.decode64(data)
rescue
  raise EvilError, 'Bad base64 data'
end

._encode(data) ⇒ Object



82
83
84
# File 'lib/cryptdoh.rb', line 82

def self._encode(data)
  Base64.encode64(data).chomp
end

._hmac(key, message) ⇒ Object



77
78
79
80
# File 'lib/cryptdoh.rb', line 77

def self._hmac(key, message)
  # Only require 128 bits of security, so cut in half
  OpenSSL::HMAC.digest(DIGEST, key, message)[0..15]
end

._kdf(password, salt = nil) ⇒ Object

Raises:



70
71
72
73
74
75
# File 'lib/cryptdoh.rb', line 70

def self._kdf(password, salt = nil)
  salt ||= SecureRandom.random_bytes(SALT_LENGTH)
  raise UserError, "Salt is the wrong size" unless salt.size == SALT_LENGTH
  key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, ITERATIONS, KEY_LENGTH * 2, DIGEST)
  [salt, key]
end

._verifyObject



92
93
94
95
96
97
98
99
# File 'lib/cryptdoh.rb', line 92

def self._verify
  message = 'this is a secret message'
  password = 'dZ]av}a]i4qK2:1Z:t |Ju.'

  decrypt(password, encrypt(password, message)) == message
rescue
  false
end

.decrypt(password, message) ⇒ Object

Raises:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/cryptdoh.rb', line 43

def self.decrypt(password, message)
  (version, encoded_iv, encoded_salt, encoded_ciphertext, encoded_hmac) = message.split('.')

  (salt, key) = _kdf(password, _decode(encoded_salt))
  cipher_key = key[0..KEY_LENGTH-1]
  hmac_key = key[KEY_LENGTH..-1]

  hmac = _hmac(hmac_key, [version, encoded_iv, encoded_salt, encoded_ciphertext].join('.'))
  raise EvilError, 'Invalid HMAC' unless _decode(encoded_hmac) == hmac

  decipher = OpenSSL::Cipher::AES.new(KEY_LENGTH * 8, :CBC)
  decipher.decrypt
  decipher.iv = _decode(encoded_iv)
  decipher.key = cipher_key

  decipher.update(_decode(encoded_ciphertext)) + decipher.final
end

.encrypt(password, message, args = {}) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/cryptdoh.rb', line 22

def self.encrypt(password, message, args = {})
  _check_password_length(password)
  _check_password_strength(password) unless args[:skip_strength_check]

  (salt, key) = _kdf(password)
  cipher_key = key[0..KEY_LENGTH-1]
  hmac_key = key[KEY_LENGTH..-1]

  cipher = OpenSSL::Cipher::AES.new(KEY_LENGTH * 8, :CBC)
  cipher.encrypt
  iv = cipher.random_iv
  cipher.key = cipher_key

  ciphertext = cipher.update(message) + cipher.final

  cipher_message = [VERSION, _encode(iv), _encode(salt), _encode(ciphertext)].join('.')
  hmac = _hmac(hmac_key, cipher_message)

  [cipher_message, _encode(hmac)].join('.')
end