encrypth 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9079e1bb667c0dc79fda0fd7e3997ab5529dd33cc62f6725ced36913d236868
4
+ data.tar.gz: 831220a4dbffbb5deac197e876f58ad77b6c4ce80dac9caeb501013b21ff86ff
5
+ SHA512:
6
+ metadata.gz: 71f8b94819558abbc1fb261a5a77829e50fb4069f2514dd598200db81afd76667be3d11de6110be71c711d35ac15d8ccc29203d7d017824a535f34244313c728
7
+ data.tar.gz: b5bfa2ca6e5d919b52596a2cf4db15a8a9a76bdc1b0e5030dcefc81d6f0857f8de500d92eac701990656df232c1ef17b1aa70bda07800190f36ea629986f2661
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Encrypth
2
+
3
+ `Encrypth` — это простой Ruby gem для безопасного архивирования и шифрования файлов с помощью AES-256-GCM. Gem был создан в качестве учебного проекта, и в дальнейшем можно ожидать расширение функционала.
4
+
5
+ ## Что делает
6
+
7
+ - создает зашифрованный tar-архив из файлов и директорий
8
+ - использует надежный режим шифрования AES-256-GCM
9
+ - генерирует ключ по паролю через PBKDF2-HMAC-SHA256
10
+ - сохраняет соль отдельно в файле `archive.salt`
11
+ - извлекает и расшифровывает содержимое обратно в указанную директорию
12
+
13
+ ## Установка
14
+
15
+ ```sh
16
+ bundle install
17
+ ```
18
+
19
+ ## Использование CLI
20
+
21
+ Синтаксис:
22
+
23
+ ```sh
24
+ ruby lib/encrypth.rb <archive> <-e|-d> <...files|destination>
25
+ ```
26
+
27
+ ### Шифрование
28
+
29
+ ```sh
30
+ ruby lib/encrypth.rb secret.enc -e file1.txt folder2 file3.jpg
31
+ ```
32
+
33
+ - `secret.enc` — имя выходного зашифрованного архива
34
+ - `-e` — флаг шифрования
35
+ - перечислите файлы и/или директории для архивации
36
+ - пароль вводится скрыто
37
+ - создается файл соли `secret.enc.salt`
38
+
39
+ ### Дешифрование
40
+
41
+ ```sh
42
+ ruby lib/encrypth.rb secret.enc -d output_folder
43
+ ```
44
+
45
+ - `secret.enc` — существующий зашифрованный архив
46
+ - `-d` — флаг дешифрования
47
+ - `output_folder` — папка для извлечения файлов
48
+ - если папка не указана, используется текущая директория
49
+
50
+ ## API
51
+
52
+ `Encrypth` предоставляет базовый API для шифрования и дешифрования данных:
53
+
54
+ - `Encrypth.generate_key` — создает случайный 32-байтовый ключ
55
+ - `Encrypth.from_password(password, salt = nil)` — создает ключ из пароля и соли
56
+ - `Encrypth.new(key).encrypt(data)` — шифрует строку
57
+ - `Encrypth.new(key).decrypt(encrypted_string)` — расшифровывает строку
58
+
59
+ ## Структура проекта
60
+
61
+ - `lib/encrypth.rb` — точка входа
62
+ - `lib/encrypth/cli.rb` — CLI-интерфейс
63
+ - `lib/encrypth/cipher.rb` — шифрование AES-256-GCM и PBKDF2
64
+ - `lib/encrypth/archiver.rb` — создание и извлечение tar-архива
65
+ - `demo/encrypthdemo.rb` — демонстрация базового шифрования
66
+ - `test/encrypth_test.rb` — набор тестов на Minitest
67
+
68
+ ## Лицензия
69
+
70
+ Проект распространяется под лицензией MIT.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "rake/testtask"
2
+
3
+ Rake::TestTask.new(:test) do |t|
4
+ t.libs << "lib"
5
+ t.test_files = FileList["test/**/*_test.rb"]
6
+ end
7
+
8
+ task default: :test
@@ -0,0 +1,77 @@
1
+ require "fileutils"
2
+ require "stringio"
3
+ require "find"
4
+ require "rubygems/package"
5
+
6
+ module Encrypth
7
+ class Archiver
8
+ def initialize(cipher)
9
+ @cipher = cipher
10
+ end
11
+
12
+ # Шифрует содержимое файлов и сохраняет его в виде зашифрованного архива
13
+ def encrypt_files(files, archive_path)
14
+ tar_content = create_tar_archive(files, archive_path)
15
+ encrypted = @cipher.encrypt(tar_content)
16
+ File.binwrite(archive_path, encrypted)
17
+ end
18
+
19
+ # Дешифрует архив и извлекает файлы в указанную директорию
20
+ def decrypt_to_directory(archive_path, destination)
21
+ encrypted = File.binread(archive_path)
22
+ tar_content = @cipher.decrypt(encrypted)
23
+ extract_tar_archive(tar_content, destination)
24
+ end
25
+
26
+ private
27
+
28
+ # Создает tar-архив из списка файлов и возвращает его содержимое в виде строки
29
+ def create_tar_archive(files, archive_path)
30
+ io = StringIO.new.tap { |s| s.set_encoding("binary") }
31
+ Gem::Package::TarWriter.new(io) do |tar|
32
+ files.each do |file|
33
+ if File.directory?(file)
34
+ add_directory_to_archive(tar, file)
35
+ else
36
+ add_file_to_archive(tar, file)
37
+ end
38
+ end
39
+ end
40
+ io.string
41
+ end
42
+
43
+ def add_directory_to_archive(archive, directory)
44
+ Find.find(directory) do |path|
45
+ next if File.directory?(path)
46
+ tar_path = path.sub("#{directory}/", "")
47
+ archive.add_file(tar_path, File.stat(path).mode) do |f|
48
+ f.write(File.binread(path))
49
+ end
50
+ end
51
+ end
52
+
53
+ def add_file_to_archive(archive, file)
54
+ tar_path = File.basename(file)
55
+ archive.add_file(tar_path, File.stat(file).mode) do |f|
56
+ f.write(File.binread(file))
57
+ end
58
+ end
59
+
60
+ # Извлекает файлы из tar-архива, который передается в виде строки
61
+ def extract_tar_archive(archive_content, destination)
62
+ io = StringIO.new(archive_content).tap { |s| s.set_encoding("binary") }
63
+ Gem::Package::TarReader.new(io) do |tar|
64
+ tar.each do |entry|
65
+ path = File.join(destination, entry.full_name)
66
+ if entry.directory?
67
+ FileUtils.mkdir_p(path)
68
+ else
69
+ FileUtils.mkdir_p(File.dirname(path))
70
+ File.write(path, entry.read)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,75 @@
1
+ require "openssl"
2
+ require "base64"
3
+ require "securerandom"
4
+
5
+ module Encrypth
6
+ class Cipher
7
+ def initialize(key)
8
+ @key = key
9
+ validate_key!
10
+ end
11
+
12
+ # Шифрует данные и возвращает строку для хранения
13
+ def encrypt(data)
14
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
15
+ cipher.encrypt
16
+ cipher.key = @key
17
+
18
+ iv = cipher.random_iv
19
+ encrypted = cipher.update(data) + cipher.final
20
+ auth_tag = cipher.auth_tag
21
+
22
+ package(iv, encrypted, auth_tag)
23
+ end
24
+
25
+ # Дешифрует данные из строки, созданной методом encrypt
26
+ def decrypt(string)
27
+ iv, encrypted, auth_tag = unpack(string)
28
+
29
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
30
+ cipher.decrypt
31
+ cipher.key = @key
32
+ cipher.iv = iv
33
+ cipher.auth_tag = auth_tag
34
+
35
+ cipher.update(encrypted) + cipher.final
36
+ end
37
+
38
+ # Генерирует случайный ключ (32 байта)
39
+ def self.generate_key
40
+ SecureRandom.random_bytes(32)
41
+ end
42
+
43
+ # Создает ключ из пароля (с солью)
44
+ def self.from_password(password, salt = nil)
45
+ salt ||= SecureRandom.random_bytes(16)
46
+ key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, 20000, 32, "SHA256")
47
+ { key: key, salt: salt }
48
+ end
49
+
50
+ private
51
+
52
+ def validate_key!
53
+ if @key.bytesize != 32
54
+ raise Error, "Ключ должен быть 32 байта (получено #{@key.bytesize})"
55
+ end
56
+ end
57
+
58
+ def package(iv, encrypted, auth_tag)
59
+ [
60
+ Base64.strict_encode64(iv),
61
+ Base64.strict_encode64(encrypted),
62
+ Base64.strict_encode64(auth_tag)
63
+ ].join("--")
64
+ end
65
+
66
+ def unpack(string)
67
+ parts = string.split("--", 3)
68
+ [
69
+ Base64.decode64(parts[0]),
70
+ Base64.decode64(parts[1]),
71
+ Base64.decode64(parts[2])
72
+ ]
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,85 @@
1
+ require "io/console"
2
+ require "base64"
3
+
4
+ module Encrypth
5
+ class CLI
6
+ def self.run(args)
7
+ if args.length < 1
8
+ puts "Использование: encrypth.rb <archive> <flag (-e/-d)> <...files/destination?>"
9
+ exit(1)
10
+ end
11
+
12
+ archive = args[0]
13
+ flag = args[1]
14
+
15
+ if flag == "-e"
16
+ if File.exist?(archive)
17
+ puts "Архив #{archive} уже существует. Пожалуйста, выберите другое имя или удалите существующий архив."
18
+ exit(1)
19
+ end
20
+ files = args[2..-1]
21
+ encrypt_files(files, archive)
22
+ elsif flag == "-d"
23
+ if !File.exist?(archive)
24
+ puts "Архив #{archive} не существует."
25
+ exit(1)
26
+ end
27
+ destination = args[2]
28
+ if destination && File.exist?(destination) && !File.directory?(destination)
29
+ puts "Указанный путь #{destination} не является директорией."
30
+ exit(1)
31
+ end
32
+ destination = "." if destination.nil? || destination.empty?
33
+ decrypt_to_directory(archive, destination)
34
+ else
35
+ puts "Неверный флаг. Используйте -e для шифрования или -d для дешифрования."
36
+ exit(1)
37
+ end
38
+ end
39
+
40
+ def self.encrypt_files(files, archive)
41
+ password = read_password("Задайте пароль: ")
42
+ cipher_data = Encrypth::Cipher.from_password(password)
43
+ save_salt(archive, cipher_data[:salt])
44
+ cipher = Encrypth::Cipher.new(cipher_data[:key])
45
+ archiver = Encrypth::Archiver.new(cipher)
46
+ archiver.encrypt_files(files, archive)
47
+ end
48
+
49
+ def self.decrypt_to_directory(archive, destination)
50
+ password = read_password("Введите пароль: ")
51
+ salt = load_salt(archive)
52
+ cipher_data = Encrypth::Cipher.from_password(password, salt)
53
+ cipher = Encrypth::Cipher.new(cipher_data[:key])
54
+ archiver = Encrypth::Archiver.new(cipher)
55
+ archiver.decrypt_to_directory(archive, destination)
56
+ end
57
+
58
+ private
59
+
60
+ def self.salt_path(archive)
61
+ "#{archive}.salt"
62
+ end
63
+
64
+ def self.save_salt(archive, salt)
65
+ File.binwrite(salt_path(archive), Base64.strict_encode64(salt))
66
+ end
67
+
68
+ def self.load_salt(archive)
69
+ salt_file = salt_path(archive)
70
+ unless File.exist?(salt_file)
71
+ raise "Файл соли не найден. Невозможно восстановить ключ для архива #{archive}."
72
+ end
73
+
74
+ Base64.decode64(File.read(salt_file))
75
+ end
76
+
77
+ def self.read_password(prompt)
78
+ print prompt
79
+ $stdout.flush
80
+ password = STDIN.noecho { |io| io.gets }&.chomp || ""
81
+ puts
82
+ password
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Encrypth
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,111 @@
1
+ module Encrypth
2
+ class WebArchiver
3
+ SALT_LEN = 32
4
+ IV_LEN = 12
5
+ TAG_LEN = 16
6
+
7
+ def initialize(password)
8
+ @password = password
9
+ end
10
+
11
+ def encrypt(files)
12
+ out_path = File.join(Dir.tmpdir, "enc_#{SecureRandom.hex(8)}.tar.enc")
13
+ tar_path = File.join(Dir.tmpdir, "tar_#{SecureRandom.hex(8)}.tar")
14
+
15
+ salt = OpenSSL::Random.random_bytes(SALT_LEN)
16
+ key = derive_key(@password, salt)
17
+
18
+ begin
19
+ create_tar(files, tar_path)
20
+
21
+ cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
22
+ cipher.key = key
23
+ iv = cipher.random_iv
24
+ cipher.iv_len = IV_LEN
25
+ cipher.iv = iv
26
+
27
+ File.open(out_path, 'wb') do |out_f|
28
+ out_f.write(salt)
29
+ out_f.write(iv)
30
+
31
+ File.open(tar_path, 'rb') do |tar_f|
32
+ while chunk = tar_f.read(16384)
33
+ out_f.write(cipher.update(chunk))
34
+ end
35
+ end
36
+ out_f.write(cipher.final)
37
+ out_f.write(cipher.auth_tag)
38
+ end
39
+
40
+ { path: out_path, size: File.size(out_path) }
41
+ ensure
42
+ File.delete(tar_path) if File.exist?(tar_path)
43
+ end
44
+ end
45
+
46
+ def decrypt(archive_path, destination)
47
+ File.open(archive_path, 'rb') do |file|
48
+ salt = file.read(SALT_LEN)
49
+ iv = file.read(IV_LEN)
50
+
51
+ ciphertext_len = File.size(archive_path) - SALT_LEN - IV_LEN - TAG_LEN
52
+ key = derive_key(@password, salt)
53
+
54
+ cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
55
+ cipher.key = key
56
+ cipher.iv = iv
57
+
58
+ ciphertext = file.read(ciphertext_len)
59
+ auth_tag = file.read(TAG_LEN)
60
+ cipher.auth_tag = auth_tag
61
+
62
+ decrypted_data = cipher.update(ciphertext) + cipher.final
63
+
64
+ tar_path = File.join(Dir.tmpdir, "dec_#{SecureRandom.hex(8)}.tar")
65
+ File.binwrite(tar_path, decrypted_data)
66
+
67
+ begin
68
+ extract_tar(tar_path, destination)
69
+ ensure
70
+ File.delete(tar_path) if File.exist?(tar_path)
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def derive_key(password, salt)
78
+ OpenSSL::PKCS5.pbkdf2_hmac(password, salt, 100000, 32, 'sha256')
79
+ end
80
+
81
+ def create_tar(files, output_path)
82
+ File.open(output_path, 'wb') do |f|
83
+ Gem::Package::TarWriter.new(f) do |tar|
84
+ files.each do |file_path|
85
+ next unless File.exist?(file_path)
86
+ stat = File.stat(file_path)
87
+ tar.add_file_simple(File.basename(file_path), stat.mode, stat.size) do |io|
88
+ File.open(file_path, 'rb') { |src| io.write(src.read) }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ def extract_tar(tar_path, destination)
96
+ File.open(tar_path, 'rb') do |f|
97
+ Gem::Package::TarReader.new(f) do |reader|
98
+ reader.each do |entry|
99
+ target = File.join(destination, entry.full_name)
100
+ if entry.directory?
101
+ FileUtils.mkdir_p(target)
102
+ elsif entry.file?
103
+ FileUtils.mkdir_p(File.dirname(target))
104
+ File.binwrite(target, entry.read)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
data/lib/encrypth.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Encrypth
2
+ require_relative "encrypth/cipher"
3
+ require_relative "encrypth/archiver"
4
+ require_relative "encrypth/cli"
5
+
6
+ # раскомментируйте для консольного использования
7
+ # CLI.run(ARGV)
8
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: encrypth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Irina Grigorian
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ description: a gem with aes-256-gcm standard to make encrypting easy and safe.
41
+ email:
42
+ - irigorian@sfedu.ru
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.md
48
+ - Rakefile
49
+ - lib/encrypth.rb
50
+ - lib/encrypth/archiver.rb
51
+ - lib/encrypth/cipher.rb
52
+ - lib/encrypth/cli.rb
53
+ - lib/encrypth/version.rb
54
+ - lib/encrypth/web_archiver.rb
55
+ homepage: https://github.com/smokinblackdog/encrypth.git
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.0.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.6.9
74
+ specification_version: 4
75
+ summary: a simple wrapper for encryption.
76
+ test_files: []