active_cipher_storage 1.0.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fabc72461d7687d4fe0f17c6d22979b637012b3d160b0d5b21dbd775facd6498
4
- data.tar.gz: 0a16cf1345eaf8a7962fe086113fc04e9c76350445769a8c809213c9535cfc44
3
+ metadata.gz: d9e4e6edf34d0d997fcc28765c599526e07eba891aabf24dfa6b739663002b51
4
+ data.tar.gz: 3de2f77f7bf9dd3f70049ffb2d4f988b49008ae294d1b4b9af493247fe557cdf
5
5
  SHA512:
6
- metadata.gz: 7bc1e4f3f4721a294bd28043a9672843b5a2bd26b932b2cd7390f16cbf79bec98b3b62bfd9169e6f9267f429d9fccf896309d4dae7e389bc3a6c8284c44988dc
7
- data.tar.gz: 22e3bc0328636ee8078cd679ace9f49c5ab83617ba814191f2c7d80886e19429061d7bc193f751af2202175d1af444b3b3a5d8c45fc6d9ab8ae77eeb510529f6
6
+ metadata.gz: 90621f0a221f638a47587884a3fbdce8c303edb3e64c56b7aba96a1721408118aa9fb6c66cae3491aa30e7e33484d779615e088c48e5778590d3bdc8ba1e34bf
7
+ data.tar.gz: 7989b6cb3630975d9a03c48f067f7a8985b89c6fb287f4f43717d21d79c002b046281cbcd2028ff0aea2d0afde29baad6241c584c08d0f9f618b6e92c5552127
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.3] - 2026-04-25
11
+
12
+ ### Changed
13
+
14
+ - Update the README with clearer usage guidance and improved readability.
15
+
16
+ ## [1.0.2] - 2026-04-25
17
+
18
+ ### Changed
19
+
20
+ - Publish updated RubyGems metadata for Rails Active Storage encryption, Ruby encryption/decryption, S3 streaming, multipart uploads, AES-256-GCM, and AWS KMS discoverability.
21
+
10
22
  ## [1.0.1] - 2026-04-25
11
23
 
12
24
  ### Changed
@@ -33,6 +45,8 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
33
45
  - Header-only key rotation for re-wrapping encrypted DEKs.
34
46
  - Unit and integration coverage for crypto, providers, Active Storage, S3, multipart upload, streaming, metadata, and key rotation.
35
47
 
36
- [Unreleased]: https://github.com/codebyjass/active-cipher-storage/compare/v1.0.1...HEAD
48
+ [Unreleased]: https://github.com/codebyjass/active-cipher-storage/compare/v1.0.3...HEAD
49
+ [1.0.3]: https://github.com/codebyjass/active-cipher-storage/compare/v1.0.2...v1.0.3
50
+ [1.0.2]: https://github.com/codebyjass/active-cipher-storage/compare/v1.0.1...v1.0.2
37
51
  [1.0.1]: https://github.com/codebyjass/active-cipher-storage/compare/v1.0.0...v1.0.1
38
52
  [1.0.0]: https://github.com/codebyjass/active-cipher-storage/releases/tag/v1.0.0
data/README.md CHANGED
@@ -1,83 +1,88 @@
1
- # ActiveCipherStorage
1
+ # Active Cipher Storage
2
2
 
