dotenvcrypt 0.4.0 → 0.5.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 +4 -4
- data/bin/dotenvcrypt +3 -3
- data/lib/dotenvcrypt/encryptor.rb +65 -0
- data/lib/dotenvcrypt/manager.rb +17 -77
- data/lib/dotenvcrypt/version.rb +1 -1
- data/lib/dotenvcrypt.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25e9da553397ec83d27cdbb1319b602a21943a829a35a983c3b2a47ac236b950
|
4
|
+
data.tar.gz: 9b2366d939e79d8d0ddb7113a62b0d47150e8fc252f5daa66cac54289e4670a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a62e65dd35df66335424686223269606b04039c52c5c9248ed325c7f86c92d0c73fe14d4f6adad14e8b7f7478a3e378e7439f7ac688a6bad79cd681698a3cfad
|
7
|
+
data.tar.gz: 706ca24d30f12ce314e18da8bb9f9a63fbbf089dab8f3082474cd9a34719157a4f57c78227b16f696d8acb8afb6a10e695d89781ad9e4d07da005b8958b2632b
|
data/bin/dotenvcrypt
CHANGED
@@ -55,11 +55,11 @@ case command
|
|
55
55
|
when 'encrypt'
|
56
56
|
input_file = ARGV[0] || UNENCRYPTED_FILE
|
57
57
|
output_file = ARGV[1] || ENCRYPTED_FILE
|
58
|
-
dotenvcrypt.
|
58
|
+
dotenvcrypt.encrypt_file(input_file, output_file)
|
59
59
|
when 'decrypt'
|
60
|
-
dotenvcrypt.
|
60
|
+
dotenvcrypt.decrypt_file(ARGV[0] || ENCRYPTED_FILE)
|
61
61
|
when 'edit'
|
62
|
-
dotenvcrypt.
|
62
|
+
dotenvcrypt.edit_file(ARGV[0] || ENCRYPTED_FILE)
|
63
63
|
else
|
64
64
|
puts "Unknown command: #{command}"
|
65
65
|
puts opt_parser
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Dotenvcrypt
|
2
|
+
class Encryptor
|
3
|
+
def initialize(encryption_key)
|
4
|
+
@encryption_key = encryption_key
|
5
|
+
end
|
6
|
+
|
7
|
+
# Encrypt file
|
8
|
+
def encrypt(data)
|
9
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
10
|
+
cipher.encrypt
|
11
|
+
key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
12
|
+
cipher.key = key
|
13
|
+
|
14
|
+
# Generate a secure random IV (12 bytes is recommended for GCM)
|
15
|
+
iv = cipher.random_iv
|
16
|
+
|
17
|
+
encrypted = cipher.update(data) + cipher.final
|
18
|
+
|
19
|
+
# Get the authentication tag (16 bytes)
|
20
|
+
auth_tag = cipher.auth_tag
|
21
|
+
|
22
|
+
# Combine IV, encrypted data, and auth tag
|
23
|
+
combined = iv + auth_tag + encrypted
|
24
|
+
|
25
|
+
# Convert to base64 for readability
|
26
|
+
Base64.strict_encode64(combined)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Decrypt file
|
30
|
+
def decrypt(encoded_data)
|
31
|
+
begin
|
32
|
+
data = Base64.strict_decode64(encoded_data)
|
33
|
+
rescue ArgumentError
|
34
|
+
puts "❌ Decryption failed. File is not in valid base64 format."
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
# For GCM mode:
|
39
|
+
# - IV is 12 bytes
|
40
|
+
# - Auth tag is 16 bytes
|
41
|
+
iv_size = 12
|
42
|
+
auth_tag_size = 16
|
43
|
+
|
44
|
+
# Extract the components
|
45
|
+
iv = data[0...iv_size]
|
46
|
+
auth_tag = data[iv_size...(iv_size + auth_tag_size)]
|
47
|
+
encrypted_data = data[(iv_size + auth_tag_size)..-1]
|
48
|
+
|
49
|
+
cipher = OpenSSL::Cipher::AES256.new(:GCM)
|
50
|
+
cipher.decrypt
|
51
|
+
cipher.key = OpenSSL::Digest::SHA256.digest(encryption_key)
|
52
|
+
cipher.iv = iv
|
53
|
+
cipher.auth_tag = auth_tag
|
54
|
+
|
55
|
+
cipher.update(encrypted_data) + cipher.final
|
56
|
+
rescue OpenSSL::Cipher::CipherError
|
57
|
+
puts "❌ Decryption failed. Invalid key, corrupted file, or tampering detected."
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :encryption_key
|
64
|
+
end
|
65
|
+
end
|
data/lib/dotenvcrypt/manager.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'openssl'
|
4
3
|
require 'base64'
|
4
|
+
require 'openssl'
|
5
|
+
require 'tempfile'
|
5
6
|
require 'tty-prompt'
|
6
7
|
require 'tty-command'
|
7
|
-
|
8
|
-
require 'optparse'
|
8
|
+
require_relative 'encryptor'
|
9
9
|
|
10
10
|
module Dotenvcrypt
|
11
11
|
class Manager
|
@@ -22,38 +22,43 @@ module Dotenvcrypt
|
|
22
22
|
|
23
23
|
def initialize(encryption_key = nil)
|
24
24
|
@encryption_key = encryption_key
|
25
|
+
@encryptor = Encryptor.new(fetch_key)
|
25
26
|
end
|
26
27
|
|
27
28
|
# Encrypt an existing `.env` file
|
28
|
-
def
|
29
|
+
def encrypt_file(unencrypted_file, encrypted_file)
|
29
30
|
unless File.exist?(unencrypted_file)
|
30
31
|
puts "❌ File not found: #{unencrypted_file}"
|
31
32
|
exit(1)
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
+
File.open(encrypted_file, 'w') do |f|
|
36
|
+
f.write encryptor.encrypt(File.read(unencrypted_file))
|
37
|
+
end
|
35
38
|
|
36
39
|
puts "🔒 Encrypted #{unencrypted_file} → #{encrypted_file}"
|
37
40
|
end
|
38
41
|
|
39
42
|
# Decrypt an encrypted `.env` file and print to stdout
|
40
|
-
def
|
41
|
-
puts decrypt(encrypted_file)
|
43
|
+
def decrypt_file(encrypted_file)
|
44
|
+
puts encryptor.decrypt(File.read(encrypted_file))
|
42
45
|
end
|
43
46
|
|
44
47
|
# Edit decrypted env, then re-encrypt
|
45
|
-
def
|
48
|
+
def edit_file(encrypted_file)
|
46
49
|
temp_file = Tempfile.new('dotenvcrypt')
|
47
50
|
|
48
51
|
File.open(temp_file.path, 'w') do |f|
|
49
|
-
f.write decrypt(encrypted_file)
|
52
|
+
f.write encryptor.decrypt(File.read(encrypted_file))
|
50
53
|
end
|
51
54
|
|
52
55
|
puts "Waiting for file to be saved. Abort with Ctrl-C."
|
53
56
|
|
54
57
|
system("#{ENV['EDITOR'] || 'vim'} #{temp_file.path}")
|
55
58
|
|
56
|
-
|
59
|
+
File.open(encrypted_file, 'w') do |f|
|
60
|
+
f.write encryptor.encrypt(File.read(temp_file.path))
|
61
|
+
end
|
57
62
|
|
58
63
|
puts "🔒 Encrypted and saved #{encrypted_file}"
|
59
64
|
ensure
|
@@ -62,68 +67,7 @@ module Dotenvcrypt
|
|
62
67
|
|
63
68
|
private
|
64
69
|
|
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
|
70
|
+
attr_reader :encryption_key, :encryptor
|
127
71
|
|
128
72
|
# Get encryption key: From CLI arg OR encryption-key file OR securely prompt from stdin
|
129
73
|
def fetch_key
|
@@ -133,11 +77,7 @@ module Dotenvcrypt
|
|
133
77
|
return File.read(key_file).strip if File.exist?(key_file)
|
134
78
|
end
|
135
79
|
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
def prompt
|
140
|
-
@prompt ||= TTY::Prompt.new
|
80
|
+
TTY::Prompt.new.ask("Enter encryption key:", echo: false)
|
141
81
|
end
|
142
82
|
end
|
143
83
|
end
|
data/lib/dotenvcrypt/version.rb
CHANGED
data/lib/dotenvcrypt.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dotenvcrypt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Loman
|
@@ -38,8 +38,8 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.10.1
|
41
|
-
description: dotenvcrypt ensures
|
42
|
-
storage of these files directly within Git.
|
41
|
+
description: dotenvcrypt ensures your .env files - and, by extension, any secrets
|
42
|
+
within them - are encrypted, enabling storage of these files directly within Git.
|
43
43
|
email:
|
44
44
|
- daniel.loman@gmail.com
|
45
45
|
executables:
|
@@ -51,6 +51,7 @@ files:
|
|
51
51
|
- README.md
|
52
52
|
- bin/dotenvcrypt
|
53
53
|
- lib/dotenvcrypt.rb
|
54
|
+
- lib/dotenvcrypt/encryptor.rb
|
54
55
|
- lib/dotenvcrypt/manager.rb
|
55
56
|
- lib/dotenvcrypt/version.rb
|
56
57
|
homepage: https://github.com/namolnad/dotenvcrypt
|