dotenvcrypt 0.4.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 +74 -0
- data/bin/dotenvcrypt +67 -0
- data/lib/dotenvcrypt/manager.rb +144 -0
- data/lib/dotenvcrypt/version.rb +3 -0
- data/lib/dotenvcrypt.rb +2 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 281a89c7380b61972623d6d94ab5e10911b07d49abd05653974b60c83c0a243c
|
4
|
+
data.tar.gz: cd25bc6834e0aa6bd8927a3ca5e20fd00712967abd446c537109de7bc6a5e9ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ab9cb7e645289a41a17e2f534d58ad59cd891f95d5665613d25329a11815bc154ba74e6ab8eba43f7647561f818ba177b249d42fe6078a5d7be9b8e84a2a35a
|
7
|
+
data.tar.gz: 6bbe096aca3355525202c64e9fdd79a6942441f7e7f7ab8457d48a81a389696e88f21c4fa9b6708ca038feca44538bce8a32209e8f3ee73fa882473a6f96591a
|
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,74 @@
|
|
1
|
+
# dotenvcrypt 🛡️🔐
|
2
|
+
|
3
|
+
**Securely encrypt, manage, and load your `.env` files in public repositories.**
|
4
|
+
|
5
|
+
> Inspired by `rails credentials`, `dotenvcrypt` ensures your API keys and other `.env` secrets are encrypted while keeping your workflow simple.
|
6
|
+
|
7
|
+
---
|
8
|
+
|
9
|
+
## 🚀 Features
|
10
|
+
|
11
|
+
- ✅ Encrypt `.env` files into `.env.enc` for safe storage in Git.
|
12
|
+
- ✅ Decrypt and load environment variables securely into the shell.
|
13
|
+
- ✅ Edit encrypted `.env.enc`, then re-encrypt after saving.
|
14
|
+
|
15
|
+
---
|
16
|
+
|
17
|
+
## 📦 Installation
|
18
|
+
|
19
|
+
### Using Homebrew (recommended)
|
20
|
+
|
21
|
+
```bash
|
22
|
+
brew install namolnad/formulae/dotenvcrypt
|
23
|
+
```
|
24
|
+
|
25
|
+
## 🔧 Usage
|
26
|
+
|
27
|
+
### Basic Commands
|
28
|
+
|
29
|
+
```bash
|
30
|
+
# Encrypt a .env file
|
31
|
+
dotenvcrypt encrypt .env .env.enc
|
32
|
+
|
33
|
+
# Decrypt an encrypted file
|
34
|
+
dotenvcrypt decrypt .env.enc
|
35
|
+
|
36
|
+
# Edit an encrypted file (decrypts, opens editor, re-encrypts)
|
37
|
+
dotenvcrypt edit .env.enc
|
38
|
+
|
39
|
+
# Load encrypted environment variables into your shell
|
40
|
+
eval "$(dotenvcrypt decrypt .env.enc)"
|
41
|
+
```
|
42
|
+
|
43
|
+
### Key Management
|
44
|
+
|
45
|
+
dotenvcrypt looks for your encryption key in these locations (in order):
|
46
|
+
|
47
|
+
1. Command line argument: `--key YOUR_SECRET_KEY`
|
48
|
+
1. Environment variable: `DOTENVCRYPT_KEY`
|
49
|
+
1. File: `./.dotenvcrypt.key` (in the current directory)
|
50
|
+
1. File: `$XDG_CONFIG_HOME/dotenvcrypt/secret.key` (or `$HOME/.config/dotenvcrypt/secret.key`)
|
51
|
+
1. File: `$HOME/.dotenvcrypt.key`
|
52
|
+
1. Interactive prompt (if no key is found)
|
53
|
+
|
54
|
+
### Real-World Example
|
55
|
+
|
56
|
+
Add this to your shell profile (`.zshrc`, `.bashrc`, etc.) to automatically load variables:
|
57
|
+
|
58
|
+
```bash
|
59
|
+
# Set up encryption key (example using 1Password CLI)
|
60
|
+
dotenvcrypt_key_path="$XDG_CONFIG_HOME/dotenvcrypt/secret.key"
|
61
|
+
if [[ ! -f $dotenvcrypt_key_path || ! -s $dotenvcrypt_key_path ]]; then
|
62
|
+
mkdir -p $(dirname $dotenvcrypt_key_path)
|
63
|
+
# Replace with your preferred key storage method
|
64
|
+
(op item get your-item-reference --fields password) > $dotenvcrypt_key_path
|
65
|
+
chmod 600 $dotenvcrypt_key_path
|
66
|
+
fi
|
67
|
+
|
68
|
+
# Load encrypted environment variables if envcrypt is installed
|
69
|
+
if command -v dotenvcrypt &> /dev/null; then
|
70
|
+
set -a # automatically export all variables
|
71
|
+
eval "$(dotenvcrypt decrypt $HOME/.env.enc)"
|
72
|
+
set +a # stop automatically exporting
|
73
|
+
fi
|
74
|
+
```
|
data/bin/dotenvcrypt
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'dotenvcrypt'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
# CLI Commands with argument parsing
|
8
|
+
options = {}
|
9
|
+
command = nil
|
10
|
+
|
11
|
+
UNENCRYPTED_FILE = '.env'.freeze
|
12
|
+
ENCRYPTED_FILE = '.env.enc'.freeze
|
13
|
+
|
14
|
+
opt_parser = OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: dotenvcrypt <command> [options]"
|
16
|
+
|
17
|
+
opts.separator ""
|
18
|
+
opts.separator "Commands:"
|
19
|
+
opts.separator " encrypt [input_file] [output_file] Encrypt .env file (default: #{UNENCRYPTED_FILE} to #{ENCRYPTED_FILE})"
|
20
|
+
opts.separator " decrypt [file] Decrypt to stdout (default: #{ENCRYPTED_FILE})"
|
21
|
+
opts.separator " edit [file] Edit encrypted file (default: #{ENCRYPTED_FILE})"
|
22
|
+
|
23
|
+
opts.separator ""
|
24
|
+
opts.separator "Options:"
|
25
|
+
|
26
|
+
opts.on("-k", "--key KEY", "Encryption key") do |key|
|
27
|
+
options[:key] = key
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-h", "--help", "Show this help message") do
|
31
|
+
puts opts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-v", "--version", "Show version") do
|
36
|
+
puts Dotenvcrypt::VERSION
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Parse global options first
|
42
|
+
opt_parser.order!(ARGV)
|
43
|
+
|
44
|
+
# Get the command
|
45
|
+
command = ARGV.shift
|
46
|
+
|
47
|
+
if command.nil?
|
48
|
+
puts opt_parser
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
dotenvcrypt = Dotenvcrypt::Manager.new(options[:key])
|
53
|
+
|
54
|
+
case command
|
55
|
+
when 'encrypt'
|
56
|
+
input_file = ARGV[0] || UNENCRYPTED_FILE
|
57
|
+
output_file = ARGV[1] || ENCRYPTED_FILE
|
58
|
+
dotenvcrypt.encrypt_env(input_file, output_file)
|
59
|
+
when 'decrypt'
|
60
|
+
dotenvcrypt.decrypt_env(ARGV[0] || ENCRYPTED_FILE)
|
61
|
+
when 'edit'
|
62
|
+
dotenvcrypt.edit_env(ARGV[0] || ENCRYPTED_FILE)
|
63
|
+
else
|
64
|
+
puts "Unknown command: #{command}"
|
65
|
+
puts opt_parser
|
66
|
+
exit 1
|
67
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'base64'
|
5
|
+
require 'tty-prompt'
|
6
|
+
require 'tty-command'
|
7
|
+
require 'tempfile'
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
module Dotenvcrypt
|
11
|
+
class Manager
|
12
|
+
# Determine config directory following XDG Base Directory Specification
|
13
|
+
CONFIG_HOME = ENV['XDG_CONFIG_HOME'] || "#{ENV['HOME']}/.config"
|
14
|
+
CONFIG_DIR = "#{CONFIG_HOME}/dotenvcrypt".freeze
|
15
|
+
|
16
|
+
# Check multiple locations in order of preference
|
17
|
+
ENCRYPTION_KEY_LOCATIONS = [
|
18
|
+
"#{Dir.pwd}/.dotenvcrypt.key", # Project-specific key (current directory)
|
19
|
+
"#{CONFIG_DIR}/secret.key", # XDG standard location
|
20
|
+
"#{ENV['HOME']}/.dotenvcrypt.key" # Legacy location (for backward compatibility)
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def initialize(encryption_key = nil)
|
24
|
+
@encryption_key = encryption_key
|
25
|
+
end
|
26
|
+
|
27
|
+
# Encrypt an existing `.env` file
|
28
|
+
def encrypt_env(unencrypted_file, encrypted_file)
|
29
|
+
unless File.exist?(unencrypted_file)
|
30
|
+
puts "❌ File not found: #{unencrypted_file}"
|
31
|
+
exit(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
encrypt(unencrypted_file, encrypted_file)
|
35
|
+
|
36
|
+
puts "🔒 Encrypted #{unencrypted_file} → #{encrypted_file}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Decrypt an encrypted `.env` file and print to stdout
|
40
|
+
def decrypt_env(encrypted_file)
|
41
|
+
puts decrypt(encrypted_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Edit decrypted env, then re-encrypt
|
45
|
+
def edit_env(encrypted_file)
|
46
|
+
temp_file = Tempfile.new('dotenvcrypt')
|
47
|
+
|
48
|
+
File.open(temp_file.path, 'w') do |f|
|
49
|
+
f.write decrypt(encrypted_file)
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "Waiting for file to be saved. Abort with Ctrl-C."
|
53
|
+
|
54
|
+
system("#{ENV['EDITOR'] || 'vim'} #{temp_file.path}")
|
55
|
+
|
56
|
+
encrypt(temp_file.path, encrypted_file)
|
57
|
+
|
58
|
+
puts "🔒 Encrypted and saved #{encrypted_file}"
|
59
|
+
ensure
|
60
|
+
temp_file.unlink if temp_file
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :encryption_key
|
66
|
+
|
67
|
+
# Encrypt file
|
68
|
+
def encrypt(input_file, output_file)
|
69
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
70
|
+
cipher.encrypt
|
71
|
+
key = OpenSSL::Digest::SHA256.digest(fetch_key)
|
72
|
+
cipher.key = key
|
73
|
+
|
74
|
+
# Generate a secure random IV (12 bytes is recommended for GCM)
|
75
|
+
iv = cipher.random_iv
|
76
|
+
|
77
|
+
data = File.read(input_file)
|
78
|
+
encrypted = cipher.update(data) + cipher.final
|
79
|
+
|
80
|
+
# Get the authentication tag (16 bytes)
|
81
|
+
auth_tag = cipher.auth_tag
|
82
|
+
|
83
|
+
# Combine IV, encrypted data, and auth tag
|
84
|
+
combined = iv + auth_tag + encrypted
|
85
|
+
|
86
|
+
# Convert to base64 for readability
|
87
|
+
base64_data = Base64.strict_encode64(combined)
|
88
|
+
|
89
|
+
File.open(output_file, 'w') do |f|
|
90
|
+
f.write(base64_data)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Decrypt file
|
95
|
+
def decrypt(input_file)
|
96
|
+
# Read the encrypted file
|
97
|
+
begin
|
98
|
+
base64_data = File.read(input_file)
|
99
|
+
data = Base64.strict_decode64(base64_data)
|
100
|
+
rescue ArgumentError
|
101
|
+
puts "❌ Decryption failed. File is not in valid base64 format."
|
102
|
+
exit(1)
|
103
|
+
end
|
104
|
+
|
105
|
+
# For GCM mode:
|
106
|
+
# - IV is 12 bytes
|
107
|
+
# - Auth tag is 16 bytes
|
108
|
+
iv_size = 12
|
109
|
+
auth_tag_size = 16
|
110
|
+
|
111
|
+
# Extract the components
|
112
|
+
iv = data[0...iv_size]
|
113
|
+
auth_tag = data[iv_size...(iv_size + auth_tag_size)]
|
114
|
+
encrypted_data = data[(iv_size + auth_tag_size)..-1]
|
115
|
+
|
116
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
117
|
+
cipher.decrypt
|
118
|
+
cipher.key = OpenSSL::Digest::SHA256.digest(fetch_key)
|
119
|
+
cipher.iv = iv
|
120
|
+
cipher.auth_tag = auth_tag
|
121
|
+
|
122
|
+
cipher.update(encrypted_data) + cipher.final
|
123
|
+
rescue OpenSSL::Cipher::CipherError
|
124
|
+
puts "❌ Decryption failed. Invalid key, corrupted file, or tampering detected."
|
125
|
+
exit(1)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get encryption key: From CLI arg OR encryption-key file OR securely prompt from stdin
|
129
|
+
def fetch_key
|
130
|
+
return encryption_key if encryption_key && !encryption_key.empty?
|
131
|
+
return ENV['DOTENVCRYPT_KEY'] if ENV['DOTENVCRYPT_KEY'] && !ENV['DOTENVCRYPT_KEY'].empty?
|
132
|
+
ENCRYPTION_KEY_LOCATIONS.each do |key_file|
|
133
|
+
return File.read(key_file).strip if File.exist?(key_file)
|
134
|
+
end
|
135
|
+
|
136
|
+
prompt.ask("Enter encryption key:", echo: false)
|
137
|
+
end
|
138
|
+
|
139
|
+
def prompt
|
140
|
+
@prompt ||= TTY::Prompt.new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
data/lib/dotenvcrypt.rb
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dotenvcrypt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Loman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tty-prompt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.23.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.23.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tty-command
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.10.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.10.1
|
41
|
+
description: dotenvcrypt ensures any API keys in your .env files are encrypted - enabling
|
42
|
+
storage of these files directly within Git.
|
43
|
+
email:
|
44
|
+
- daniel.loman@gmail.com
|
45
|
+
executables:
|
46
|
+
- dotenvcrypt
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- LICENSE
|
51
|
+
- README.md
|
52
|
+
- bin/dotenvcrypt
|
53
|
+
- lib/dotenvcrypt.rb
|
54
|
+
- lib/dotenvcrypt/manager.rb
|
55
|
+
- lib/dotenvcrypt/version.rb
|
56
|
+
homepage: https://github.com/namolnad/dotenvcrypt
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata:
|
60
|
+
homepage_uri: https://github.com/namolnad/dotenvcrypt
|
61
|
+
source_code_uri: https://github.com/namolnad/dotenvcrypt
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 2.5.0
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubygems_version: 3.5.22
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Securely encrypt, manage, and store your .env files in Git
|
81
|
+
test_files: []
|