batch-kit 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +165 -0
  4. data/lib/batch-kit.rb +9 -0
  5. data/lib/batch-kit/arguments.rb +57 -0
  6. data/lib/batch-kit/config.rb +517 -0
  7. data/lib/batch-kit/configurable.rb +68 -0
  8. data/lib/batch-kit/core_ext/enumerable.rb +97 -0
  9. data/lib/batch-kit/core_ext/file.rb +69 -0
  10. data/lib/batch-kit/core_ext/file_utils.rb +103 -0
  11. data/lib/batch-kit/core_ext/hash.rb +17 -0
  12. data/lib/batch-kit/core_ext/numeric.rb +17 -0
  13. data/lib/batch-kit/core_ext/string.rb +88 -0
  14. data/lib/batch-kit/database.rb +133 -0
  15. data/lib/batch-kit/database/java_util_log_handler.rb +65 -0
  16. data/lib/batch-kit/database/log4r_outputter.rb +57 -0
  17. data/lib/batch-kit/database/models.rb +548 -0
  18. data/lib/batch-kit/database/schema.rb +229 -0
  19. data/lib/batch-kit/encryption.rb +7 -0
  20. data/lib/batch-kit/encryption/java_encryption.rb +178 -0
  21. data/lib/batch-kit/encryption/ruby_encryption.rb +175 -0
  22. data/lib/batch-kit/events.rb +157 -0
  23. data/lib/batch-kit/framework/acts_as_job.rb +197 -0
  24. data/lib/batch-kit/framework/acts_as_sequence.rb +123 -0
  25. data/lib/batch-kit/framework/definable.rb +169 -0
  26. data/lib/batch-kit/framework/job.rb +121 -0
  27. data/lib/batch-kit/framework/job_definition.rb +105 -0
  28. data/lib/batch-kit/framework/job_run.rb +145 -0
  29. data/lib/batch-kit/framework/runnable.rb +235 -0
  30. data/lib/batch-kit/framework/sequence.rb +87 -0
  31. data/lib/batch-kit/framework/sequence_definition.rb +38 -0
  32. data/lib/batch-kit/framework/sequence_run.rb +48 -0
  33. data/lib/batch-kit/framework/task_definition.rb +89 -0
  34. data/lib/batch-kit/framework/task_run.rb +53 -0
  35. data/lib/batch-kit/helpers/date_time.rb +54 -0
  36. data/lib/batch-kit/helpers/email.rb +198 -0
  37. data/lib/batch-kit/helpers/html.rb +175 -0
  38. data/lib/batch-kit/helpers/process.rb +101 -0
  39. data/lib/batch-kit/helpers/zip.rb +30 -0
  40. data/lib/batch-kit/job.rb +11 -0
  41. data/lib/batch-kit/lockable.rb +138 -0
  42. data/lib/batch-kit/loggable.rb +78 -0
  43. data/lib/batch-kit/logging.rb +169 -0
  44. data/lib/batch-kit/logging/java_util_logger.rb +87 -0
  45. data/lib/batch-kit/logging/log4r_logger.rb +71 -0
  46. data/lib/batch-kit/logging/null_logger.rb +35 -0
  47. data/lib/batch-kit/logging/stdout_logger.rb +96 -0
  48. data/lib/batch-kit/resources.rb +191 -0
  49. data/lib/batch-kit/sequence.rb +7 -0
  50. metadata +122 -0
