dotenvcrypt-core 0.7.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/LICENSE +21 -0
- data/README.md +89 -0
- data/lib/dotenvcrypt/core/encryptor.rb +71 -0
- data/lib/dotenvcrypt/core/env_loader.rb +47 -0
- data/lib/dotenvcrypt/core/version.rb +7 -0
- data/lib/dotenvcrypt/core.rb +21 -0
- data/lib/dotenvcrypt-core.rb +3 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 256f81ef09fad3aee0108ae5d490cdb5c0bf3cdce712d53160b185d1b1e6b150
|
|
4
|
+
data.tar.gz: 8274f1cdffefe95628e2e3d62af81dd7f7b3286352ab8c8ef917ed74e62e21ba
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3db1b1d2c3aaa40419b3ace5e8cda5f1e0ceb7e9eeb3a19c9c986caedf180350bfd3c2acb995398f64553aa08bb24958bb374ec64d310c6974ab51e10cadbd3a
|
|
7
|
+
data.tar.gz: 320c16b44c395c9d6eda29ac194cd2ba66bb5e8d7f1a751712ece266d9ab7cdb966297fdd39ee20c47a10b7476d24c7a582c3d5b67f0fc047630a3e51d98f2ac
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Loman
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# dotenvcrypt-core
|
|
2
|
+
|
|
3
|
+
Pure Ruby library for encrypting and decrypting .env files using AES-256-GCM encryption.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
gem install dotenvcrypt-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'dotenvcrypt-core'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Loading Encrypted ENV Files
|
|
20
|
+
|
|
21
|
+
The simplest way to use dotenvcrypt-core is to load an encrypted .env file directly into your environment:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require 'dotenvcrypt-core'
|
|
25
|
+
|
|
26
|
+
# Load encrypted .env file into ENV
|
|
27
|
+
Dotenvcrypt.load_env('.env.enc', key: 'your-secret-key')
|
|
28
|
+
|
|
29
|
+
# Now you can access your environment variables
|
|
30
|
+
puts ENV['DATABASE_URL']
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Encrypting and Decrypting Manually
|
|
34
|
+
|
|
35
|
+
For more control, you can use the Encryptor class directly:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require 'dotenvcrypt-core'
|
|
39
|
+
|
|
40
|
+
# Create an encryptor with your secret key
|
|
41
|
+
encryptor = Dotenvcrypt::Core::Encryptor.new('your-secret-key')
|
|
42
|
+
|
|
43
|
+
# Encrypt data
|
|
44
|
+
plaintext = "SECRET_KEY=my-secret-value\nAPI_TOKEN=abc123"
|
|
45
|
+
encrypted = encryptor.encrypt(plaintext)
|
|
46
|
+
File.write('.env.enc', encrypted)
|
|
47
|
+
|
|
48
|
+
# Decrypt data
|
|
49
|
+
encrypted_data = File.read('.env.enc')
|
|
50
|
+
decrypted = encryptor.decrypt(encrypted_data)
|
|
51
|
+
puts decrypted
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Error Handling
|
|
55
|
+
|
|
56
|
+
The library raises exceptions instead of printing to stdout or exiting:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
begin
|
|
60
|
+
Dotenvcrypt.load('.env.enc', key: 'wrong-key')
|
|
61
|
+
rescue Dotenvcrypt::Core::DecryptionError => e
|
|
62
|
+
puts "Failed to decrypt: #{e.message}"
|
|
63
|
+
rescue Dotenvcrypt::Core::InvalidFormatError => e
|
|
64
|
+
puts "Invalid file format: #{e.message}"
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Available exceptions:
|
|
69
|
+
- `Dotenvcrypt::Core::EncryptionError` - General encryption errors
|
|
70
|
+
- `Dotenvcrypt::Core::DecryptionError` - Decryption failed (wrong key, corrupted file, tampering)
|
|
71
|
+
- `Dotenvcrypt::Core::InvalidFormatError` - File is not in valid base64 format
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
- **AES-256-GCM encryption** - Industry-standard authenticated encryption
|
|
76
|
+
- **Pure Ruby** - No external dependencies beyond Ruby's standard library
|
|
77
|
+
- **Exception-based error handling** - Integrate cleanly into your application
|
|
78
|
+
- **Dotenv-compatible** - Works with standard .env file format
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- Uses AES-256 in GCM (Galois/Counter Mode) for authenticated encryption
|
|
83
|
+
- Generates secure random initialization vectors (IV) for each encryption
|
|
84
|
+
- Provides tamper detection via authentication tags
|
|
85
|
+
- Keys are hashed with SHA256 before use
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotenvcrypt
|
|
4
|
+
module Core
|
|
5
|
+
class EncryptionError < StandardError; end
|
|
6
|
+
class DecryptionError < StandardError; end
|
|
7
|
+
class InvalidFormatError < DecryptionError; end
|
|
8
|
+
|
|
9
|
+
class Encryptor
|
|
10
|
+
def initialize(encryption_key)
|
|
11
|
+
@encryption_key = encryption_key
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Encrypt file
|
|
15
|
+
def encrypt(data)
|
|
16
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
|
17
|
+
cipher.encrypt
|
|
18
|
+
key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
|
19
|
+
cipher.key = key
|
|
20
|
+
|
|
21
|
+
# Generate a secure random IV (12 bytes is recommended for GCM)
|
|
22
|
+
iv = cipher.random_iv
|
|
23
|
+
|
|
24
|
+
encrypted = cipher.update(data) + cipher.final
|
|
25
|
+
|
|
26
|
+
# Get the authentication tag (16 bytes)
|
|
27
|
+
auth_tag = cipher.auth_tag
|
|
28
|
+
|
|
29
|
+
# Combine IV, encrypted data, and auth tag
|
|
30
|
+
combined = iv + auth_tag + encrypted
|
|
31
|
+
|
|
32
|
+
# Convert to base64 for readability
|
|
33
|
+
Base64.strict_encode64(combined)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Decrypt file
|
|
37
|
+
def decrypt(encoded_data)
|
|
38
|
+
begin
|
|
39
|
+
data = Base64.strict_decode64(encoded_data)
|
|
40
|
+
rescue ArgumentError
|
|
41
|
+
raise InvalidFormatError, "File is not in valid base64 format"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# For GCM mode:
|
|
45
|
+
# - IV is 12 bytes
|
|
46
|
+
# - Auth tag is 16 bytes
|
|
47
|
+
iv_size = 12
|
|
48
|
+
auth_tag_size = 16
|
|
49
|
+
|
|
50
|
+
# Extract the components
|
|
51
|
+
iv = data[0...iv_size]
|
|
52
|
+
auth_tag = data[iv_size...(iv_size + auth_tag_size)]
|
|
53
|
+
encrypted_data = data[(iv_size + auth_tag_size)..-1]
|
|
54
|
+
|
|
55
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
|
56
|
+
cipher.decrypt
|
|
57
|
+
cipher.key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
|
58
|
+
cipher.iv = iv
|
|
59
|
+
cipher.auth_tag = auth_tag
|
|
60
|
+
|
|
61
|
+
cipher.update(encrypted_data) + cipher.final
|
|
62
|
+
rescue OpenSSL::Cipher::CipherError
|
|
63
|
+
raise DecryptionError, "Invalid key, corrupted file, or tampering detected"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
attr_reader :encryption_key
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dotenvcrypt
|
|
4
|
+
module Core
|
|
5
|
+
class EnvLoader
|
|
6
|
+
def self.load_env(encrypted_file_path, key:)
|
|
7
|
+
raise ArgumentError, "File not found: #{encrypted_file_path}" unless File.exist?(encrypted_file_path)
|
|
8
|
+
|
|
9
|
+
# Read and decrypt
|
|
10
|
+
encrypted_data = File.read(encrypted_file_path)
|
|
11
|
+
encryptor = Encryptor.new(key)
|
|
12
|
+
decrypted_content = encryptor.decrypt(encrypted_data)
|
|
13
|
+
|
|
14
|
+
# Parse and load into ENV
|
|
15
|
+
parse_and_load_env(decrypted_content)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def self.parse_and_load_env(content)
|
|
21
|
+
loaded = {}
|
|
22
|
+
|
|
23
|
+
content.each_line do |line|
|
|
24
|
+
line = line.strip
|
|
25
|
+
|
|
26
|
+
# Skip comments and empty lines
|
|
27
|
+
next if line.empty? || line.start_with?('#')
|
|
28
|
+
|
|
29
|
+
# Parse KEY=value format
|
|
30
|
+
if line =~ /\A([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\z/
|
|
31
|
+
key = $1
|
|
32
|
+
value = $2
|
|
33
|
+
|
|
34
|
+
# Remove surrounding quotes if present (single or double)
|
|
35
|
+
value = value.gsub(/\A["']|["']\z/, '')
|
|
36
|
+
|
|
37
|
+
# Load into ENV
|
|
38
|
+
ENV[key] = value
|
|
39
|
+
loaded[key] = value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
loaded
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require_relative 'core/version'
|
|
6
|
+
require_relative 'core/encryptor'
|
|
7
|
+
require_relative 'core/env_loader'
|
|
8
|
+
|
|
9
|
+
module Dotenvcrypt
|
|
10
|
+
module Core
|
|
11
|
+
# Convenience method for loading encrypted env files
|
|
12
|
+
def self.load_env(encrypted_file_path, key:)
|
|
13
|
+
EnvLoader.load_env(encrypted_file_path, key: key)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Also expose load_env at the top level for convenience
|
|
18
|
+
def self.load_env(encrypted_file_path, key:)
|
|
19
|
+
Core.load_env(encrypted_file_path, key: key)
|
|
20
|
+
end
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dotenvcrypt-core
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.7.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dan Loman
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Pure Ruby library for encrypting and decrypting .env files using AES-256-GCM
|
|
14
|
+
email:
|
|
15
|
+
- daniel.loman@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/dotenvcrypt-core.rb
|
|
23
|
+
- lib/dotenvcrypt/core.rb
|
|
24
|
+
- lib/dotenvcrypt/core/encryptor.rb
|
|
25
|
+
- lib/dotenvcrypt/core/env_loader.rb
|
|
26
|
+
- lib/dotenvcrypt/core/version.rb
|
|
27
|
+
homepage: https://github.com/namolnad/dotenvcrypt
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
homepage_uri: https://github.com/namolnad/dotenvcrypt
|
|
32
|
+
source_code_uri: https://github.com/namolnad/dotenvcrypt
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 2.5.0
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 3.5.22
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: Core encryption/decryption library for dotenvcrypt
|
|
52
|
+
test_files: []
|