dotenvcrypt 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 281a89c7380b61972623d6d94ab5e10911b07d49abd05653974b60c83c0a243c
4
- data.tar.gz: cd25bc6834e0aa6bd8927a3ca5e20fd00712967abd446c537109de7bc6a5e9ff
3
+ metadata.gz: 1ead91de7add08540af64e47ff6f32f8c0a3e0f552fdd6b5a04a0485dc618909
4
+ data.tar.gz: fef0232606440a15f36b5f9e3ab39588df74780cd110697e53c4cfc1a5ca1f48
5
5
  SHA512:
6
- metadata.gz: 3ab9cb7e645289a41a17e2f534d58ad59cd891f95d5665613d25329a11815bc154ba74e6ab8eba43f7647561f818ba177b249d42fe6078a5d7be9b8e84a2a35a
7
- data.tar.gz: 6bbe096aca3355525202c64e9fdd79a6942441f7e7f7ab8457d48a81a389696e88f21c4fa9b6708ca038feca44538bce8a32209e8f3ee73fa882473a6f96591a
6
+ metadata.gz: 64aa077f96823950f7c354f9a08f61c671e042d2fefd17df84c5776f53cbc21c590b6716c22d3d696baa2cf67804142b6a44cef5fc7acde87c5fc182f4ffbbea
7
+ data.tar.gz: 2600fa1f56ae93551ad6649f7dca32cebb2dbfd3bec63c5501a5c836162110d4aa2eff7efaccde382a0044c312ac0dce44cb890dc54c9e0b1bde9eea9b02249d
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.encrypt_env(input_file, output_file)
58
+ dotenvcrypt.encrypt_file(input_file, output_file)
59
59
  when 'decrypt'
60
- dotenvcrypt.decrypt_env(ARGV[0] || ENCRYPTED_FILE)
60
+ dotenvcrypt.decrypt_file(ARGV[0] || ENCRYPTED_FILE)
61
61
  when 'edit'
62
- dotenvcrypt.edit_env(ARGV[0] || ENCRYPTED_FILE)
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
@@ -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
- require 'tempfile'
8
- require 'optparse'
8
+ require_relative 'encryptor'
9
9
 
10
10
  module Dotenvcrypt
11
11
  class Manager
@@ -22,38 +22,49 @@ 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 encrypt_env(unencrypted_file, encrypted_file)
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
- encrypt(unencrypted_file, encrypted_file)
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 decrypt_env(encrypted_file)
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 edit_env(encrypted_file)
48
+ def edit_file(encrypted_file)
46
49
  temp_file = Tempfile.new('dotenvcrypt')
47
50
 
51
+ original_content = encryptor.decrypt(File.read(encrypted_file))
52
+
48
53
  File.open(temp_file.path, 'w') do |f|
49
- f.write decrypt(encrypted_file)
54
+ f.write original_content
50
55
  end
51
56
 
52
57
  puts "Waiting for file to be saved. Abort with Ctrl-C."
53
58
 
54
59
  system("#{ENV['EDITOR'] || 'vim'} #{temp_file.path}")
55
60
 
56
- encrypt(temp_file.path, encrypted_file)
61
+ updated_content = File.read(temp_file.path)
62
+
63
+ if updated_content != original_content
64
+ File.open(encrypted_file, 'w') do |f|
65
+ f.write encryptor.encrypt(updated_content)
66
+ end
67
+ end
57
68
 
58
69
  puts "🔒 Encrypted and saved #{encrypted_file}"
59
70
  ensure
@@ -62,68 +73,7 @@ module Dotenvcrypt
62
73
 
63
74
  private
64
75
 
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
76
+ attr_reader :encryption_key, :encryptor
127
77
 
128
78
  # Get encryption key: From CLI arg OR encryption-key file OR securely prompt from stdin
129
79
  def fetch_key
@@ -133,11 +83,7 @@ module Dotenvcrypt
133
83
  return File.read(key_file).strip if File.exist?(key_file)
134
84
  end
135
85
 
136
- prompt.ask("Enter encryption key:", echo: false)
137
- end
138
-
139
- def prompt
140
- @prompt ||= TTY::Prompt.new
86
+ TTY::Prompt.new.ask("Enter encryption key:", echo: false)
141
87
  end
142
88
  end
143
89
  end
@@ -1,3 +1,3 @@
1
1
  module Dotenvcrypt
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/dotenvcrypt.rb CHANGED
@@ -1,2 +1,3 @@
1
+ require_relative "dotenvcrypt/encryptor"
1
2
  require_relative "dotenvcrypt/manager"
2
3
  require_relative "dotenvcrypt/version"
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.0
4
+ version: 0.6.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 any API keys in your .env files are encrypted - enabling
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