@@ -0,0 +1,229 @@
1
+ require 'sequel'
2
+ require_relative '../logging'
3
+
4
+
5
+ class BatchKit
6
+
7
+ class Database
8
+
9
+ # Manages the database tables and connection used to record batch-kit
10
+ # events.
11
+ class Schema
12
+
13
+ attr_reader :log
14
+
15
+
16
+ # Create a batch-kit schema instance
17
+ #
18
+ # @param options [Hash] An options hash.
19
+ # @option options [Symbol] :log_level The level of database messages
20
+ # that should be visible (default is :error).
21
+ def initialize(options = {})
22
+ @log = BatchKit::LogManager.logger('batch-kit.database.schema')
23
+ @log.level = options.fetch(:log_level, :error)
24
+ end
25
+
26
+
27
+ # Returns the Sequel database connection.
28
+ def connection
29
+ @conn
30
+ end
31
+
32
+
33
+ # Connects to the database determined by +args+.
34
+ #
35
+ # @see Sequel#connect for details on the different arguments that
36
+ # are required to connect to your database.
37
+ def connect(*args)
38
+ Sequel.default_timezone = :utc
39
+ @conn = Sequel.connect(*args)
40
+ @conn.loggers << @log
41
+ @conn.autosequence = true
42
+
43
+ create_tables unless deployed?
44
+ end
45
+
46
+
47
+ # @return true if the batch-kit database tables have been deployed.
48
+ def deployed?
49
+ @conn.table_exists?(:batch_md5)
50
+ end
51
+
52
+
53
+ # Drops all database tables used for tracking batch-kit processes.
54
+ def drop_tables
55
+ @conn.drop_table(:batch_requestor)
56
+ @conn.drop_table(:batch_request)
57
+ @conn.drop_table(:batch_lock)
58
+ @conn.drop_table(:batch_task_run)
59
+ @conn.drop_table(:batch_task)
60
+ @conn.drop_table(:batch_job_run_failure)
61
+ @conn.drop_table(:batch_job_run_log)
62
+ @conn.drop_table(:batch_job_run_arg)
63
+ @conn.drop_table(:batch_job_run)
64
+ @conn.drop_table(:batch_job)
65
+ @conn.drop_table(:batch_md5)
66
+ end
67
+
68
+
69
+ # Creates all database tables needed for tracking batch-kit processes.
70
+ def create_tables
71
+ # MD5 table, used to hold hashes of objects to detect version changes
72
+ @conn.create_table?(:batch_md5) do
73
+ primary_key :md5_id
74
+ String :object_type, size: 30, null: false
75
+ String :object_name, size: 255, null: false
76
+ Fixnum :object_version, null: false
77
+ String :md5_digest, size: 32, null: false
78
+ DateTime :md5_created_at, null: false
79
+ unique [:object_type, :object_name, :object_version]
80
+ end
81
+
82
+ # Job table, holds details of job definitions
83
+ @conn.create_table?(:batch_job) do
84
+ primary_key :job_id
85
+ String :job_name, size: 80, null: false
86
+ String :job_class, size: 80, null: false
87
+ String :job_method, size: 80, null: false
88
+ String :job_desc, size: 255, null: true
89
+ String :job_host, size: 50, null: false
90
+ String :job_file, size: 255, null: false
91
+ Fixnum :job_version, null: false
92
+ foreign_key :job_file_md5_id, :batch_md5, null: false
93
+ Fixnum :job_run_count, null: false
94
+ Fixnum :job_success_count, null: false
95
+ Fixnum :job_fail_count, null: false
96
+ Fixnum :job_abort_count, null: false
97
+ Bignum :job_min_success_duration_ms, null: false
98
+ Bignum :job_max_success_duration_ms, null: false
99
+ Bignum :job_mean_success_duration_ms, null: false
100
+ Bignum :job_m2_success_duration_ms, null: false
101
+ DateTime :job_created_at, null: false
102
+ DateTime :job_modified_at, null: false
103
+ DateTime :job_last_run_at, null: true
104
+ unique [:job_host, :job_name]
105
+ end
106
+
107
+ # Task table, holds details of task definitions
108
+ @conn.create_table?(:batch_task) do
109
+ primary_key :task_id
110
+ foreign_key :job_id, :batch_job, null: false
111
+ Fixnum :job_version, null: false
112
+ String :task_name, size: 80, null: false
113
+ String :task_class, size: 80, null: false
114
+ String :task_method, size: 80, null: false
115
+ String :task_desc, size: 255, null: true
116
+ TrueClass :task_current_flag, null: false, default: true
117
+ Fixnum :task_run_count, null: false
118
+ Fixnum :task_success_count, null: false
119
+ Fixnum :task_fail_count, null: false
120
+ Fixnum :task_abort_count, null: false
121
+ Bignum :task_min_success_duration_ms, null: false
122
+ Bignum :task_max_success_duration_ms, null: false
123
+ Bignum :task_mean_success_duration_ms, null: false
124
+ Bignum :task_m2_success_duration_ms, null: false
125
+ DateTime :task_created_at, null: false
126
+ DateTime :task_modified_at, null: false
127
+ DateTime :task_last_run_at, null: true
128
+ end
129
+
130
+ # Job run table, holds details of a single execution of a job
131
+ @conn.create_table?(:batch_job_run) do
132
+ primary_key :job_run
133
+ foreign_key :job_id, :batch_job, null: false
134
+ String :job_instance, size: 80, null: true
135
+ Fixnum :job_version, null: false
136
+ String :job_run_by, size: 50, null: false
137
+ String :job_cmd_line, size: 2000, null: true
138
+ DateTime :job_start_time, null: false
139
+ DateTime :job_end_time, null: true
140
+ String :job_status, size: 12, null: false
141
+ Fixnum :job_pid, null: true
142
+ Fixnum :job_exit_code, null: true
143
+ TrueClass :job_purged_flag, null: false, default: false
144
+ end
145
+
146
+ # Job run arguments table, holds details of the arguments used on a job
147
+ @conn.create_table?(:batch_job_run_arg) do
148
+ foreign_key :job_run, :batch_job_run
149
+ String :job_arg_name, size: 50, null: false
150
+ String :job_arg_value, size: 255, null: true
151
+ primary_key [:job_run, :job_arg_name]
152
+ end
153
+
154
+ # Job run log table, holds log records for a job
155
+ @conn.create_table?(:batch_job_run_log) do
156
+ foreign_key :job_run, :batch_job_run
157
+ Fixnum :log_line, null: false
158
+ DateTime :log_time, null: false
159
+ String :log_name, size: 40, null: false
160
+ String :log_level, size: 8, null: false
161
+ String :thread_id, size: 8, null: true
162
+ String :log_message, size: 1000, null: false
163
+ primary_key [:job_run, :log_line]
164
+ end
165
+
166
+ # Job failure table, holds exception details for job failures
167
+ @conn.create_table?(:batch_job_run_failure) do
168
+ # We don't use an FK here, because we want to be able to retain
169
+ # failure details longer than we retain job runs
170
+ Fixnum :job_run, null: false
171
+ foreign_key :job_id, :batch_job, null: false
172
+ Fixnum :job_version, null: false
173
+ DateTime :job_failed_at, null: false
174
+ String :exception_message, size: 500, null: false
175
+ String :exception_backtrace, size: 4000, null: false
176
+ end
177
+
178
+ # Task run table, holds details of a single execution of a task
179
+ @conn.create_table?(:batch_task_run) do
180
+ primary_key :task_run
181
+ foreign_key :task_id, :batch_task, null: false
182
+ foreign_key :job_run, :batch_job_run, null: false
183
+ String :task_instance, size: 80, null: true
184
+ DateTime :task_start_time, null: false
185
+ DateTime :task_end_time, null: true
186
+ String :task_status, size: 12, null: false
187
+ Fixnum :task_exit_code, null: true
188
+ end
189
+
190
+ # Lock table, holds details of the current locks
191
+ @conn.create_table?(:batch_lock) do
192
+ String :lock_name, type: String, size: 50, unique_key: true
193
+ foreign_key :job_run, :batch_job_run
194
+ DateTime :lock_created_at, null: false
195
+ DateTime :lock_expires_at, null: false
196
+ end
197
+
198
+ # Request table, holds details of job run requests
199
+ @conn.create_table?(:batch_request) do
200
+ primary_key :request_id
201
+ String :request_type, size: 80, null: false
202
+ String :request_label, size: 80, null: false
203
+ String :request_source, size: 80, null: false
204
+ String :job_host, size: 30, null: false
205
+ String :job_file, size: 255, null: false
206
+ String :job_args, size: 2000, null: true
207
+ String :job_working_dir, size: 255, null: true
208
+ TrueClass :processed_flag, default: false, null: false
209
+ foreign_key :job_run, :batch_job_run, null: true
210
+ DateTime :request_created_at, null: false
211
+ DateTime :request_start_at, null: true
212
+ DateTime :request_launched_at, null: true
213
+ end
214
+
215
+ # Requestor table, holds details of job run requestors
216
+ @conn.create_table?(:batch_requestor) do
217
+ foreign_key :request_id, :batch_request, null: false
218
+ DateTime :requested_at, null: false
219
+ String :requested_by, size: 80, null: false
220
+ String :email_address, size: 100, null: true
221
+ end
222
+ end
223
+
224
+ end
225
+
226
+ end
227
+
228
+ end
229
+
@@ -0,0 +1,7 @@
1
+ if RUBY_PLATFORM == 'java'
2
+ # JRuby OpenSSL support does not include the PKCS5 module, so we use the
3
+ # built-in Java crypto classes instead
4
+ require_relative 'encryption/java_encryption'
5
+ else
6
+ require_relative 'encryption/ruby_encryption'
7
+ end
@@ -0,0 +1,178 @@
1
+ class BatchKit
2
+
3
+ # Performs password-based encryption (PBE) and decryption using the AES 128-bit
4
+ # cipher algorithm. Password-based encryption uses a password or passphrase
5
+ # as the shared secret key with which to encrypt/decrypt other text.
6
+ #
7
+ # As with all key-based encryption schemes, the encrypted values are only as
8
+ # secure as the key used to encrypt them. If the key is accessible to a
9
+ # hacker, any values encrypted with the key can be decrypted, so the key
10
+ # should be stored separately from the encrypted values, and be kept as
11
+ # secure as possible.
12
+ module Encryption
13
+
14
+ include_package 'java.security'
15
+ include_package 'javax.crypto'
16
+ include_package 'javax.crypto.spec'
17
+
18
+ # Use PBKDF2 with SHA-1 as the key hashing algorithm
19
+ # The NIST specifically names SHA1 as an acceptable hashing algorithm
20
+ # for PKBDF2
21
+ KEY_ALGORITHM = 'PBKDF2WithHmacSHA1'
22
+
23
+ # Iteration count; NIST recommends at least 1000 iterations
24
+ KEY_ITERATION_COUNT = 5000
25
+
26
+ # Length of the generated key in bits
27
+ KEY_DERIVED_LENGTH = 128
28
+
29
+ # Algorithm to use for encryption
30
+ CIPHER_ALGORITHM = 'AES/CBC/PKCS5Padding'
31
+
32
+ # Salt; a random 8 bytes used to generate an encryption key from a key
33
+ # password / passphrase.
34
+ # @note The same value *must* be used for the salt when converting a key
35
+ # password/passphrase into an encryption key for both encryptionn and
36
+ # decryption.
37
+ SALT = [70, 211, 28, 57, 192, 6, 78, 163].pack("CCCCCCCC").to_java_bytes
38
+
39
+
40
+ # Generate a random master key that can be used to encrypt/decrypt other
41
+ # sensitive data such as passwords. The master key must be stored some
42
+ # place separate from the values it is used to encrypt.
43
+ #
44
+ # @return [String] A random string of text that can be used as a master
45
+ # key for encrypting/decrypting other values.
46
+ def generate_master_key()
47
+ java.util.UUID.randomUUID().toString()
48
+ end
49
+ module_function :generate_master_key
50
+
51
+
52
+ # Encrypt the supplied +clear_text+, using +key_text+ as the pass-phrase.
53
+ #
54
+ # @param key_text [String] The clear-text pass-phrase to use as the key
55
+ # for encryption.
56
+ # @param clear_text [String] The cleartext string to be encrypted.
57
+ # @param salt [Array<Byte>] An 8-byte array of random values to use as
58
+ # the salt.
59
+ # @return [String] A base-64 encoded string representing the encrypted
60
+ # +clear_text+ value.
61
+ def encrypt(key_text, clear_text, salt = SALT)
62
+ key = generate_key(key_text, salt)
63
+ encipher(key, clear_text)
64
+ end
65
+ module_function :encrypt
66
+
67
+
68
+ # Encipher the supplied +clear_text+, using +key+ as the encryption key.
69
+ #
70
+ # Note: this method is possibly less secure than #encrypt, since it uses
71
+ # the actual key in the enciphering, rather than an SHA1 hash of the key.
72
+ #
73
+ # @param key [SecretKeySpec] The key to use for encryption.
74
+ # @param clear_text [String] The cleartext string to be encrypted.
75
+ # @param cipher_algorithm [String] The name of the cipher algorithm to
76
+ # use when encrypting the clear_text.
77
+ # @return [String] A base-64 encoded string representing the encrypted
78
+ # +clear_text+ value.
79
+ def encipher(key, clear_text, cipher_algorithm = CIPHER_ALGORITHM)
80
+ cipher = Cipher.getInstance(cipher_algorithm)
81
+ cipher.init(Cipher::ENCRYPT_MODE, key)
82
+ params = cipher.getParameters()
83
+ iv = params.getParameterSpec(IvParameterSpec.java_class).getIV()
84
+ cipher_bytes = cipher.doFinal(clear_text.to_java_bytes)
85
+ # Combine IV and cipher bytes, and base-64 encode
86
+ buffer_bytes = Java::byte[KEY_DERIVED_LENGTH / 8 + cipher_bytes.length].new
87
+ buffer = java.nio.ByteBuffer.wrap(buffer_bytes)
88
+ buffer.put(iv)
89
+ buffer.put(cipher_bytes)
90
+ base64_encode(buffer_bytes)
91
+ end
92
+ module_function :encipher
93
+
94
+
95
+ # Decrypt the supplied +cipher_text+, using +key_text+ as the pass-phrase.
96
+ #
97
+ # @param key_text [String] The clear-text pass-phrase to use as the key
98
+ # for decryption.
99
+ # @param cipher_text [String] A base-64 encoded cipher text string that
100
+ # is to be decrypted.
101
+ # @param salt [Array<Byte>] An 8-byte array of random values to use as
102
+ # the salt. Like the +key_text+, it is imperative that the same value
103
+ # is used for the salt when decrypting a previously encrypted value.
104
+ # @return [String] The clear text that was encrypted.
105
+ def decrypt(key_text, cipher_text, salt = SALT)
106
+ key = generate_key(key_text, salt)
107
+ decipher(key, cipher_text)
108
+ end
109
+ module_function :decrypt
110
+
111
+
112
+ # Decipher the supplited +cipher_text+, using +key+ as the decipher key.
113
+ #
114
+ # @param key [SecretKeySpec] The key used for encryption.
115
+ # @param cipher_text [String] A base-64 encoded cipher text string that
116
+ # is to be decrypted.
117
+ # @param cipher_algorithm [String] The name of the cipher algorithm used
118
+ # to encrypt the clear_text.
119
+ # @return [String] The clear text that was encrypted.
120
+ def decipher(key, cipher_text, cipher_algorithm = CIPHER_ALGORITHM)
121
+ cipher = Cipher.getInstance(cipher_algorithm)
122
+ buffer_bytes = base64_decode(cipher_text)
123
+ # Unpack IV and cipher bytes
124
+ iv_bytes = Java::byte[KEY_DERIVED_LENGTH / 8].new
125
+ cipher_bytes = Java::byte[buffer_bytes.length - KEY_DERIVED_LENGTH / 8].new
126
+ buffer = java.nio.ByteBuffer.wrap(buffer_bytes)
127
+ buffer.get(iv_bytes)
128
+ buffer.get(cipher_bytes)
129
+ cipher.init(Cipher::DECRYPT_MODE, key, IvParameterSpec.new(iv_bytes))
130
+ String.from_java_bytes(cipher.doFinal(cipher_bytes))
131
+ end
132
+ module_function :decipher
133
+
134
+
135
+ # Convert a byte array to a base-64 encoded string representation.
136
+ #
137
+ # @param bytes [String, Array<byte>] A String or Java byte-array to be
138
+ # encoded.
139
+ # @return [String] A base-64 encoded String.
140
+ def base64_encode(bytes)
141
+ bytes = bytes.to_java_bytes if bytes.is_a?(String)
142
+ javax.xml.bind.DatatypeConverter.printBase64Binary(bytes)
143
+ end
144
+ module_function :base64_encode
145
+
146
+
147
+ # Convert a base-64 encoded string to a byte array.
148
+ #
149
+ # @param str [String] A base-64 encoded String.
150
+ # @return [Array<byte>] A Java byte-array containing the decoded bytes.
151
+ def base64_decode(str)
152
+ javax.xml.bind.DatatypeConverter.parseBase64Binary(str)
153
+ end
154
+ module_function :base64_decode
155
+
156
+
157
+ private
158
+
159
+ # Generates the key to be used for encryption/decryption, based on
160
+ # +key_text+ and +salt+.
161
+ #
162
+ # @param key_text [String] A clear-text password or pass phrase that is
163
+ # to be used to derive the encryption key.
164
+ # @param salt [Array<Byte>] An 8-byte array of random values to use as
165
+ # the salt.
166
+ # @return [SecretKey] A key that can be used for encryption/decryption.
167
+ def generate_key(key_text, salt)
168
+ factory = SecretKeyFactory.getInstance(KEY_ALGORITHM)
169
+ key_spec = PBEKeySpec.new(key_text.to_s.to_java.toCharArray(), salt,
170
+ KEY_ITERATION_COUNT, KEY_DERIVED_LENGTH)
171
+ key = factory.generateSecret(key_spec)
172
+ SecretKeySpec.new(key.getEncoded(), CIPHER_ALGORITHM.split('/').first)
173
+ end
174
+ module_function :generate_key
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,175 @@
1
+ require 'openssl'
2
+ require 'digest/sha1'
3
+ require 'base64'
4
+
5
+
6
+ class BatchKit
7
+
8
+ # Performs password-based encryption (PBE) and decryption using the AES 128-bit
9
+ # cipher algorithm. Password-based encryption uses a password or passphrase
10
+ # as the shared secret key with which to encrypt/decrypt other text.
11
+ #
12
+ # As with all key-based encryption schemes, the encrypted values are only as
13
+ # secure as the key used to encrypt them. If the key is accessible to a
14
+ # hacker, any values encrypted with the key can be decrypted, so the key
15
+ # should be stored separately from the encrypted values, and be kept as
16
+ # secure as possible.
17
+ module Encryption
18
+
19
+ # Use PBKDF2 with SHA-1 as the key hashing algorithm
20
+ # The NIST specifically names SHA1 as an acceptable hashing algorithm
21
+ # for PKBDF2
22
+ KEY_ALGORITHM = :pbkdf2_hmac_sha1
23
+
24
+ # Iteration count; NIST recommends at least 1000 iterations
25
+ KEY_ITERATION_COUNT = 5000
26
+
27
+ # Length of the generated key in bits
28
+ KEY_DERIVED_LENGTH = 128
29
+
30
+ # Algorithm to use for encryption
31
+ CIPHER_ALGORITHM = 'AES-128-CBC'
32
+
33
+ # Salt; a random 8 bytes used to generate an encryption key from a key
34
+ # password / passphrase.
35
+ #
36
+ # @note The same value *must* be used for the salt when converting a key
37
+ # password/passphrase into an encryption key for both encryptionn and
38
+ # decryption.
39
+ SALT = [70, 211, 28, 57, 192, 6, 78, 163].pack("CCCCCCCC")
40
+
41
+
42
+ # Generate a random master key that can be used to encrypt/decrypt other
43
+ # sensitive data such as passwords. The master key must be stored some
44
+ # place separate from the values it is used to encrypt.
45
+ #
46
+ # @return [String] A random string of text that can be used as a master
47
+ # key for encrypting/decrypting other values.
48
+ def generate_master_key()
49
+ require 'securerandom'
50
+ SecureRandom.uuid
51
+ end
52
+ module_function :generate_master_key
53
+
54
+
55
+ # Encrypt the supplied +clear_text+, using +key_text+ as the pass-phrase.
56
+ #
57
+ # @param key_text [String] The clear-text pass-phrase to use as the key
58
+ # for encryption.
59
+ # @param clear_text [String] The cleartext string to be encrypted.
60
+ # @param salt [Array<Byte>] An 8-byte array of random values to use as
61
+ # the salt.
62
+ # @return [String] A base-64 encoded string representing the encrypted
63
+ # +clear_text+ value.
64
+ def encrypt(key_text, clear_text, salt = SALT)
65
+ key = generate_key(key_text, salt)
66
+ encipher(key, clear_text)
67
+ end
68
+ module_function :encrypt
69
+
70
+
71
+ # Encipher the supplied +clear_text+, using +key+ as the encryption key.
72
+ #
73
+ # Note: this method is possibly less secure than #encrypt, since it uses
74
+ # the actual key in the enciphering, rather than an SHA1 hash of the key.
75
+ #
76
+ # @param key [String] The key to use for encryption.
77
+ # @param clear_text [String] The cleartext string to be encrypted.
78
+ # @param cipher_algorithm [String] The name of the cipher algorithm to
79
+ # use when encrypting the clear_text.
80
+ # @return [String] A base-64 encoded string representing the encrypted
81
+ # +clear_text+ value.
82
+ def encipher(key, clear_text, cipher_algorithm = CIPHER_ALGORITHM)
83
+ cipher = OpenSSL::Cipher.new(cipher_algorithm)
84
+ cipher.encrypt
85
+ cipher.key = key
86
+
87
+ iv = cipher.random_iv
88
+ cipher_bytes = cipher.update(clear_text) + cipher.final
89
+
90
+ # Combine IV and cipher bytes, and base-64 encode
91
+ base64_encode([iv + cipher_bytes])
92
+ end
93
+ module_function :encipher
94
+
95
+
96
+ # Decrypt the supplied +cipher_text+, using +key_text+ as the pass-phrase.
97
+ #
98
+ # @param key_text [String] The clear-text pass-phrase to use as the key
99
+ # for decryption.
100
+ # @param cipher_text [String] A base-64 encoded cipher text string that
101
+ # is to be decrypted.
102
+ # @param salt [Array<Byte>] An 8-byte array of random values to use as
103
+ # the salt. Like the +key_text+, it is imperative that the same value
104
+ # is used for the salt when decrypting a previously encrypted value.
105
+ # @return [String] The clear text that was encrypted.
106
+ def decrypt(key_text, cipher_text, salt = SALT)
107
+ key = generate_key(key_text, salt)
108
+ decipher(key, cipher_text)
109
+ end
110
+ module_function :decrypt
111
+
112
+
113
+ # Decipher the supplited +cipher_text+, using +key+ as the decipher key.
114
+ #
115
+ # @param key [String] The key used for encryption.
116
+ # @param cipher_text [String] A base-64 encoded cipher text string that
117
+ # is to be decrypted.
118
+ # @param cipher_algorithm [String] The name of the cipher algorithm used
119
+ # to encrypt the clear_text.
120
+ # @return [String] The clear text that was encrypted.
121
+ def decipher(key, cipher_text, cipher_algorithm = CIPHER_ALGORITHM)
122
+ cipher = OpenSSL::Cipher.new(cipher_algorithm)
123
+ cipher.decrypt
124
+ cipher.key = key
125
+
126
+ # Base-64 decode, and unpack IV and cipher from cipher_text
127
+ iv_cipher = base64_decode(cipher_text)
128
+ iv_len = KEY_DERIVED_LENGTH / 8
129
+ cipher.iv = iv_cipher[0...iv_len]
130
+ cipher_bytes = iv_cipher[iv_len..-1]
131
+ cipher.update(cipher_bytes) + cipher.final
132
+ end
133
+ module_function :decipher
134
+
135
+
136
+ # Convert a byte array to a base-64 encoded string representation.
137
+ #
138
+ # @param bytes [String, Array<String>] A String or Java byte-array to be
139
+ # encoded.
140
+ # @return [String] A base-64 encoded String.
141
+ def base64_encode(bytes)
142
+ bytes = [bytes] if bytes.is_a?(String)
143
+ bytes.pack('m0').chomp
144
+ end
145
+ module_function :base64_encode
146
+
147
+
148
+ # Convert a base-64 encoded string to a byte array.
149
+ #
150
+ # @param str [String] A base-64 encoded String.
151
+ # @return [Array<byte>] A String containing the decoded bytes.
152
+ def base64_decode(str)
153
+ str.unpack('m0').first
154
+ end
155
+ module_function :base64_decode
156
+
157
+
158
+ private
159
+
160
+ # Generates the key to be used for encryption/decryption, based on
161
+ # +key_text+ and +salt+.
162
+ #
163
+ # @param key_text [String] A clear-text password or pass phrase that is
164
+ # to be used to derive the encryption key.
165
+ # @param salt [String] A String containing a random salt.
166
+ # @return [SecretKey] A key that can be used for encryption/decryption.
167
+ def generate_key(key_text, salt)
168
+ OpenSSL::PKCS5.send(KEY_ALGORITHM, key_text, salt,
169
+ KEY_ITERATION_COUNT, KEY_DERIVED_LENGTH)
170
+ end
171
+ module_function :generate_key
172
+
173
+ end
174
+
175
+ end