Class: EncryptedStore::CryptoHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/encrypted_store/crypto_hash.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = {}) ⇒ CryptoHash



7
8
9
10
# File 'lib/encrypted_store/crypto_hash.rb', line 7

def initialize(data={})
  super()
  merge!(data)
end

Class Method Details

._calc_crc32(data) ⇒ Object



111
112
113
# File 'lib/encrypted_store/crypto_hash.rb', line 111

def _calc_crc32(data)
  [Zlib.crc32(data)].pack('N')
end

._keyiv_gen(key, salt, iter_mag) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/encrypted_store/crypto_hash.rb', line 49

def _keyiv_gen(key, salt, iter_mag)
  if iter_mag == -1
    raise Errors::InvalidKeySize, 'must be exactly 256 bits' unless key.bytes.length == 32
    raise Errors::InvalidSaltSize, 'must be exactly 128 bits' unless salt.bytes.length == 16
    iv = salt
  else
    digest = OpenSSL::Digest::SHA256.new
    key_and_iv = OpenSSL::PKCS5.pbkdf2_hmac(key, salt, 1 << iter_mag, 48, digest)

    key = key_and_iv[0..31]
    iv  = key_and_iv[32..-1]
  end

  [key, iv]
end

._split_binary_data(encrypted_data) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/encrypted_store/crypto_hash.rb', line 65

def _split_binary_data(encrypted_data)
  # Split encrypted data and CRC
  bytes = encrypted_data.bytes

  version = bytes[0]
  version_method = "_split_binary_data_v#{version}"

  if respond_to?(version_method)
    send(version_method, encrypted_data)
  else
    raise Errors::UnsupportedVersionError, "Unsupported encrypted data version: #{version}"
  end
end

._split_binary_data_v1(encrypted_data) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/encrypted_store/crypto_hash.rb', line 79

def _split_binary_data_v1(encrypted_data)
  bytes = encrypted_data.bytes
  salt_length = bytes[1]

  salt_start_index = 2
  salt_end_index   = salt_start_index + salt_length - 1
  salt = bytes[salt_start_index..salt_end_index].pack('c*')
  data = bytes[salt_end_index+1..-5].pack('c*')

  crc = bytes[-4..-1]
  raise Errors::ChecksumFailedError unless crc == _calc_crc32(encrypted_data[0..-5]).bytes

  [salt, 12, data]
end

._split_binary_data_v2(encrypted_data) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/encrypted_store/crypto_hash.rb', line 94

def _split_binary_data_v2(encrypted_data)
  bytes = encrypted_data.bytes
  salt_length = bytes[1]
  iter_mag    = bytes[2].chr.unpack('c').first

  salt_start_index = 3
  salt_end_index   = salt_start_index + salt_length - 1
  salt = bytes[salt_start_index..salt_end_index].pack('c*')
  data = bytes[salt_end_index+1..-5].pack('c*')

  crc = bytes[-4..-1]
  raise Errors::ChecksumFailedError unless crc == _calc_crc32(encrypted_data[0..-5]).bytes

  [salt, iter_mag, data]
end

.decrypt(dek, data) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/encrypted_store/crypto_hash.rb', line 34

def decrypt(dek, data)
  return CryptoHash.new unless data
  salt, iter_mag, data = _split_binary_data(data)

  key, iv = _keyiv_gen(dek, salt, iter_mag)

  decryptor = OpenSSL::Cipher::AES256.new(:CBC).decrypt
  decryptor.key = key
  decryptor.iv = iv

  new_hash = JSON.parse(decryptor.update(data) + decryptor.final)
  new_hash = Hash[new_hash.map { |k,v| [k.to_sym, v] }]
  CryptoHash.new(new_hash)
end

Instance Method Details

#encrypt(dek, salt, iter_mag = 10) ⇒ Object

Encrypts the hash using the data encryption key and salt.

Returns a blob: | Byte 0 | Byte 1 | Byte 2 | Bytes 3…S | Bytes S+1…E | Bytes E+1..E+4 |


| Version | Salt Length | Iteration Magnitude | Salt | Encrypted Data | CRC32 |



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/encrypted_store/crypto_hash.rb', line 19

def encrypt(dek, salt, iter_mag=10)
  return nil if empty?
  raise Errors::InvalidSaltSize, 'too long' if salt.bytes.length > 255

  key, iv = _keyiv_gen(dek, salt, iter_mag)

  encryptor = OpenSSL::Cipher::AES256.new(:CBC).encrypt
  encryptor.key = key
  encryptor.iv = iv

  data_packet = _encrypted_data_header_v2(salt, iter_mag) + encryptor.update(self.to_json) + encryptor.final
  _append_crc32(data_packet)
end