easy_crypt 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +248 -0
- data/lib/easy_crypt/exception.rb +17 -0
- data/lib/easy_crypt/secrets_provider/base.rb +65 -0
- data/lib/easy_crypt/secrets_provider/env_provider.rb +70 -0
- data/lib/easy_crypt/secrets_provider/rails_credentials_provider.rb +82 -0
- data/lib/easy_crypt/secrets_provider.rb +53 -0
- data/lib/easy_crypt/version.rb +5 -0
- data/lib/easy_crypt.rb +188 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a3936f18965419a36551cd0cc5b5e4d8acd6bdd3afe1ba39dc9879869fb1f649
|
|
4
|
+
data.tar.gz: 852d38340c89b75221b884eb9e12d2475f06e441e19abad17e9f5ca216a13024
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3b84dd63f38fe9b6802a5ca667f38800b346e8b52040147967872db726f2e4e23de37702259a5744fc421b1c228deab46757d7b8b8966af9e4724b8dd7c50007
|
|
7
|
+
data.tar.gz: d3769217d764e084b035785d6a7d7a1e52df286ad1de46a374d3ca115d36f1d1f110c6e75d040a5ed0dd196f5e7ee940b699578778ac927dce538e950070afac
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[INSERT CONTACT METHOD].
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Benjamin Bouchet
|
|
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,248 @@
|
|
|
1
|
+
# EasyCrypt
|
|
2
|
+
|
|
3
|
+
EasyCrypt is a Ruby utility that provides secure and flexible encryption and decryption capabilities for Ruby on Rails applications. It is built on top of Rails’ ActiveSupport::MessageEncryptor, allowing you to securely and easily encrypt and decrypt data.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Multiple secrets providers support (currently _Rails credentials_ and _env variables_).
|
|
8
|
+
- Simple, purpose-based encryption/decryption API.
|
|
9
|
+
- Configurable encryption cipher.
|
|
10
|
+
- Minimal configuration required.
|
|
11
|
+
- Built-in encryption signatures to ensure data integrity.
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
14
|
+
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Configuration](#configuration)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Secrets Provider Configuration](#secrets-provider-configuration)
|
|
19
|
+
- [Encrypting Data](#encrypting-data)
|
|
20
|
+
- [Decrypting Data](#decrypting-data)
|
|
21
|
+
- [Dynamic Method Calls](#dynamic-method-calls)
|
|
22
|
+
- [Error Handling](#error-handling)
|
|
23
|
+
- [Customizing the Cipher](#customizing-the-cipher)
|
|
24
|
+
- [Advanced Topics](#advanced-topics)
|
|
25
|
+
- [Message Expiration](#message-expiration)
|
|
26
|
+
- [Performance Considerations](#performance-considerations)
|
|
27
|
+
- [Security Best Practices](#security-best-practices)
|
|
28
|
+
- [Contributing](#contributing)
|
|
29
|
+
- [License](#license)
|
|
30
|
+
- [Code of Conduct](#code-of-Conduct)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Add this line to your application's Gemfile:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
gem 'easy_crypt'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then execute:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
bundle install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or install it yourself:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
gem install easy_crypt
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
Create an initializer in your Rails application (e.g., `config/initializers/easy_crypt.rb`).
|
|
55
|
+
Configure EasyCrypt to specify how secrets are stored and which cipher to use:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
EasyCrypt.configure do |config|
|
|
59
|
+
# Choose your secrets provider. Currently supports :rails_credentials or :env_vars
|
|
60
|
+
# Default is :rails_credentials
|
|
61
|
+
config.secrets_provider = :rails_credentials
|
|
62
|
+
|
|
63
|
+
# Set your default cipher
|
|
64
|
+
# Default is identical to ActiveSupport::MessageEncryptor.default_cipher,
|
|
65
|
+
# commonly 'aes-256-gcm' or 'aes-256-cbc', depending on your Rails configuration
|
|
66
|
+
config.default_cipher = 'aes-256-gcm'
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
EasyCrypt expects you to define secrets for multiple "purposes". This means that if you’re encrypting user data, you can set up one pair of secret and salt for the "user_data" purpose. If you’re encrypting tokens for session authentication, you might use the "authentication" purpose with a different pair of secret and salt, and so on.
|
|
73
|
+
|
|
74
|
+
### Secrets Provider Configuration
|
|
75
|
+
|
|
76
|
+
EasyCrypt currently supports two types of secrets providers: environment variables (`:env_vars`) and Rails credentials (`:rails_credentials`).
|
|
77
|
+
|
|
78
|
+
#### Using Rails Credentials
|
|
79
|
+
|
|
80
|
+
With Rails credentials, you can define secrets for each purpose in `config/credentials.yml.enc`:
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
easy_crypt:
|
|
84
|
+
authentication:
|
|
85
|
+
salt: "auth-salt"
|
|
86
|
+
secret: "auth-secret"
|
|
87
|
+
user_data:
|
|
88
|
+
salt: "user-data-salt"
|
|
89
|
+
secret: "user-data-secret"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This structure allows you to define multiple purposes under the `easy_crypt` key, each with its own `salt` and `secret`. EasyCrypt will automatically retrieve the corresponding values when encrypting or decrypting using the corresponding purpose.
|
|
93
|
+
|
|
94
|
+
The example above defines "authentication" and "user_data" purpose, but you are free to pick the names that make sense for the purposes you need, as long as you follow the same structure.
|
|
95
|
+
|
|
96
|
+
#### Using Environment Variables
|
|
97
|
+
|
|
98
|
+
If you prefer using environment variables, define the salt and secret for each purpose in your `.env` file or directly in the environment. The format is:
|
|
99
|
+
|
|
100
|
+
```env
|
|
101
|
+
EASY_CRYPT_<PURPOSE>_SALT=your-salt
|
|
102
|
+
EASY_CRYPT_<PURPOSE>_SECRET=your-secret
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For example:
|
|
106
|
+
|
|
107
|
+
```env
|
|
108
|
+
EASY_CRYPT_AUTHENTICATION_SALT=auth-salt
|
|
109
|
+
EASY_CRYPT_AUTHENTICATION_SECRET=auth-secret
|
|
110
|
+
EASY_CRYPT_USER_DATA_SALT=user-data-salt
|
|
111
|
+
EASY_CRYPT_USER_DATA_SECRET=user-data-secret
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
You can then encrypt or decrypt data for these purposes.
|
|
115
|
+
|
|
116
|
+
### Encrypting Data
|
|
117
|
+
|
|
118
|
+
To encrypt a piece of data with the "user_data" purpose:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
encrypted_value = EasyCrypt.encrypt_user_data("Sensitive Information")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Internally, EasyCrypt automatically retrieves the secret and salt for the "user_data" purpose, using your configured secrets provider. It uses `ActiveSupport::MessageEncryptor` to encrypt and sign the data.
|
|
125
|
+
|
|
126
|
+
### Decrypting Data
|
|
127
|
+
|
|
128
|
+
To decrypt the data you just encrypted, call:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
decrypted_value = EasyCrypt.decrypt_user_data(encrypted_value)
|
|
132
|
+
|
|
133
|
+
if decrypted_value
|
|
134
|
+
puts "Decrypted successfully: #{decrypted_value}"
|
|
135
|
+
else
|
|
136
|
+
puts "Invalid or expired data."
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If anything goes wrong in the decryption process (e.g., invalid signature or message expiration when using time-based encryption), EasyCrypt returns `nil` rather than raising an exception.
|
|
141
|
+
|
|
142
|
+
### Dynamic Method Calls
|
|
143
|
+
|
|
144
|
+
EasyCrypt uses `method_missing` to allow dynamic encrypt/decrypt methods for different purposes, following this pattern:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
encrypt_<purpose>
|
|
148
|
+
decrypt_<purpose>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For example, if you want data to be encrypted with a "passwords" purpose:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
encrypted_pw = EasyCrypt.encrypt_passwords("super_secret")
|
|
155
|
+
decrypted_pw = EasyCrypt.decrypt_passwords(encrypted_pw)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Given that you have properly defined the secret and salt for the "passwords" purpose in your secrets provider.
|
|
159
|
+
|
|
160
|
+
### Error Handling
|
|
161
|
+
|
|
162
|
+
When decryption fails (e.g., altered data or expired message), EasyCrypt returns `nil` to avoid exposing decryption errors.
|
|
163
|
+
|
|
164
|
+
For method names that don't match the `encrypt_<purpose>` or `decrypt_<purpose>` naming pattern, a `NoMethodError` will be raised.
|
|
165
|
+
|
|
166
|
+
If your secrets provider returns invalid or missing credentials, such as when `salt` or `secret` are missing, EasyCrypt raises a `MissingSecretsConfigurationError`.
|
|
167
|
+
|
|
168
|
+
### Customizing the Cipher
|
|
169
|
+
|
|
170
|
+
EasyCrypt uses by default the cipher returned by `ActiveSupport::MessageEncryptor.default_cipher`, which depends on your application configuration.
|
|
171
|
+
|
|
172
|
+
You can override the gem’s default cipher through EasyCrypt configuration:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
EasyCrypt.configure do |config|
|
|
176
|
+
# Choose a different cipher if desired
|
|
177
|
+
config.default_cipher = 'aes-128-gcm'
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Secrets Providers can also specify cipher for a particular purpose.
|
|
182
|
+
|
|
183
|
+
#### Using Rails Credentials
|
|
184
|
+
|
|
185
|
+
In `config/credentials.yml.enc`:
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
easy_crypt:
|
|
189
|
+
data_in_transit:
|
|
190
|
+
cipher: "aes-128-gcm"
|
|
191
|
+
salt: "auth-salt"
|
|
192
|
+
secret: "auth-secret"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Here, the `data_in_transit` purpose will use the `aes-128-gcm` cipher, overriding the default cipher.
|
|
196
|
+
|
|
197
|
+
### Using Environment Variables
|
|
198
|
+
|
|
199
|
+
You can set the cipher for a purpose using an environment variable:
|
|
200
|
+
|
|
201
|
+
```env
|
|
202
|
+
EASY_CRYPT_DATA_IN_TRANSIT_CIPHER=aes-128-gcm
|
|
203
|
+
EASY_CRYPT_DATA_IN_TRANSIT_SALT=auth-salt
|
|
204
|
+
EASY_CRYPT_DATA_IN_TRANSIT_SECRET=auth-secret
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Here, the `data_in_transit` purpose will use the `aes-128-gcm` cipher, overriding the default cipher.
|
|
208
|
+
|
|
209
|
+
## Advanced Topics
|
|
210
|
+
|
|
211
|
+
### Message Expiration
|
|
212
|
+
|
|
213
|
+
EasyCrypt supports passing `expires_at` or `expires_in` to the underlying `encrypt_and_sign` method as described in [Rails' documentation](ActiveSupport::MessageEncryptor.default_cipher). If provided, the data becomes invalid after that time:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
encrypted = EasyCrypt.encrypt_user_data("Sensitive Info", expires_in: 1.hour)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Performance Considerations
|
|
220
|
+
|
|
221
|
+
If you need to handle large data encryption, consider compressing it before calling the encrypt methods. Also, avoid redundant encryption in tight loops for better performance.
|
|
222
|
+
|
|
223
|
+
### Security Best Practices
|
|
224
|
+
|
|
225
|
+
This list is not exhaustive:
|
|
226
|
+
|
|
227
|
+
- __Use Strong Algorithms__: Choose encryption methods wisely, AES-256 for symmetric and RSA-2048+ or elliptic curves for asymmetric encryption.
|
|
228
|
+
- __Hashing for Validation__: Use secure hashing algorithms (e.g., bcrypt, Argon2) to validate secrets you don't need to store, like passwords.
|
|
229
|
+
- __Sign for Integrity__: Sign data to prevent tampering (e.g., with RSA or ECDSA).
|
|
230
|
+
- __Store Secrets Securely__: Use secret management tools (e.g., HashiCorp Vault).
|
|
231
|
+
- __Rotate Secrets Periodically__: Regularly update keys and credentials to limit exposure.
|
|
232
|
+
- __Avoid Exposing Secrets__: Never log sensitive information or hardcode secrets in code or repositories.
|
|
233
|
+
- __Encrypt Everywhere__: Encrypt data at rest, in transit, and even on trusted networks.
|
|
234
|
+
- __Rely on Proven Libraries__: Avoid building custom encryption; use trusted libraries (e.g., OpenSSL, EasyCrypt). Note: EasyCrypt uses OpenSSL under the hood.
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/randoum/easy_crypt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/randoum/easy_crypt/blob/main/CODE_OF_CONDUCT.md).
|
|
239
|
+
|
|
240
|
+
Adding a rotation feature could be a good contribution, if you feel like doing so.
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
245
|
+
|
|
246
|
+
## Code of Conduct
|
|
247
|
+
|
|
248
|
+
Everyone interacting in the EasyCrypt project's codebases and issue trackers is expected to follow the [code of conduct](https://github.com/randoum/easy_crypt/blob/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyCrypt
|
|
4
|
+
# Exception raised when the requested secrets provider does not exist
|
|
5
|
+
class InvalidSecretsProvider < StandardError
|
|
6
|
+
def initialize(str)
|
|
7
|
+
super("Unknown secrets provider `#{str}`")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Exception raised when secrets are missing for the requested purpose
|
|
12
|
+
class MissingSecretsConfigurationError < StandardError
|
|
13
|
+
def initialize(purpose, missing_attributes)
|
|
14
|
+
super("Missing required secret configuration for '#{purpose}': #{missing_attributes.join(', ')}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyCrypt
|
|
4
|
+
class SecretsProvider
|
|
5
|
+
# Base class for all secret providers. Implements the interface that
|
|
6
|
+
# concrete providers must follow.
|
|
7
|
+
#
|
|
8
|
+
# This abstract base class defines the structure for secrets providers, including
|
|
9
|
+
# required methods for retrieving encryption configuration values such as `salt`,
|
|
10
|
+
# `secret`, and `cipher`. Subclasses are expected to implement these methods based
|
|
11
|
+
# on their respective sources (e.g., environment variables, Rails credentials).
|
|
12
|
+
#
|
|
13
|
+
# @abstract Subclass and override {#secret}, {#salt}, and {#cipher} to implement
|
|
14
|
+
# a custom secrets provider.
|
|
15
|
+
class Base
|
|
16
|
+
# @return [Symbol] The purpose of the credential being accessed
|
|
17
|
+
attr_reader :purpose
|
|
18
|
+
|
|
19
|
+
# Initialize a new secrets provider
|
|
20
|
+
#
|
|
21
|
+
# @param purpose [Symbol] The purpose of the credential (e.g., :authentication)
|
|
22
|
+
def initialize(purpose)
|
|
23
|
+
@purpose = purpose
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Validates the presence of required attributes for the secrets provider.
|
|
27
|
+
#
|
|
28
|
+
# Checks if the attributes `salt`, and `secret` are defined.
|
|
29
|
+
# Raises a `MissingSecretsConfigurationError` error if any of these attributes are missing.
|
|
30
|
+
#
|
|
31
|
+
# @raise [MissingSecretsConfigurationError] If any required attribute is missing.
|
|
32
|
+
def validate_attributes!
|
|
33
|
+
missing_attributes = %w[salt secret].reject { |attr| send(attr) }
|
|
34
|
+
raise MissingSecretsConfigurationError.new(purpose, missing_attributes) if missing_attributes.any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the encryption cipher.
|
|
38
|
+
#
|
|
39
|
+
# @abstract
|
|
40
|
+
# @return [String, nil] The encryption cipher.
|
|
41
|
+
# @raise [NotImplementedError] If not implemented in the subclass.
|
|
42
|
+
def cipher
|
|
43
|
+
raise NotImplementedError, 'Subclasses must implement #cipher'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the salt value for key generation
|
|
47
|
+
#
|
|
48
|
+
# @abstract
|
|
49
|
+
# @return [String] The salt value
|
|
50
|
+
# @raise [NotImplementedError] If the subclass doesn't implement this method
|
|
51
|
+
def salt
|
|
52
|
+
raise NotImplementedError, 'Subclasses must implement #salt'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the secret key for encryption
|
|
56
|
+
#
|
|
57
|
+
# @abstract
|
|
58
|
+
# @return [String] The secret key
|
|
59
|
+
# @raise [NotImplementedError] If the subclass doesn't implement this method
|
|
60
|
+
def secret
|
|
61
|
+
raise NotImplementedError, 'Subclasses must implement #secret'
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyCrypt
|
|
4
|
+
class SecretsProvider
|
|
5
|
+
# Provider that fetches encryption configuration from environment variables
|
|
6
|
+
#
|
|
7
|
+
# Multiple credential purposes can be configured using environment variables.
|
|
8
|
+
# Each credential purpose requires three environment variables following this pattern:
|
|
9
|
+
#
|
|
10
|
+
# EASY_CRYPT_[PURPOSE]_CIPHER - (optional) Cipher to use for the encryption
|
|
11
|
+
# falls back to EasyCrypt.default_cipher is not set
|
|
12
|
+
# EASY_CRYPT_[PURPOSE]_SALT - Salt value for the encryption
|
|
13
|
+
# EASY_CRYPT_[PURPOSE]_SECRET - Secret key for the encryption
|
|
14
|
+
#
|
|
15
|
+
# Where [PURPOSE] is the uppercase version of the credential purpose
|
|
16
|
+
#
|
|
17
|
+
# @example Using authentication credentials
|
|
18
|
+
# # In .env file or environment:
|
|
19
|
+
# EASY_CRYPT_AUTHENTICATION_CIPHER=aes128
|
|
20
|
+
# EASY_CRYPT_AUTHENTICATION_SALT=your-salt-value
|
|
21
|
+
# EASY_CRYPT_AUTHENTICATION_SECRET=your-secret-key
|
|
22
|
+
#
|
|
23
|
+
# provider = EnvProvider.new(:authentication)
|
|
24
|
+
# provider.cipher #=> "aes128"
|
|
25
|
+
# provider.salt #=> "your-salt-value"
|
|
26
|
+
# provider.secret #=> "your-secret-key"
|
|
27
|
+
#
|
|
28
|
+
# @example Using user_data credentials
|
|
29
|
+
# # In .env file or environment:
|
|
30
|
+
# EASY_CRYPT_USER_DATA_SALT=different-salt
|
|
31
|
+
# EASY_CRYPT_USER_DATA_SECRET=different-secret
|
|
32
|
+
#
|
|
33
|
+
# provider = EnvProvider.new(:user_data)
|
|
34
|
+
# provider.cipher #=> nil
|
|
35
|
+
# provider.salt #=> "different-salt"
|
|
36
|
+
# provider.secret #=> "different-secret"
|
|
37
|
+
#
|
|
38
|
+
# You can set these variables in your .env file:
|
|
39
|
+
# echo "EASY_CRYPT_AUTHENTICATION_CIPHER=aes128" >> .env
|
|
40
|
+
# echo "EASY_CRYPT_AUTHENTICATION_SALT=your-salt" >> .env
|
|
41
|
+
# echo "EASY_CRYPT_AUTHENTICATION_SECRET=your-secret" >> .env
|
|
42
|
+
#
|
|
43
|
+
# Or export them directly in your environment:
|
|
44
|
+
# export EASY_CRYPT_AUTHENTICATION_CIPHER=aes128
|
|
45
|
+
# export EASY_CRYPT_AUTHENTICATION_SALT=your-salt
|
|
46
|
+
# export EASY_CRYPT_AUTHENTICATION_SECRET=your-secret
|
|
47
|
+
class EnvProvider < Base
|
|
48
|
+
# @return [String, nil] The encryption cipher from environment variable
|
|
49
|
+
def cipher
|
|
50
|
+
credentials('CIPHER')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [String, nil] The salt value from environment variable
|
|
54
|
+
def salt
|
|
55
|
+
credentials('SALT')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [String, nil] The secret key from environment variable
|
|
59
|
+
def secret
|
|
60
|
+
credentials('SECRET')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def credentials(key)
|
|
66
|
+
ENV.fetch("EASY_CRYPT_#{purpose.to_s.upcase}_#{key}", nil)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyCrypt
|
|
4
|
+
class SecretsProvider
|
|
5
|
+
# Provider that fetches encryption configuration from Rails credentials
|
|
6
|
+
#
|
|
7
|
+
# Configuration should be structured in credentials.yml.enc as:
|
|
8
|
+
#
|
|
9
|
+
# easy_crypt: # EasyCrypt purposes configuration parent key
|
|
10
|
+
# authentication: # Credential for the "authentication" purpose
|
|
11
|
+
# cipher: "aes128" # (optional) Cipher to use for the encryption
|
|
12
|
+
# # falls back to EasyCrypt.default_cipher is not set
|
|
13
|
+
# salt: "your-salt-value" # Salt value for the encryption
|
|
14
|
+
# secret: "your-secret-key" # Secret key for the encryption
|
|
15
|
+
# user_data: # Credential for the "user data" purpose
|
|
16
|
+
# salt: "different-salt" # Salt value for the encryption
|
|
17
|
+
# secret: "different-secret" # Secret key for the encryption
|
|
18
|
+
#
|
|
19
|
+
# You can define multiple credential purposes under the `easy_crypt` namespace,
|
|
20
|
+
# each with its own configuration. The credential purpose is specified when
|
|
21
|
+
# initializing the provider.
|
|
22
|
+
#
|
|
23
|
+
# To edit credentials.yml.enc, you can use one of these commands:
|
|
24
|
+
#
|
|
25
|
+
# With Visual Studio Code:
|
|
26
|
+
# EDITOR="code --wait" bin/rails credentials:edit
|
|
27
|
+
#
|
|
28
|
+
# With Vim:
|
|
29
|
+
# EDITOR="vim" bin/rails credentials:edit
|
|
30
|
+
#
|
|
31
|
+
# With Sublime Text:
|
|
32
|
+
# EDITOR="subl --wait" bin/rails credentials:edit
|
|
33
|
+
#
|
|
34
|
+
# With Nano:
|
|
35
|
+
# EDITOR="nano" bin/rails credentials:edit
|
|
36
|
+
#
|
|
37
|
+
# @example Configuration in credentials.yml.enc
|
|
38
|
+
# easy_crypt:
|
|
39
|
+
# authentication:
|
|
40
|
+
# cipher: aes128
|
|
41
|
+
# salt: "salt for the authentication purpose"
|
|
42
|
+
# secret: "secret for the authentication purpose"
|
|
43
|
+
# user_data:
|
|
44
|
+
# salt: "different-salt"
|
|
45
|
+
# secret: "different-secret"
|
|
46
|
+
#
|
|
47
|
+
# @example Accessing credentials
|
|
48
|
+
# # Using authentication credentials
|
|
49
|
+
# provider = RailsCredentialsProvider.new(:authentication)
|
|
50
|
+
# provider.cipher #=> "aes128"
|
|
51
|
+
# provider.salt #=> "salt for the authentication purpose"
|
|
52
|
+
# provider.secret #=> "secret for the authentication purpose"
|
|
53
|
+
#
|
|
54
|
+
# # Using user_data credentials
|
|
55
|
+
# provider = RailsCredentialsProvider.new(:user_data)
|
|
56
|
+
# provider.cipher #=> nil
|
|
57
|
+
# provider.salt #=> "different-salt"
|
|
58
|
+
# provider.secret #=> "different-secret"
|
|
59
|
+
class RailsCredentialsProvider < Base
|
|
60
|
+
# @return [String, nil] The encryption cipher from Rails credentials
|
|
61
|
+
def cipher
|
|
62
|
+
credentials&.cipher
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [String] The salt value from Rails credentials
|
|
66
|
+
def salt
|
|
67
|
+
credentials&.salt
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [String] The secret key from Rails credentials
|
|
71
|
+
def secret
|
|
72
|
+
credentials&.secret
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def credentials
|
|
78
|
+
Rails.application.credentials.easy_crypt[purpose]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'secrets_provider/base'
|
|
4
|
+
require_relative 'secrets_provider/env_provider'
|
|
5
|
+
require_relative 'secrets_provider/rails_credentials_provider'
|
|
6
|
+
|
|
7
|
+
module EasyCrypt
|
|
8
|
+
# The SecretsProvider class serves as a factory for creating secret providers
|
|
9
|
+
# that supply encryption configuration values from different sources.
|
|
10
|
+
#
|
|
11
|
+
# @example Creating a provider for Rails credentials and for the authentication purpose
|
|
12
|
+
# provider = SecretsProvider.build(:rails_credentials, :authentication)
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a provider for environment variables and for the user_data purpose
|
|
15
|
+
# provider = SecretsProvider.build(:env_vars, :user_data)
|
|
16
|
+
class SecretsProvider
|
|
17
|
+
# Available secrets providers and their corresponding classes
|
|
18
|
+
PROVIDERS = {
|
|
19
|
+
env_vars: EnvProvider,
|
|
20
|
+
rails_credentials: RailsCredentialsProvider
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
# Builds a new secrets provider instance based on the specified provider
|
|
24
|
+
#
|
|
25
|
+
# This method acts as a factory for instantiating a secrets provider that fetches
|
|
26
|
+
# encryption configurations (e.g., secret keys, salts) for a given purpose from
|
|
27
|
+
# the selected source. The method ensures the specified provider exists and validates
|
|
28
|
+
# the configuration values.
|
|
29
|
+
#
|
|
30
|
+
# @param provider [Symbol] The secrets provider to use (:env_vars or :rails_credentials).
|
|
31
|
+
# - `:env_vars`: To fetch secrets from environment variables.
|
|
32
|
+
# - `:rails_credentials`: To fetch secrets from Rails credentials.
|
|
33
|
+
# @param purpose [Symbol] The purpose of the credential (e.g., :authentication, :user_data).
|
|
34
|
+
# This links the encryption secrets to a set of credentials (such as secret key and salt).
|
|
35
|
+
#
|
|
36
|
+
# @example Building a provider for Rails credentials with the `authentication` purpose
|
|
37
|
+
# provider = SecretsProvider.build(:rails_credentials, :authentication)
|
|
38
|
+
#
|
|
39
|
+
# @example Building a provider for environment variables with the `user_data` purpose
|
|
40
|
+
# provider = SecretsProvider.build(:env_vars, :user_data)
|
|
41
|
+
#
|
|
42
|
+
# @raise [InvalidSecretsProvider] If the specified secrets provider is not recognized.
|
|
43
|
+
#
|
|
44
|
+
# @return [Base] An instance of the appropriate provider class.
|
|
45
|
+
def self.build(provider, purpose)
|
|
46
|
+
provider = PROVIDERS
|
|
47
|
+
.fetch(provider.to_sym) { raise InvalidSecretsProvider, provider }
|
|
48
|
+
.new(purpose)
|
|
49
|
+
provider.validate_attributes!
|
|
50
|
+
provider
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/easy_crypt.rb
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails'
|
|
4
|
+
require 'active_support/message_encryptor'
|
|
5
|
+
require 'active_support/key_generator'
|
|
6
|
+
|
|
7
|
+
require_relative 'easy_crypt/exception'
|
|
8
|
+
require_relative 'easy_crypt/secrets_provider'
|
|
9
|
+
|
|
10
|
+
# EasyCrypt
|
|
11
|
+
# =========
|
|
12
|
+
#
|
|
13
|
+
# EasyCrypt is a Ruby utility that provides secure and flexible encryption/decryption capabilities
|
|
14
|
+
# for Ruby on Rails applications.
|
|
15
|
+
#
|
|
16
|
+
# Features
|
|
17
|
+
# --------
|
|
18
|
+
# * Multiple secrets providers support (environment variables and Rails credentials)
|
|
19
|
+
# * Configurable encryption cipher
|
|
20
|
+
# * Purpose-based encryption/decryption
|
|
21
|
+
# * Built on top of Rails' ActiveSupport::MessageEncryptor
|
|
22
|
+
# * Simple and intuitive API
|
|
23
|
+
#
|
|
24
|
+
# Installation
|
|
25
|
+
# ------------
|
|
26
|
+
# Add this line to your application's Gemfile:
|
|
27
|
+
# gem 'easy_crypt'
|
|
28
|
+
#
|
|
29
|
+
# Then execute:
|
|
30
|
+
# $ bundle install
|
|
31
|
+
#
|
|
32
|
+
# Or install it yourself as:
|
|
33
|
+
# $ gem install easy_crypt
|
|
34
|
+
#
|
|
35
|
+
# Configuration
|
|
36
|
+
# -------------
|
|
37
|
+
#
|
|
38
|
+
# Create an initializer at config/initializers/easy_crypt.rb:
|
|
39
|
+
#
|
|
40
|
+
# ```ruby
|
|
41
|
+
# EasyCrypt.configure do |config|
|
|
42
|
+
# # Choose your secrets provider. Currently supports :rails_credentials or :env_vars.
|
|
43
|
+
# # Default to :rails_credentials
|
|
44
|
+
# config.secrets_provider = :rails_credentials
|
|
45
|
+
#
|
|
46
|
+
# # Set your default cipher (optional)
|
|
47
|
+
# # Default to `ActiveSupport::MessageEncryptor.default_cipher` which is set to
|
|
48
|
+
# # 'aes-256-gcm' or 'aes-256-cbc' as of now, depending on your application configuration.
|
|
49
|
+
# # See `use_authenticated_message_encryption` configuration option for more information.
|
|
50
|
+
# # See `OpenSSL::Cipher.ciphers` for a list of accepted values.
|
|
51
|
+
# config.default_cipher = 'aes-256-gcm'
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# Usage
|
|
55
|
+
# ---
|
|
56
|
+
#
|
|
57
|
+
# First, you need to configure a secret and a salt for a specific purpose.
|
|
58
|
+
# To do so, you'll need to use one of the supported secrets providers (currently
|
|
59
|
+
# Rails credentials and env vars) and define a purpose and its credentials.
|
|
60
|
+
#
|
|
61
|
+
# See `EasyCrypt::SecretsProvider` subclasses for more information on how
|
|
62
|
+
# to configure the secrets provider of your choice.
|
|
63
|
+
#
|
|
64
|
+
# Given that you provided a secret and a salt for the "user_data" purpose,
|
|
65
|
+
# you are able to call the `encrypt_user_data` and `decrypt_user_data` methods
|
|
66
|
+
# to directly use the credentials of the "user_data" purpose.
|
|
67
|
+
#
|
|
68
|
+
# ```ruby
|
|
69
|
+
# # Encrypt a value
|
|
70
|
+
# encrypted = EasyCrypt.encrypt_user_data("sensitive information")
|
|
71
|
+
#
|
|
72
|
+
# # Decrypt a value
|
|
73
|
+
# decrypted = EasyCrypt.decrypt_user_data(encrypted)
|
|
74
|
+
# ```
|
|
75
|
+
#
|
|
76
|
+
module EasyCrypt
|
|
77
|
+
# @return [String] The default cipher used for encryption/decryption operations
|
|
78
|
+
@default_cipher = ActiveSupport::MessageEncryptor.default_cipher
|
|
79
|
+
|
|
80
|
+
# @return [Symbol] The provider used for retrieving encryption secrets
|
|
81
|
+
@secrets_provider = :rails_credentials
|
|
82
|
+
|
|
83
|
+
class << self
|
|
84
|
+
attr_accessor :default_cipher, :secrets_provider
|
|
85
|
+
|
|
86
|
+
# Configures the EasyCrypt module with custom settings
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# EasyCrypt.configure do |config|
|
|
90
|
+
# config.secrets_provider = :env_vars
|
|
91
|
+
# config.default_cipher = 'aes-128-gcm'
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# @yieldparam config [EasyCrypt] The configuration object
|
|
95
|
+
# @return [void]
|
|
96
|
+
def configure
|
|
97
|
+
yield self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Handles dynamic encryption/decryption method calls
|
|
101
|
+
#
|
|
102
|
+
# This method allows dynamically calling encryption or decryption methods for specific purposes
|
|
103
|
+
# by embedding the purpose directly in the method name. The pattern for such method names is:
|
|
104
|
+
# `encrypt_<purpose>` for encryption and `decrypt_<purpose>` for decryption.
|
|
105
|
+
#
|
|
106
|
+
# For instance:
|
|
107
|
+
# - Calling `EasyCrypt.encrypt_authentication(value)` will encrypt the provided value using the
|
|
108
|
+
# credentials configured for the `authentication` purpose.
|
|
109
|
+
# - Calling `EasyCrypt.decrypt_user_data(encrypted_value)` will decrypt the given value using the
|
|
110
|
+
# credentials for the `user_data` purpose.
|
|
111
|
+
#
|
|
112
|
+
# This dynamic approach enables simple and intuitive APIs without requiring explicit method
|
|
113
|
+
# definitions for each purpose.
|
|
114
|
+
#
|
|
115
|
+
# @example Encrypting a value for the "authentication" purpose
|
|
116
|
+
# encrypted = EasyCrypt.encrypt_authentication("my_secret_data")
|
|
117
|
+
#
|
|
118
|
+
# @example Decrypting a value for the "user_data" purpose
|
|
119
|
+
# decrypted = EasyCrypt.decrypt_user_data(encrypted_data)
|
|
120
|
+
#
|
|
121
|
+
# @note If the method name does not match the expected pattern, `method_missing` will defer to
|
|
122
|
+
# the superclass implementation, raising a `NoMethodError` if the method is undefined.
|
|
123
|
+
#
|
|
124
|
+
# @return [Object, String, nil] The encrypted or decrypted value, or nil if decryption fails
|
|
125
|
+
def method_missing(method_name, *args)
|
|
126
|
+
if /^(?<action>encrypt|decrypt)_(?<purpose>[a-z_]+)$/ =~ method_name
|
|
127
|
+
send(action, *args.unshift(purpose.to_sym))
|
|
128
|
+
else
|
|
129
|
+
super
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Checks if a method is supported by the module
|
|
134
|
+
def respond_to_missing?(method_name, include_private)
|
|
135
|
+
/^(encrypt|decrypt)_[a-z_]+$/ =~ method_name || super
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
# Encrypts and signs a value for a specific purpose
|
|
141
|
+
#
|
|
142
|
+
# @param purpose [Symbol] The purpose for which the value is being encrypted, e.g., :user_data
|
|
143
|
+
# @param value [Object] The value to encrypt (must be serializable)
|
|
144
|
+
# @param options [Hash] Additional options for encryption
|
|
145
|
+
#
|
|
146
|
+
# @example Encrypting a value for the "user_data" purpose
|
|
147
|
+
# EasyCrypt.encrypt_user_data("sensitive information")
|
|
148
|
+
#
|
|
149
|
+
# @return [String] The encrypted and signed value
|
|
150
|
+
def encrypt(purpose, value, options = {})
|
|
151
|
+
options.merge!(purpose: purpose)
|
|
152
|
+
encryptor(purpose).encrypt_and_sign(value, **options)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Decrypts a value that was encrypted for a specific purpose
|
|
156
|
+
#
|
|
157
|
+
# @param purpose [Symbol] The purpose for which the value was encrypted, e.g., :user_data
|
|
158
|
+
# @param encrypted_data [String] The encrypted data to decrypt
|
|
159
|
+
#
|
|
160
|
+
# @example Decrypting a value for the "user_data" purpose
|
|
161
|
+
# EasyCrypt.decrypt_user_data(encrypted_value)
|
|
162
|
+
#
|
|
163
|
+
# @return [Object, nil] The decrypted value, or nil if decryption fails
|
|
164
|
+
def decrypt(purpose, encrypted_data)
|
|
165
|
+
encryptor(purpose).decrypt_and_verify(encrypted_data, purpose: purpose)
|
|
166
|
+
rescue ActiveSupport::MessageEncryptor::InvalidMessage
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Creates a new MessageEncryptor instance for a specific purpose
|
|
171
|
+
#
|
|
172
|
+
# @param purpose [Symbol] The purpose for which to create the encryptor, e.g., :user_data
|
|
173
|
+
#
|
|
174
|
+
# @example Creating an encryptor configured for the "user_data" purpose
|
|
175
|
+
# encryptor = EasyCrypt.send(:encryptor, :user_data)
|
|
176
|
+
#
|
|
177
|
+
# @return [ActiveSupport::MessageEncryptor] The configured encryptor instance
|
|
178
|
+
def encryptor(purpose)
|
|
179
|
+
provider = SecretsProvider.build(secrets_provider, purpose)
|
|
180
|
+
|
|
181
|
+
cipher = provider.cipher || default_cipher
|
|
182
|
+
|
|
183
|
+
len = ActiveSupport::MessageEncryptor.key_len(cipher)
|
|
184
|
+
key = ActiveSupport::KeyGenerator.new(provider.secret).generate_key(provider.salt, len)
|
|
185
|
+
ActiveSupport::MessageEncryptor.new(key, cipher: cipher)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: easy_crypt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Benjamin Bouchet
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-01-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: |2-
|
|
14
|
+
|
|
15
|
+
Encryption and decryption for Rails applications, based on ActiveSupport::MessageEncryptor, using
|
|
16
|
+
multiple secrets providers, and defining a simple, purpose-based encryption/decryption API.
|
|
17
|
+
email: randoum@gmail.com
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- CHANGELOG.md
|
|
23
|
+
- CODE_OF_CONDUCT.md
|
|
24
|
+
- LICENSE.txt
|
|
25
|
+
- README.md
|
|
26
|
+
- lib/easy_crypt.rb
|
|
27
|
+
- lib/easy_crypt/exception.rb
|
|
28
|
+
- lib/easy_crypt/secrets_provider.rb
|
|
29
|
+
- lib/easy_crypt/secrets_provider/base.rb
|
|
30
|
+
- lib/easy_crypt/secrets_provider/env_provider.rb
|
|
31
|
+
- lib/easy_crypt/secrets_provider/rails_credentials_provider.rb
|
|
32
|
+
- lib/easy_crypt/version.rb
|
|
33
|
+
homepage: https://github.com/randoum/easy_crypt
|
|
34
|
+
licenses:
|
|
35
|
+
- MIT
|
|
36
|
+
metadata:
|
|
37
|
+
homepage_uri: https://github.com/randoum/easy_crypt
|
|
38
|
+
documentation_uri: https://rubydoc.info/github/randoum/easy_crypt
|
|
39
|
+
changelog_uri: https://github.com/randoum/easy_crypt/blob/main/CHANGELOG.md
|
|
40
|
+
source_code_uri: https://github.com/randoum/easy_crypt
|
|
41
|
+
bug_tracker_uri: https://github.com/randoum/easy_crypt/issues
|
|
42
|
+
rubygems_mfa_required: 'true'
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 2.7.0
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: 1.8.11
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.1.2
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: Secure and flexible encryption for Ruby on Rails applications.
|
|
62
|
+
test_files: []
|