3
3
  [![CI](https://github.com/codebyjass/active-cipher-storage/actions/workflows/ruby.yml/badge.svg)](https://github.com/codebyjass/active-cipher-storage/actions/workflows/ruby.yml)
4
4
 
5
- Transparent AES-256-GCM encryption for Rails Active Storage, direct AWS S3 usage, and backend-managed chunk uploads, with a pluggable KMS provider layer.
5
+ Active Cipher Storage is published as the `active_cipher_storage` Ruby gem.
6
6
 
7
- ActiveCipherStorage supports three upload paths:
7
+ It adds Rails Active Storage encryption and decryption without changing the way your Rails app attaches files. Files are encrypted before they are stored in AWS S3 or another storage service, and decrypted when your app reads them back.
8
8
 
9
- - **Rails Active Storage** application code keeps using normal attachment APIs while the storage service encrypts on upload and decrypts on download.
10
- - **Direct S3 clients** — service objects and non-Rails apps can call `put_encrypted`, `get_decrypted`, and `stream_decrypted`.
11
- - **Frontend chunk uploads** the frontend sends plaintext chunks to your backend; the backend encrypts those chunks and uploads encrypted S3 multipart parts.
9
+ This solves a common Rails security problem: sensitive files should be protected before they leave your application.
10
+
11
+ It works with normal Rails Active Storage attachments, direct S3 uploads from Ruby service objects, streaming downloads, and backend-managed multipart uploads for large files.
12
+
13
+ ## Features
14
+
15
+ - Encrypt files before uploading them to S3 or Active Storage.
16
+ - Decrypt files automatically when downloading.
17
+ - Works with Rails Active Storage.
18
+ - Supports direct AWS S3 client usage.
19
+ - Handles large files with streaming AES-256-GCM encryption.
20
+ - Supports backend-managed multipart uploads for frontend chunk upload flows.
21
+ - Uses pluggable key providers: environment variables, AWS KMS, or custom KMS providers.
22
+ - Supports header-only key rotation without rewriting the full file body.
23
+
24
+ ## Use Cases
25
+
26
+ - Encrypt user documents before storing them in S3.
27
+ - Secure financial records, contracts, medical files, invoices, and other sensitive uploads.
28
+ - Add application-level encryption on top of AWS S3 server-side encryption.
29
+ - Keep Rails Active Storage APIs while storing encrypted files.
30
+ - Stream large encrypted files from S3 without loading the whole file into memory.
31
+ - Meet compliance and privacy requirements around PII, GDPR, HIPAA-style data, or internal security policies.
12
32
 
13
33
  ## Contents
14
34
 
15
- 1. [How it works](#how-it-works)
16
- 2. [Installation](#installation)
17
- 3. [Rails / Active Storage setup](#rails--active-storage-setup)
18
- 4. [Standalone S3 usage](#standalone-s3-usage)
19
- 5. [Chunked multipart upload](#chunked-multipart-upload)
20
- 6. [Streaming download](#streaming-download)
21
- 7. [Manual encrypt / decrypt](#manual-encrypt--decrypt)
22
- 8. [Blob metadata](#blob-metadata)
23
- 9. [KMS providers](#kms-providers)
35
+ 1. [Features](#features)
36
+ 2. [Use Cases](#use-cases)
37
+ 3. [How it works](#how-it-works)
38
+ 4. [Installation](#installation)
39
+ 5. [Rails / Active Storage setup](#rails--active-storage-setup)
40
+ 6. [Standalone S3 usage](#standalone-s3-usage)
41
+ 7. [Chunked multipart upload](#chunked-multipart-upload)
42
+ 8. [Streaming download](#streaming-download)
43
+ 9. [Manual encrypt / decrypt](#manual-encrypt--decrypt)
44
+ 10. [Blob metadata](#blob-metadata)
45
+ 11. [KMS providers](#kms-providers)
24
46
  - [Environment-variable provider](#environment-variable-provider)
25
47
  - [AWS KMS provider](#aws-kms-provider)
26
48
  - [Custom provider](#custom-provider)
27
- 10. [Key rotation](#key-rotation)
28
- 11. [Configuration reference](#configuration-reference)
29
- 12. [Encryption format](#encryption-format)
30
- 13. [Security notes](#security-notes)
31
- 14. [Testing](#testing)
32
- 15. [Contributing](#contributing)
33
- 16. [Security reports](#security-reports)
34
- 17. [License](#license)
35
- 18. [Ruby and Rails compatibility](#ruby-and-rails-compatibility)
49
+ 12. [Key rotation](#key-rotation)
50
+ 13. [Configuration reference](#configuration-reference)
51
+ 14. [Encryption format](#encryption-format)
52
+ 15. [Security notes](#security-notes)
53
+ 16. [Testing](#testing)
54
+ 17. [Contributing](#contributing)
55
+ 18. [Security reports](#security-reports)
56
+ 19. [License](#license)
57
+ 20. [Ruby and Rails compatibility](#ruby-and-rails-compatibility)
36
58
 
37
59
  ## How it works
38
60
 
39
- Every encrypted file is self-contained. No external metadata store is needed.
40
-
41
- ```
42
- ┌─────────────────────────────────────────────────────────┐
43
- │ Plaintext file │
44
- └────────────────────────┬────────────────────────────────┘
45
-
46
- ┌──────────────▼──────────────┐
47
- │ 1. Generate random DEK │ (32 bytes, AES-256)
48
- │ per-file, per-operation │
49
- └──────────────┬──────────────┘
50
-
51
- ┌──────────────▼──────────────┐
52
- │ 2. Encrypt file with DEK │ AES-256-GCM
53
- │ unique IV per operation │ + auth tag
54
- └──────────────┬──────────────┘
55
-
56
- ┌──────────────▼──────────────┐
57
- │ 3. Wrap DEK with KMS │ ENV, AWS KMS,
58
- │ master key │ or custom
59
- └──────────────┬──────────────┘
60
-
61
- ┌──────────────▼──────────────┐
62
- │ 4. Binary payload │ Header + IV +
63
- │ (stored in S3) │ Ciphertext + Auth tag
64
- └─────────────────────────────┘
65
- ```
66
-
67
- Decryption reverses the flow: the KMS provider unwraps the DEK from the header, then AES-GCM verifies the auth tag and decrypts the ciphertext.
68
-
69
- Every encrypted payload uses the same self-describing format, whether it came from Active Storage, the direct S3 adapter, or the backend chunk upload API.
61
+ Every file gets its own random data encryption key. The file is encrypted with AES-256-GCM, and that data key is wrapped by your configured key provider.
62
+
63
+ The encrypted file is self-contained. It stores:
64
+
65
+ - a small Active Cipher Storage header,
66
+ - the encrypted data key,
67
+ - the ciphertext,
68
+ - authentication tags used to detect tampering.
69
+
70
+ When the file is downloaded, the gem reads the header, asks the key provider to unwrap the data key, verifies the AES-GCM authentication tag, and returns plaintext to your app.
71
+
72
+ The same format is used for Rails Active Storage uploads, direct S3 uploads, streaming downloads, and multipart upload flows.
70
73
 
71
74
  ## Installation
72
75
 
76
+ Add the gem to your Gemfile:
77
+
73
78
  ```ruby
74
- # Gemfile
75
79
  gem "active_cipher_storage"
80
+ ```
76
81
 
77
- # For AWS KMS provider:
78
- gem "aws-sdk-kms"
82
+ If you use AWS KMS or the direct S3 adapter, add the AWS SDK gems you need:
79
83
 
80
- # For standalone S3 adapter:
84
+ ```ruby
85
+ gem "aws-sdk-kms"
81
86
  gem "aws-sdk-s3"
82
87
  ```
83
88
 
@@ -87,7 +92,11 @@ bundle install
87
92
 
88
93
  ## Rails / Active Storage setup
89
94
 
90
- ### 1. Configure a KMS provider
95
+ Use this path when you want Rails Active Storage to encrypt attachments automatically.
96
+
97
+ Your model, controller, and view code can keep using normal Active Storage APIs. The only change is the storage service configuration.
98
+
99
+ ### 1. Configure a key provider
91
100
 
92
101
  ```ruby
93
102
  # config/initializers/active_cipher_storage.rb
@@ -154,7 +163,9 @@ user.document.attach(io: file, filename: "report.pdf")
154
163
  url = rails_blob_url(user.document)
155
164
  ```
156
165
 
157
- Active Storage transparently encrypts on upload and decrypts on download. Existing plaintext objects are still readable: if a blob does not start with the `ACS\x01` magic header, the service returns it unchanged.
166
+ Active Storage now encrypts on upload and decrypts on download.
167
+
168
+ Existing plaintext objects are still readable. If a blob does not start with the `ACS\x01` magic header, the service returns it unchanged.
158
169
 
159
170
  `config.encrypt_uploads` controls new Active Storage writes only. When disabled, new uploads are stored as plaintext and marked with `"encrypted": false` metadata. Reads continue to auto-detect by payload header, so existing encrypted blobs still decrypt correctly and existing plaintext blobs still download unchanged.
160
171
 
@@ -162,7 +173,9 @@ Direct Active Storage browser uploads are intentionally disabled because they by
162
173
 
163
174
  ## Standalone S3 usage
164
175
 
165
- No Rails required.
176
+ You can also use Active Cipher Storage without Rails.
177
+
178
+ This is useful for background jobs, service objects, scripts, or non-Rails Ruby apps that upload encrypted files directly to S3.
166
179
 
167
180
  ```ruby
168
181
  require "active_cipher_storage"
@@ -176,12 +189,12 @@ s3 = ActiveCipherStorage::Adapters::S3Adapter.new(
176
189
  region: "us-east-1"
177
190
  )
178
191
 
179
- # Encrypt and upload
192
+ # Encrypt before upload
180
193
  File.open("contract.pdf", "rb") do |f|
181
194
  s3.put_encrypted("legal/contract-2026.pdf", f)
182
195
  end
183
196
 
184
- # Download and decrypt — returns an IO
197
+ # Download and decrypt
185
198
  io = s3.get_decrypted("legal/contract-2026.pdf")
186
199
  File.binwrite("decrypted_contract.pdf", io.read)
187
200
  ```
@@ -197,9 +210,11 @@ s3 = ActiveCipherStorage::Adapters::S3Adapter.new(
197
210
 
198
211
  ## Chunked multipart upload
199
212
 
200
- For large files where the frontend sends data in separate HTTP requests, use `EncryptedMultipartUpload`. Each frontend chunk is encrypted by the backend as an authenticated ACS frame and buffered until the S3 multipart minimum part size is met, then flushed as an encrypted S3 multipart part.
213
+ For large files, many apps upload from the browser in chunks.
214
+
215
+ Active Cipher Storage supports that flow, but the browser still does not get encryption keys. The frontend sends plaintext chunks to your Rails app, and your backend encrypts those chunks before uploading encrypted multipart parts to S3.
201
216
 
202
- This flow is backend-managed. The frontend never receives encryption keys and never uploads plaintext directly to S3.
217
+ Use `EncryptedMultipartUpload` for this backend-managed upload flow.
203
218
 
204
219
  ```ruby
205
220
  uploader = ActiveCipherStorage::EncryptedMultipartUpload.new(
@@ -207,14 +222,14 @@ uploader = ActiveCipherStorage::EncryptedMultipartUpload.new(
207
222
  bucket: "my-bucket"
208
223
  )
209
224
 
210
- # --- Request 1: start the upload ---
225
+ # Request 1: start the upload
211
226
  session_id = uploader.initiate(key: "uploads/video.mp4")
212
227
  # Keep session_id for this active upload lifecycle.
213
228
 
214
- # --- Requests 2..N: send chunks (any size) ---
229
+ # Requests 2..N: send chunks
215
230
  uploader.upload_part(session_id: session_id, chunk_io: request.body)
216
231
 
217
- # --- Final request: seal and complete ---
232
+ # Final request: seal and complete
218
233
  result = uploader.complete(session_id: session_id)
219
234
  # => { status: :completed, key: "uploads/video.mp4", parts_count: 12 }
220
235
  ```
@@ -255,13 +270,14 @@ class UploadsController < ApplicationController
255
270
  end
256
271
  ```
257
272
 
258
- **Session storage:**
273
+ **Session storage**
274
+
259
275
  By default, session state is held in process memory (`MemorySessionStore`). This is intended for one active backend-managed upload lifecycle and is not durable across process restarts or deploys.
260
276
 
261
277
  For multi-process deployments where chunks for the same active upload may land on different workers or hosts, pass a shared store:
262
278
 
263
279
  ```ruby
264
- # Rails.cache backed by Redis allows cross-worker active upload sessions
280
+ # Rails.cache backed by Redis allows cross-worker active upload sessions.
265
281
  uploader = ActiveCipherStorage::EncryptedMultipartUpload.new(
266
282
  s3_client: s3_client,
267
283
  bucket: "my-bucket",
@@ -269,11 +285,13 @@ uploader = ActiveCipherStorage::EncryptedMultipartUpload.new(
269
285
  )
270
286
  ```
271
287
 
272
- **Security:** The plaintext DEK is never stored in the session. Only the KMS-wrapped encrypted DEK is persisted; it is decrypted fresh for each chunk and zeroed immediately after use.
288
+ **Security:** The plaintext data key is never stored in the session. Only the KMS-wrapped encrypted data key is persisted; it is decrypted fresh for each chunk and zeroed immediately after use.
273
289
 
274
290
  ## Streaming download
275
291
 
276
- `stream_decrypted` pipes S3 bytes through the decryptor and yields plaintext chunks on the fly. Memory usage is bounded by one ACS chunk (default 5 MiB) regardless of file size.
292
+ Use `stream_decrypted` when you need to send a large encrypted file to a client without loading the whole file into memory.
293
+
294
+ The adapter reads encrypted bytes from S3, decrypts authenticated chunks as they arrive, and yields plaintext chunks to your block. Memory usage stays bounded by one Active Cipher Storage chunk, which is 5 MiB by default.
277
295
 
278
296
  ```ruby
279
297
  s3 = ActiveCipherStorage::Adapters::S3Adapter.new(
@@ -302,13 +320,15 @@ File.open("output.bin", "wb") do |f|
302
320
  end
303
321
  ```
304
322
 
305
- `stream_decrypted` handles S3 delivering data in any chunk size the internal `StreamingDecryptor` buffers incoming bytes and emits plaintext only when a complete, authenticated ACS frame is available.
323
+ `stream_decrypted` handles S3 delivering data in any chunk size. The internal decryptor buffers incoming bytes and emits plaintext only when a complete, authenticated frame is available.
306
324
 
307
325
  Use `stream_decrypted` for chunked ACS objects. If the object is non-chunked, call `get_decrypted`; streaming a non-chunked or non-ACS/plaintext object raises `InvalidFormat` with a clear error.
308
326
 
309
327
  ## Manual encrypt / decrypt
310
328
 
311
- Use `Cipher` (in-memory) or `StreamCipher` (chunked, constant memory):
329
+ If you do not need Rails or S3 integration, you can use the lower-level cipher classes directly.
330
+
331
+ Use `Cipher` for small files and `StreamCipher` for large files:
312
332
 
313
333
  ```ruby
314
334
  require "active_cipher_storage"
@@ -317,7 +337,7 @@ ActiveCipherStorage.configure do |c|
317
337
  c.provider = ActiveCipherStorage::Providers::EnvProvider.new
318
338
  end
319
339
 
320
- # ── In-memory (small files) ─────────────────────────────
340
+ # Small files
321
341
  cipher = ActiveCipherStorage::Cipher.new
322
342
  encrypted = cipher.encrypt(File.open("secret.txt", "rb"))
323
343
  # => Binary String with embedded header, IV, ciphertext, auth tag
@@ -325,7 +345,7 @@ encrypted = cipher.encrypt(File.open("secret.txt", "rb"))
325
345
  plaintext = cipher.decrypt(encrypted)
326
346
  # => Original plaintext String
327
347
 
328
- # ── Streaming (large files) ─────────────────────────────
348
+ # Large files
329
349
  stream = ActiveCipherStorage::StreamCipher.new
330
350
 
331
351
  File.open("large.bin", "rb") do |input|
@@ -6,12 +6,12 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Jaspreet Singh"]
7
7
  spec.email = ["codebyjass@users.noreply.github.com"]
8
8
 
9
- spec.summary = "Transparent file encryption for Active Storage and S3 with pluggable KMS providers"
9
+ spec.summary = "Rails Active Storage encryption for Ruby apps"
10
10
  spec.description = <<~DESC
11
- active_cipher_storage provides AES-256-GCM envelope encryption for files stored
12
- via Rails Active Storage or directly via the AWS S3 SDK. Key management is
13
- delegated to pluggable KMS providers: environment-variable keys, AWS KMS,
14
- or any custom provider implementing the base interface.
11
+ active_cipher_storage encrypts and decrypts Rails Active Storage files with
12
+ AES-256-GCM envelope encryption. It supports AWS S3, streaming downloads,
13
+ multipart uploads, AWS KMS, environment-variable keys, and custom key
14
+ providers for Ruby and Rails applications.
15
15
  DESC
16
16
 
17
17
  spec.homepage = "https://github.com/codebyjass/active-cipher-storage"
@@ -1,3 +1,3 @@
1
1
  module ActiveCipherStorage
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_cipher_storage
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaspreet Singh
@@ -177,10 +177,10 @@ dependencies:
177
177
  - !ruby/object:Gem::Version
178
178
  version: '13.0'
179
179
  description: |
180
- active_cipher_storage provides AES-256-GCM envelope encryption for files stored
181
- via Rails Active Storage or directly via the AWS S3 SDK. Key management is
182
- delegated to pluggable KMS providers: environment-variable keys, AWS KMS,
183
- or any custom provider implementing the base interface.
180
+ active_cipher_storage encrypts and decrypts Rails Active Storage files with
181
+ AES-256-GCM envelope encryption. It supports AWS S3, streaming downloads,
182
+ multipart uploads, AWS KMS, environment-variable keys, and custom key
183
+ providers for Ruby and Rails applications.
184
184
  email:
185
185
  - codebyjass@users.noreply.github.com
186
186
  executables: []
@@ -239,6 +239,5 @@ requirements: []
239
239
  rubygems_version: 3.4.19
240
240
  signing_key:
241
241
  specification_version: 4
242
- summary: Transparent file encryption for Active Storage and S3 with pluggable KMS
243
- providers
242
+ summary: Rails Active Storage encryption for Ruby apps
244
243
  test_files: []