ipcrypt 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ffcebf3891f542221e9130fc8399490e5ec66a865f0ee64dc4bad819a9d4cd9d
4
+ data.tar.gz: ba53bcec1d4002cf843190fce26eddfec44a11e2f6f92281b3435395399960f0
5
+ SHA512:
6
+ metadata.gz: 935359db9f48e1280a9a0c1d29c270a92329649e0f353d3e022dbff79a43a76bf60858d1023ce12b21eb662dbb0787d8cdf656b72def425d86c9353006d2c2cc
7
+ data.tar.gz: 5b39e89d7f0193de858f9a944a34190eac1167d6d877c7af45a3b51eba4c0018c573f0c5ed91fabed218d8e93a808281f3913102ab462cbbf6cdde1a9dc5bdb5
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Edwin Onuonga
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.
@@ -0,0 +1,129 @@
1
+ [![Ruby Version](https://img.shields.io/badge/ruby-~%3E%202.5-red.svg)](https://github.com/eonu/ipcrypt/blob/945450c32a145d7675b5d2b0c796708195d42388/ipcrypt.gemspec#L20)
2
+ [![Gem](https://img.shields.io/gem/v/ipcrypt.svg)](https://rubygems.org/gems/ipcrypt)
3
+ [![Build Status](https://travis-ci.org/eonu/ipcrypt.svg?branch=master)](https://travis-ci.org/eonu/ipcrypt)
4
+ [![License](https://img.shields.io/github/license/eonu/ipcrypt.svg)](https://github.com/eonu/ipcrypt/blob/master/LICENSE)
5
+
6
+ # IPCrypt
7
+
8
+ Ruby implementation of the format-preserving IPCrypt encryption algorithm for IPv4 addresses.
9
+
10
+ ---
11
+
12
+ IPCrypt is a format-preserving cipher for IPv4 addresses - that is, a cipher that accepts an IPv4 address for encryption, and generates a new decryptable IPv4 address.
13
+
14
+ The cipher was developed by Jean-Philippe Aumasson, initially in the form of a [python implementation](https://github.com/veorq/ipcrypt) which is the reference for this Ruby implementation.
15
+
16
+ ## Features
17
+
18
+ This gem provides:
19
+
20
+ - A CLI tool (`ipcrypt`) for the encryption/decryption of IPv4 addresses stored in CSV files
21
+ - A Ruby interface in the form of a module (`IPCrypt::IP`) for the encryption/decryption of IPv4 addresses of the class `String` within Ruby applications
22
+
23
+ ## Installation
24
+
25
+ Install the `ipcrypt` gem:
26
+
27
+ ```bash
28
+ $ gem install ipcrypt
29
+ ```
30
+
31
+ ### Installation for CLI usage
32
+
33
+ The CLI should be available to use after the gem has been installed:
34
+
35
+ ```bash
36
+ $ ipcrypt
37
+ Commands:
38
+ ipcrypt d [CSV] [COLUMN] # Decrypt IPv4 addresses from a CSV file
39
+ ipcrypt e [CSV] [COLUMN] # Encrypt IPv4 addresses from a CSV file
40
+
41
+ Options:
42
+ -k, --key=KEY # 16-byte key
43
+ ```
44
+
45
+ ### Installation for usage within Ruby applications
46
+
47
+ 1. Add the gem to your application's Gemfile:
48
+
49
+ ```ruby
50
+ gem 'ipcrypt'
51
+ ```
52
+
53
+ 2. Execute the following command:
54
+
55
+ ```bash
56
+ $ bundle install
57
+ ```
58
+
59
+ ## CLI Usage
60
+
61
+ ```bash
62
+ $ cat test.csv
63
+ id,firstname,lastname,ip_address,country
64
+ 1,a,b,127.0.0.1,c
65
+ 2,d,e,0.0.0.0,f
66
+ 3,g,h,255.255.255.255,i
67
+ 4,j,k,192.168.2.1,l
68
+
69
+ $ ipcrypt e test.csv ip_address -k '16-byte-key-123!' > encrypted.csv
70
+
71
+ $ cat encrypted.csv
72
+ id,firstname,lastname,ip_address,country
73
+ 1,a,b,94.99.154.180,c
74
+ 2,d,e,34.112.126.36,f
75
+ 3,g,h,6.156.93.249,i
76
+ 4,j,k,41.85.161.64,l
77
+
78
+ $ ipcrypt d encrypted.csv ip_address -k '16-byte-key-123!'
79
+ id,firstname,lastname,ip_address,country
80
+ 1,a,b,127.0.0.1,c
81
+ 2,d,e,0.0.0.0,f
82
+ 3,g,h,255.255.255.255,i
83
+ 4,j,k,192.168.2.1,l
84
+ ```
85
+
86
+ ## Usage within Ruby applications
87
+
88
+ The `IPCrypt::IP` is an interface for instantiating an `IPCrypt::Engine` - this class performs the task of encryption and decryption.
89
+
90
+ A random 16-byte key will be generated and stored as the `@default_key` instance variable - this can be retrieved with the `#default_key` attribute reader. This default key is used as the encryption key if none is specified as an argument for the `#encrypt` instance method.
91
+
92
+ ### Using a default key
93
+
94
+ ```ruby
95
+ encrypter = IPCrypt::IP['94.175.013.122', '73.155.92.01']
96
+ => #<IPCrypt::Engine:0x00007ff9f904fc68 @default_key="\x95\xC6\rM\xE5\xFAKE\xAD\x16\x80\x9A\xF6\xA9\v\x8B", @ips=["94.175.013.122", "73.155.92.01"]>
97
+
98
+ encrypted = encrypter.encrypt
99
+ => ["221.69.213.73", "80.1.170.94"]
100
+
101
+ decrypter = IPCrypt::IP[encrypted]
102
+ => #<IPCrypt::Engine:0x00007ff9f91618e0 @default_key="\xF8\x00\x8Cdj\xFF\xE9\x82\xE9\v\x85e\xF6\x7F 8", @ips=["221.69.213.73", "80.1.170.94"]>
103
+
104
+ decrypted = decrypter.decrypt encrypter.default_key
105
+ => ["94.175.13.122", "73.155.92.1"]
106
+ ```
107
+
108
+ ### Using a set key
109
+
110
+ ```ruby
111
+ key = '16-byte-key-123!'
112
+ => "16-byte-key-123"
113
+
114
+ encrypter = IPCrypt::IP['94.175.013.122', '73.155.92.01']
115
+ => #<IPCrypt::Engine:0x00007fe851049db8 @default_key="\xCC\xE0j\x13s\xB9B+\xEF'\xC8\xFC\xD4\xA5\xFCW", @ips=["94.175.013.122", "73.155.92.01"]>
116
+
117
+ encrypted = encrypter.encrypt key
118
+ => ["74.248.51.155", "132.12.16.129"]
119
+
120
+ decrypter = IPCrypt::IP[encrypted]
121
+ => #<IPCrypt::Engine:0x00007fe85117b1c8 @default_key="\xFA\x9C\x1E\xEE\xA7\xE3\xA1\xFD(9Q\x94\x11\xA4\x90\x19", @ips=["74.248.51.155", "132.12.16.129"]>
122
+
123
+ decrypted = decrypter.decrypt key
124
+ => ["94.175.13.122", "73.155.92.1"]
125
+ ```
126
+
127
+ ## Contributing
128
+
129
+ Bug reports and pull requests are welcome on GitHub at https://github.com/eonu/ipcrypt.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ task :default => [:spec]
4
+
5
+ desc "Run the specs"
6
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ipcrypt/cli'
3
+ IPCrypt::CLI.start
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'ipcrypt/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ipcrypt'
7
+ spec.version = IPCrypt::VERSION
8
+ spec.authors = ['Edwin Onuonga']
9
+ spec.email = %w[edwinonuonga@gmail.com]
10
+ spec.license = 'MIT'
11
+ spec.summary = %q{Ruby implementation of the format-preserving IPCrypt encryption algorithm for IPv4 addresses.}
12
+ spec.homepage = 'https://github.com/eonu/ipcrypt'
13
+ spec.metadata = {'source_code_uri' => 'https://github.com/eonu/ipcrypt/'}
14
+
15
+ spec.files = Dir.glob(File.join('lib', '**', '*'), File::FNM_DOTMATCH) + %w[Gemfile LICENSE README.md Rakefile ipcrypt.gemspec bin/ipcrypt]
16
+ spec.bindir = 'bin'
17
+ spec.executables = 'ipcrypt'
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.required_ruby_version = "~> 2.5"
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.16'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'rspec', '~> 3.8'
25
+ spec.add_development_dependency 'rspec-collection_matchers', '~> 1.1'
26
+ spec.add_development_dependency 'faker', '~> 1.9'
27
+ spec.add_runtime_dependency 'thor', '~> 0.20'
28
+ end
@@ -0,0 +1,3 @@
1
+ require 'ipcrypt/version'
2
+ require 'ipcrypt/ip'
3
+ require 'ipcrypt/engine'
@@ -0,0 +1,42 @@
1
+ require 'ipcrypt/engine'
2
+ require 'thor'
3
+ require 'csv'
4
+ module IPCrypt
5
+ class CLI < Thor
6
+ class_option :key, aliases: '-k', type: :string, :desc => '16-byte key', required: true
7
+
8
+ %w[encrypt decrypt].each do |crypt|
9
+ desc "#{crypt} [CSV] [COLUMN]", "#{crypt.capitalize} IPv4 addresses from a CSV file"
10
+ define_method crypt do |file, column|
11
+ csv = CSV.read(file, headers: true)
12
+ puts csv.headers * ?,
13
+
14
+ _crypted = Engine.new(*csv[column]).send(crypt, options[:key])
15
+
16
+ CSV.foreach(file, headers: true).with_index do |row, i|
17
+ puts csv.headers.map {|h| h == column ? _crypted[i] : row[h]} * ?,
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.help(shell, subcommand = false)
23
+ list = printable_commands(true, subcommand)
24
+ Thor::Util.thor_classes_in(self).each do |klass|
25
+ list += klass.printable_commands(false)
26
+ end
27
+ list.sort! { |a, b| a[0] <=> b[0] }
28
+ list.reject! { |e| /.*help.*/.match? e.first }
29
+ list.map! {|c| c.map {|s| s.gsub('decrypt', ?d).gsub('encrypt', ?e)} }
30
+
31
+ if defined?(@package_name) && @package_name
32
+ shell.say "#{@package_name} commands:"
33
+ else
34
+ shell.say "Commands:"
35
+ end
36
+
37
+ shell.print_table(list, :indent => 2, :truncate => true)
38
+ shell.say
39
+ class_options_help(shell)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,120 @@
1
+ require 'ipcrypt/exceptions'
2
+ require 'securerandom'
3
+ module IPCrypt
4
+ class Engine
5
+ attr_reader :default_key
6
+ IPv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
7
+
8
+ def initialize(*ips)
9
+ @default_key = SecureRandom.random_bytes(16)
10
+ @ips = ips.flatten.map do |ip|
11
+ raise InvalidIPv4Error.new(ip) unless IPv4.match? ip
12
+ ip
13
+ end
14
+ end
15
+
16
+ def encrypt(key = @default_key)
17
+ raise InvalidKeyTypeError.new(key, key.class) unless key.is_a? String
18
+
19
+ bytes = key.bytesize
20
+ raise InvalidKeyBytesError.new(key, bytes) unless bytes == 16
21
+
22
+ encrypted = @ips.map do |ip|
23
+ k = key.chars.map {|c| c.unpack('<C').first }
24
+ state = ip.split(?.).map &:to_i
25
+
26
+ state = xor4 state, k[0...4]
27
+ state = permute_fwd state
28
+ state = xor4 state, k[4...8]
29
+ state = permute_fwd state
30
+ state = xor4 state, k[8...12]
31
+ state = permute_fwd state
32
+ state = xor4 state, k[12...16]
33
+
34
+ state * ?.
35
+ end
36
+
37
+ encrypted.size == 1 ? encrypted.first : encrypted
38
+ end
39
+
40
+ def decrypt(key)
41
+ raise InvalidKeyTypeError.new(key, key.class) unless key.is_a? String
42
+
43
+ bytes = key.bytesize
44
+ raise InvalidKeyBytesError.new(key, bytes) unless bytes == 16
45
+
46
+ decrypted = @ips.map do |ip|
47
+ k = key.chars.map {|c| c.unpack('<C').first }
48
+ state = ip.split(?.).map &:to_i
49
+
50
+ state = xor4 state, k[12...16]
51
+ state = permute_bwd state
52
+ state = xor4 state, k[8...12]
53
+ state = permute_bwd state
54
+ state = xor4 state, k[4...8]
55
+ state = permute_bwd state
56
+ state = xor4 state, k[0...4]
57
+
58
+ state * ?.
59
+ end
60
+
61
+ decrypted.size == 1 ? decrypted.first : decrypted
62
+ end
63
+
64
+ private
65
+
66
+ def rotl(b, r)
67
+ ((b << r) & 0xff) | (b >> (8 - r))
68
+ end
69
+
70
+ def xor4(x, y)
71
+ (0..3).map {|i| (x[i] ^ y[i]) & 0xff }
72
+ end
73
+
74
+ def permute_fwd(state)
75
+ b0, b1, b2, b3 = state
76
+ b0 += b1
77
+ b2 += b3
78
+ b0 &= 0xff
79
+ b2 &= 0xff
80
+ b1 = rotl b1, 2
81
+ b3 = rotl b3, 5
82
+ b1 ^= b0
83
+ b3 ^= b2
84
+ b0 = rotl b0, 4
85
+ b0 += b3
86
+ b2 += b1
87
+ b0 &= 0xff
88
+ b2 &= 0xff
89
+ b1 = rotl b1, 3
90
+ b3 = rotl b3, 7
91
+ b1 ^= b2
92
+ b3 ^= b0
93
+ b2 = rotl b2, 4
94
+ [b0, b1, b2, b3]
95
+ end
96
+
97
+ def permute_bwd(state)
98
+ b0, b1, b2, b3 = state
99
+ b2 = rotl b2, 4
100
+ b1 ^= b2
101
+ b3 ^= b0
102
+ b1 = rotl b1, 5
103
+ b3 = rotl b3, 1
104
+ b0 -= b3
105
+ b2 -= b1
106
+ b0 &= 0xff
107
+ b2 &= 0xff
108
+ b0 = rotl b0, 4
109
+ b1 ^= b0
110
+ b3 ^= b2
111
+ b1 = rotl b1, 6
112
+ b3 = rotl b3, 3
113
+ b0 -= b1
114
+ b2 -= b3
115
+ b0 &= 0xff
116
+ b2 &= 0xff
117
+ [b0, b1, b2, b3]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,17 @@
1
+ module IPCrypt
2
+ class InvalidIPv4Error < Exception
3
+ def initialize(ip)
4
+ super "Invalid IPv4 address: #{ip}"
5
+ end
6
+ end
7
+ class InvalidKeyTypeError < Exception
8
+ def initialize(key, type)
9
+ super "Expected key '#{key}' to be a String, got #{type}"
10
+ end
11
+ end
12
+ class InvalidKeyBytesError < Exception
13
+ def initialize(key, bytes)
14
+ super "Expected key '#{key}' to be 16-byte, got #{bytes} byte#{bytes == 1 ? '' : ?s}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ require 'ipcrypt/engine'
2
+ module IPCrypt
3
+ module IP
4
+ def self.[](*ips) Engine.new(*ips) end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module IPCrypt
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipcrypt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Edwin Onuonga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-collection_matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faker
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.20'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.20'
97
+ description:
98
+ email:
99
+ - edwinonuonga@gmail.com
100
+ executables:
101
+ - ipcrypt
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - Gemfile
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/ipcrypt
110
+ - ipcrypt.gemspec
111
+ - lib/ipcrypt.rb
112
+ - lib/ipcrypt/cli.rb
113
+ - lib/ipcrypt/engine.rb
114
+ - lib/ipcrypt/exceptions.rb
115
+ - lib/ipcrypt/ip.rb
116
+ - lib/ipcrypt/version.rb
117
+ homepage: https://github.com/eonu/ipcrypt
118
+ licenses:
119
+ - MIT
120
+ metadata:
121
+ source_code_uri: https://github.com/eonu/ipcrypt/
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '2.5'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.7.6
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Ruby implementation of the format-preserving IPCrypt encryption algorithm
142
+ for IPv4 addresses.
143
+ test_files: []