batch-kit 0.3

Sign up to get free protection for your applications and to get access to all the features.
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