hash_redactor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/hash_redactor.gemspec +26 -0
- data/lib/hash_redactor/hash_redactor.rb +117 -0
- data/lib/hash_redactor/version.rb +3 -0
- data/lib/hash_redactor.rb +2 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: afc446801a4d9742e40d28954ed609b4cd58aad3
|
4
|
+
data.tar.gz: c959b0bd8e2c8c4193bb2526423ab46ba4495982
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 74b7d907b37f7855c16a45aecc4bd4b6102a2ffb9a2516765a623fc3b7339c805c14b6baa73f20560890c64efaa86ccf9dd6520221c52c14f917c800dfad51ba
|
7
|
+
data.tar.gz: 14cefa25e6f0ed169952e35910d5080ce519d49d5a12467a04350773d297926e16c6c9573e4ab6532444d16b2371002bfd36c8b7e6482b55947e5871b97d95f1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Chris Jensen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# HashRedactor
|
2
|
+
|
3
|
+
Used to redact a hash by removing, digesting or encrypting keys.
|
4
|
+
|
5
|
+
Makes use of [`attr_encrypted`](https://github.com/attr-encrypted/attr_encrypted) to perform encryption.
|
6
|
+
|
7
|
+
Useful if you have large JSON objects or similar that are coming in through API calls or elsewhere and you want to scrub the private data before storing it to the database.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'hash_redactor'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install hash_redactor
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Initialize the HashRedactor with a set of fields that you want to redact in hashes that get passed to it.
|
28
|
+
|
29
|
+
You can choose 3 ways to redact each field:
|
30
|
+
|
31
|
+
+ `:remove` - The field is simply deleted
|
32
|
+
+ `:digest` - The field is passed through a one way hash function (SHA256)
|
33
|
+
+ `:encrypt` - The field is encrypted
|
34
|
+
|
35
|
+
If you plan on using digest or encrypt, then you will need to specify `:digest_salt` or `:encryption_key` respectively in the options.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
fields_to_redact = {
|
39
|
+
:ssn => :remove,
|
40
|
+
:email => :digest,
|
41
|
+
:medical_history => :encrypt
|
42
|
+
}
|
43
|
+
|
44
|
+
redactor = HashRedactor::HashRedactor.new({
|
45
|
+
redact: fields_to_redact,
|
46
|
+
encryption_key: 'a really secure key no one will ever guess it',
|
47
|
+
digest_salt: 'a secret salt'
|
48
|
+
})
|
49
|
+
```
|
50
|
+
|
51
|
+
Once initialized, you can simply call `#redact` on hashes of data.
|
52
|
+
If a key you have specified to redact is missing, HashRedactor will silently ignore it's absence.
|
53
|
+
Any key's not specified in your redact hash will be left unchanged.
|
54
|
+
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
raw_data = {
|
58
|
+
ssn: 'a very personal number',
|
59
|
+
email: 'personal@email.com',
|
60
|
+
medical_history: 'Some very private information'
|
61
|
+
plaintext: 'This will never be changed'
|
62
|
+
}
|
63
|
+
|
64
|
+
safe_data = redactor.redact(raw_data)
|
65
|
+
|
66
|
+
safe_data[:ssn] # nil
|
67
|
+
safe_data[:email_digest] # One way hash of the email
|
68
|
+
safe_data[:encrypted_medical_history] # Encrypted medical history
|
69
|
+
safe_data[:plaintext] # This will never be changed
|
70
|
+
```
|
71
|
+
|
72
|
+
To retrieve your encrypted data, pass it through decrypt.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
loaded_data = redactor.decrypt(safe_data)
|
76
|
+
loaded_data[:medical_history] # 'Some very private information'
|
77
|
+
```
|
78
|
+
|
79
|
+
## Digest method
|
80
|
+
|
81
|
+
Digest may be used in cases where you don't want to keep the data due to privacy concerns, but want to compare it.
|
82
|
+
For example, maybe you have a large database of people whom you never need to contact by email, but you want to use the email address to check for duplicates.
|
83
|
+
|
84
|
+
To enhance protection of the data you should provide a salt in the configuration options and you should not store that salt in your database.
|
85
|
+
|
86
|
+
Once the field has been digested, it's key will have _digest appended.
|
87
|
+
|
88
|
+
## Encrypt method
|
89
|
+
|
90
|
+
Encryption is for fields that you do need to recover, but don't want to store in plaintext in the database.
|
91
|
+
|
92
|
+
[`attr_encrypted`](https://github.com/attr-encrypted/attr_encrypted) is used to perform encryption.
|
93
|
+
|
94
|
+
Once the field has been encrypted, the original field is removed from the hash, and replaced with `:encrypted_FIELDNAME` and `:encrypted_FIELDNAME_iv` - both these fields must be present in the hash for decrypt to succeed.
|
95
|
+
|
96
|
+
For example, if the field you want to encrypt has the key `:email`, then the redacred hash will have the keys `:encrypted_email` and `:encrypted_email_iv`.
|
97
|
+
|
98
|
+
## Redacted Hash
|
99
|
+
|
100
|
+
From the example above, after running #redact, the safe_data hash would look something like this:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
{
|
104
|
+
:email_digest=>"VXJsQeG81HYWOb30XfpBdbqEFH1f4VaFLTqHCdxCmj8=", :encrypted_medical_history=>"2JIN3Yhxvm/m7qlE+n4pMT9yckXuPa+2IlMBFQMcbP1pcwyrG7wy0TP4scgx\n",
|
105
|
+
:encrypted_medical_history_iv=>"tqMQa1aYTdJuMhrD\n",
|
106
|
+
:plaintext => 'This will never be changed'
|
107
|
+
}
|
108
|
+
```
|
109
|
+
|
110
|
+
## Options
|
111
|
+
|
112
|
+
Default options are:
|
113
|
+
```ruby
|
114
|
+
digest_salt: "",
|
115
|
+
encryption_key: nil,
|
116
|
+
encode: true,
|
117
|
+
encode_iv: true,
|
118
|
+
default_encoding: 'm'
|
119
|
+
```
|
120
|
+
|
121
|
+
### :digest_salt
|
122
|
+
|
123
|
+
Salt to use when applying digest method
|
124
|
+
|
125
|
+
### :encryption_key
|
126
|
+
|
127
|
+
Key to use for encryption and decryption of values
|
128
|
+
|
129
|
+
### :encode, :encode_iv, :default_encoding
|
130
|
+
|
131
|
+
Determines how (if at all) to encode the encrypted data and it's iv
|
132
|
+
|
133
|
+
The default encoding is m (base64). You can change this by setting encode: `'some encoding'`. See [Arrary#pack](http://ruby-doc.org/core-2.3.0/Array.html#method-i-pack) for more encoding options.
|
134
|
+
|
135
|
+
## Development
|
136
|
+
|
137
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
138
|
+
|
139
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
140
|
+
|
141
|
+
## Contributing
|
142
|
+
|
143
|
+
1. Fork it ( https://github.com/chrisjensen/hash_redactor/fork )
|
144
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
145
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
146
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
147
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hash_redactor"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hash_redactor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_redactor"
|
8
|
+
spec.version = HashRedactor::VERSION
|
9
|
+
spec.authors = ["Chris Jensen"]
|
10
|
+
spec.email = ["chris@broadthought.co"]
|
11
|
+
|
12
|
+
spec.summary = %q{Redact specified values in a hash}
|
13
|
+
spec.description = %q{Removes, digests or encrypts selected values in a ruby hash}
|
14
|
+
spec.homepage = "https://github.com/chrisjensen/hash_redactor"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'attr_encrypted', '~> 3.0.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
26
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'attr_encrypted'
|
2
|
+
|
3
|
+
module HashRedactor
|
4
|
+
class HashRedactor
|
5
|
+
def initialize(opts = {})
|
6
|
+
@options = default_options.merge opts
|
7
|
+
|
8
|
+
@options[:encode] = @options[:default_encoding] if @options[:encode] == true
|
9
|
+
@options[:encode_iv] = @options[:default_encoding] if @options[:encode_iv] == true
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_options
|
13
|
+
{
|
14
|
+
digest_salt: "",
|
15
|
+
encryption_key: nil,
|
16
|
+
encode: true,
|
17
|
+
encode_iv: true,
|
18
|
+
default_encoding: 'm'
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Removes, digests or encrypts fields in a hash
|
23
|
+
# NOTE: This should NOT be used to protect password fields or similar
|
24
|
+
# The purpose of hashing is to reduce data to a form that can be *quickly*
|
25
|
+
# compared against other records without revealing the original value.
|
26
|
+
# To allow for this, all hashes are created using the *same salt* which is
|
27
|
+
# not secure enough for password protection
|
28
|
+
# For passwords, use BCrypt
|
29
|
+
def redact(data, opts = {})
|
30
|
+
options = @options.merge(opts)
|
31
|
+
|
32
|
+
redact_hash = options[:redact]
|
33
|
+
|
34
|
+
raise "Don't know what to redact. Please configure the redact hash when initializing or pass as an argument to redact." unless redact_hash && redact_hash.any?
|
35
|
+
|
36
|
+
result = data.clone
|
37
|
+
|
38
|
+
redact_hash.each do |hash_key,how|
|
39
|
+
if data.has_key? hash_key
|
40
|
+
case how
|
41
|
+
when :remove
|
42
|
+
nil
|
43
|
+
when :digest
|
44
|
+
result_key = (hash_key.to_s + '_digest').to_sym
|
45
|
+
|
46
|
+
result[result_key] = Digest::SHA256.base64digest(
|
47
|
+
result[hash_key] + options[:digest_salt])
|
48
|
+
when :encrypt
|
49
|
+
raise "No encryption key specified. Please pass :encryption_key in options to new or redact" unless options[:encryption_key]
|
50
|
+
|
51
|
+
crypt_key = options[:encryption_key]
|
52
|
+
iv = SecureRandom.random_bytes(12)
|
53
|
+
data_key = ('encrypted_' + hash_key.to_s).to_sym
|
54
|
+
iv_key = ('encrypted_' + hash_key.to_s + '_iv').to_sym
|
55
|
+
|
56
|
+
encrypted_value = EncryptorInterface.encrypt(:data, result[hash_key], iv: iv, key: crypt_key)
|
57
|
+
|
58
|
+
encrypted_value = [encrypted_value].pack(options[:encode]) if options[:encode]
|
59
|
+
iv = [iv].pack(options[:encode_iv]) if options[:encode_iv]
|
60
|
+
|
61
|
+
result[data_key] = encrypted_value
|
62
|
+
result[iv_key] = iv
|
63
|
+
else
|
64
|
+
raise "redact called with unknown operation on #{hash_key.to_s}: #{how.to_s}"
|
65
|
+
end
|
66
|
+
|
67
|
+
result.delete hash_key
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
def decrypt(data, opts = {})
|
75
|
+
options = @options.merge opts
|
76
|
+
|
77
|
+
redact_hash = options[:redact]
|
78
|
+
|
79
|
+
raise "Don't know what to decrypt. Please configure the redact hash when initializing or pass as an argument to #decrypt." unless redact_hash && redact_hash.any?
|
80
|
+
|
81
|
+
raise "No encryption key specified. Please pass :encryption_key in options to new or decrypt" unless options[:encryption_key]
|
82
|
+
|
83
|
+
result = data.clone
|
84
|
+
|
85
|
+
redact_hash.each do |hash_key,how|
|
86
|
+
if (how == :encrypt)
|
87
|
+
data_key = ('encrypted_' + hash_key.to_s).to_sym
|
88
|
+
iv_key = ('encrypted_' + hash_key.to_s + '_iv').to_sym
|
89
|
+
|
90
|
+
if (data.has_key? data_key)
|
91
|
+
iv = result[iv_key]
|
92
|
+
crypt_key = options[:encryption_key]
|
93
|
+
|
94
|
+
encrypted_value = result[data_key]
|
95
|
+
|
96
|
+
# Decode if necessary
|
97
|
+
iv = iv.unpack(options[:encode_iv]).first if options[:encode_iv]
|
98
|
+
encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
|
99
|
+
|
100
|
+
decrypted_value = EncryptorInterface.decrypt(:data, encrypted_value,
|
101
|
+
iv: iv, key: crypt_key)
|
102
|
+
|
103
|
+
result[hash_key] = decrypted_value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class EncryptorInterface
|
113
|
+
extend AttrEncrypted
|
114
|
+
|
115
|
+
attr_encrypted :data
|
116
|
+
end
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_redactor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Jensen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: attr_encrypted
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.14'
|
69
|
+
description: Removes, digests or encrypts selected values in a ruby hash
|
70
|
+
email:
|
71
|
+
- chris@broadthought.co
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .travis.yml
|
79
|
+
- CODE_OF_CONDUCT.md
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- hash_redactor.gemspec
|
87
|
+
- lib/hash_redactor.rb
|
88
|
+
- lib/hash_redactor/hash_redactor.rb
|
89
|
+
- lib/hash_redactor/version.rb
|
90
|
+
homepage: https://github.com/chrisjensen/hash_redactor
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.4.6
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Redact specified values in a hash
|
114
|
+
test_files: []
|