bonio-scatter_swap 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 45d027873e1e1d8ae6967520352c54f93587576e6ee7b50d0c5b923901061fbb
4
+ data.tar.gz: 0fec3718818cc64f0919f7f2b3b28b3abc145d80caa27174fda70151ae773957
5
+ SHA512:
6
+ metadata.gz: 8adea79c3f2ef9b61dd26baa4930ea7b7a293e0c799be1a6f1892e61ca5661cac7db8350ea2f0dea5bf2af9a133a44e35d271c3d540549d7b566565bdcae424f
7
+ data.tar.gz: cd50a3dbc1e6fa29601742e710757b44ce16ae4de597ecc638d89b3119a38ea2fa37530ed3778629544b01815c251959179d6ef8ba26963c8480b2830e1530d1
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format doc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in scatter_swap.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nathan and David Amick
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,105 @@
1
+ # ScatterSwap
2
+
3
+ This is the integer hashing function behind [obfuscate_id](https://github.com/namick/obfuscate_id).
4
+
5
+ > Designing a hash function is a bit of a black art and
6
+ > being that I don't have math background, I must resort
7
+ > to this simplistic swaping and scattering of array elements.
8
+
9
+ > After writing this and reading/learning some elemental hashing techniques,
10
+ > I realized that this library is an example of what is known as a [minimal perfect hash function](http://en.wikipedia.org/wiki/Perfect_hash_function#Minimal_perfect_hash_function).
11
+
12
+ > I welcome all improvements via pull-requests :-)
13
+
14
+ > If you have some comments or suggestions, please contact me at `github@nathanamick.com` - nathan amick
15
+
16
+ ## Goals
17
+
18
+ We want to transform an integer into another random looking integer and then reliably tranform it back.
19
+
20
+ It will turn the number `3` into `2356513904`, and it can then reliably reverse that scrambled `2356513904` number back into `3`
21
+
22
+ We also want sequential integers to become non-sequential.
23
+
24
+ So for example it will turn `7001, 7002, 7003` into `5270192353, 7107163820, 3296163828`, and back again.
25
+
26
+ Please note, this is not encryption or related to security in any way. It lightly obfuscates an integer in a reversible way.
27
+
28
+ ## Usage
29
+
30
+ Pass a number (as an integer or string) to the 'hash' method and it will return an obfuscated version of it.
31
+
32
+ ScatterSwap.hash(1).to_i
33
+ #=> 4517239960
34
+
35
+ Pass that obfuscated version in and it will return the original (as a zero padded string).
36
+
37
+ ScatterSwap.reverse_hash(4517239960).to_i
38
+ #=> 1
39
+
40
+
41
+ *Because this was originally built for urls like this `example.com/users/00000000001` it outputs strings. This is why the examples above have `to_i` tacked on to them. Since extracting it to its own library, that may not make sense anymore. I'm considering output the same type as it is input. Thoughts?*
42
+
43
+ ## How it works
44
+
45
+ This library is built for integers that can be expressed with 10 digits.
46
+ It zero pads smaller numbers.
47
+
48
+ The number 1 is expressed with:
49
+
50
+ 0000000001
51
+
52
+ The biggest number it can deal with is 10 billion - 1:
53
+
54
+ 9999999999
55
+
56
+ Since we are working with a limited sequential set of input integers, 10 billion,
57
+ this algorithm will suffice for simple id obfuscation for many web apps.
58
+
59
+ The largest value that Ruby on Rails default id, Mysql INT type, is just over 2 billion (2147483647)
60
+ which is the same as 2 to the power of 31 minus 1, but considerably less than 10 billion.
61
+
62
+ ## Strategies
63
+
64
+ ScatterSwap is an integer hash function designed to have:
65
+
66
+ - zero collisions ( http://en.wikipedia.org/wiki/Perfect_hash_function )
67
+ - achieve avalanche ( http://en.wikipedia.org/wiki/Avalanche_effect )
68
+ - reversible
69
+
70
+ We do that by combining two distinct strategies.
71
+
72
+ 1. Scattering - whereby we take the whole number, slice it up into 10 digits
73
+ and rearange their places, yet retaining the same 10 digits. This allows
74
+ us to preserve the sum of all the digits, regardless of order. This sum acts
75
+ as a key to reverse this scattering effect.
76
+
77
+ 2. Swapping - when dealing with many sequential numbers that we don't want
78
+ to look similar, scattering wont do us much good because so many of the
79
+ digits are the same; it deoesn't help to scatter 9 zeros around, so we need
80
+ to swap out each of those zeros for something else.. something different
81
+ for each place in the 10 digit array; for this, we need a map so that we
82
+ can reverse it.
83
+
84
+
85
+ ## Installation
86
+
87
+ Add this line to your application's Gemfile:
88
+
89
+ gem 'bonio-scatter_swap'
90
+
91
+ And then execute:
92
+
93
+ $ bundle
94
+
95
+ Or install it yourself as:
96
+
97
+ $ gem install bonio-scatter_swap
98
+
99
+ ## Contributing
100
+
101
+ 1. Fork it
102
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
103
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
104
+ 4. Push to the branch (`git push origin my-new-feature`)
105
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'scatter_swap/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'bonio-scatter_swap'
8
+ spec.version = ScatterSwap::VERSION
9
+ spec.authors = ['khiav reoy']
10
+ spec.email = ['mrtmrt15xn@yahoo.com.tw']
11
+
12
+ spec.description = %q{ScatterSwap is an integer hash function designed to have zero collisions, achieve avalanche, and be reversible.}
13
+ spec.summary = %q{Minimal perfect hash function for 10 digit integers}
14
+ spec.homepage = 'https://github.com/khiav223577/scatter_swap'
15
+ spec.license = 'MIT'
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject{|f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}){|f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+ spec.metadata = {
30
+ 'homepage_uri' => 'https://github.com/khiav223577/scatter_swap',
31
+ 'changelog_uri' => 'https://github.com/khiav223577/scatter_swap/blob/master/CHANGELOG.md',
32
+ 'source_code_uri' => 'https://github.com/khiav223577/scatter_swap',
33
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/scatter_swap',
34
+ 'bug_tracker_uri' => 'https://github.com/khiav223577/scatter_swap/issues',
35
+ }
36
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scatter_swap'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scatter_swap/version'
4
+ require 'scatter_swap/hasher'
5
+
6
+ module ScatterSwap
7
+ def self.hash(plain_integer, spin = 0, length = 10)
8
+ Hasher.new(plain_integer, spin, length).hash
9
+ end
10
+
11
+ def self.reverse_hash(hashed_integer, spin = 0, length = 10)
12
+ Hasher.new(hashed_integer, spin, length).reverse_hash
13
+ end
14
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scatter_swap/swapper'
4
+
5
+ module ScatterSwap
6
+ class Hasher
7
+ DIGITS = (0..9).to_a.freeze
8
+
9
+ attr_accessor :working_array
10
+
11
+ def initialize(original_integer, spin = 0, length = 10)
12
+ @original_integer = original_integer
13
+ @spin = spin
14
+ @length = length
15
+ zero_pad = original_integer.to_s.rjust(length, '0')
16
+ @working_array = zero_pad.chars.map(&:to_i)
17
+ end
18
+
19
+ # obfuscates an integer up to @length digits in length
20
+ def hash
21
+ swap
22
+ scatter
23
+ @working_array.join
24
+ end
25
+
26
+ # de-obfuscates an integer
27
+ def reverse_hash
28
+ unscatter
29
+ unswap
30
+ @working_array.join
31
+ end
32
+
33
+ def swapper_map(index)
34
+ Swapper.instance(@spin).generate(index)
35
+ end
36
+
37
+ # Using a unique map for each of the ten places,
38
+ # we swap out one number for another
39
+ def swap
40
+ @working_array = @working_array.map.with_index do |digit, index|
41
+ swapper_map(index)[digit]
42
+ end
43
+ end
44
+
45
+ # Reverse swap
46
+ def unswap
47
+ @working_array = @working_array.map.with_index do |digit, index|
48
+ swapper_map(index).rindex(digit)
49
+ end
50
+ end
51
+
52
+ # Rearrange the order of each digit in a reversible way by using the
53
+ # sum of the digits (which doesn't change regardless of order)
54
+ # as a key to record how they were scattered
55
+ def scatter
56
+ sum_of_digits = @working_array.inject(:+).to_i
57
+ @working_array = @length.times.map do
58
+ @working_array.rotate!(@spin ^ sum_of_digits).pop
59
+ end
60
+ end
61
+
62
+ # Reverse the scatter
63
+ def unscatter
64
+ scattered_array = @working_array
65
+ sum_of_digits = scattered_array.inject(:+).to_i
66
+ @working_array = []
67
+ @working_array.tap do |unscatter|
68
+ @length.times do
69
+ unscatter.push scattered_array.pop
70
+ unscatter.rotate! (sum_of_digits ^ @spin) * -1
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scatter_swap'
4
+
5
+ # This file is spike code and is not part of the library
6
+ #
7
+ # While developing this, I used this file to visualize what was going on with the numbers
8
+ #
9
+ # You can uncomment various methods at the bottom and then run it like this:
10
+ #
11
+ # watch -n1 ruby run_scatter_swap.rb
12
+ #
13
+ # tweak the code a bit and see instant visual changes in the generated numbers
14
+
15
+ def visualize_scatter_and_unscatter
16
+ # change this number to experiment with different values
17
+ rotations = 99
18
+
19
+ original = ScatterSwap.arrayify(123456789)
20
+ scattered = []
21
+ unscattered = []
22
+ puts original.join
23
+ puts "rotate!(#{rotations})"
24
+ 10.times do
25
+ puts original.rotate!(rotations).join + "->" + scattered.push(original.pop).join
26
+ end
27
+ puts "scattered"
28
+ puts scattered.join
29
+ 10.times do
30
+ puts unscattered.push(scattered.pop).join + "->" + unscattered.rotate!(rotations * -1).join
31
+ end
32
+
33
+ puts unscattered.join
34
+ end
35
+
36
+
37
+ def visualize_swapper_map
38
+ puts "swapper map"
39
+ 10.times do |index|
40
+ key = 1
41
+ puts ScatterSwap.swapper_map(index).join.to_s
42
+ end
43
+ end
44
+
45
+ def visualize_hash
46
+ puts "hash"
47
+ 40.times do |integer|
48
+ output = "|"
49
+ 3.times do |index|
50
+ output += " #{(integer + (123456789 * index)).to_s.rjust(5, ' ')}"
51
+ output += " => #{hashed = ScatterSwap.hash(integer + (123456789 * index) ) }"
52
+ output += " => #{ScatterSwap.reverse_hash(hashed) } |"
53
+ end
54
+ puts output
55
+ end
56
+ end
57
+
58
+
59
+ def visualize_scatter
60
+ puts "original scattered unscattered"
61
+ 20.times do |integer|
62
+ output = ""
63
+ 2.times do |index|
64
+ original = ScatterSwap.arrayify(integer + (123456789 * index))
65
+ scattered = ScatterSwap.scatter(original.clone)
66
+ unscattered = ScatterSwap.unscatter(scattered.clone)
67
+ output += "#{original.join}"
68
+ output += " => #{scattered.join}"
69
+ output += " => #{unscattered.join} | "
70
+ end
71
+ puts output
72
+ end
73
+ end
74
+
75
+
76
+ # find hash for lots of spins
77
+ def visualize_spin
78
+ 2000.times do |original|
79
+ hashed_values = []
80
+ 9000000000.times do |spin|
81
+ hashed = ScatterSwap.hash(original, spin)
82
+ if hashed_values.include? hashed
83
+ puts "collision: #{original} - #{spin} - #{hashed}"
84
+ break
85
+ end
86
+ hashed_values.push hashed
87
+ end
88
+ end
89
+ end
90
+
91
+ #visualize_spin
92
+ #visualize_hash
93
+ #visualize_scatter_and_unscatter
94
+ #visualize_scatter
95
+ #visualize_swapper_map
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # We want a unique map for each place in the original number
4
+ module ScatterSwap
5
+ class Swapper
6
+ @all_swapper_maps = {}
7
+
8
+ class << self
9
+ def instance(spin)
10
+ @all_swapper_maps[spin] ||= new(spin)
11
+ end
12
+ end
13
+
14
+ DIGITS = (0..9).to_a.freeze
15
+
16
+ def initialize(spin)
17
+ @spin = spin
18
+ @caches = {}
19
+ end
20
+
21
+ def generate(index)
22
+ @caches[index] ||= generate_without_cache(index)
23
+ end
24
+
25
+ private
26
+
27
+ def generate_without_cache(index)
28
+ array = DIGITS.dup
29
+ sum = 0
30
+ 10.times.map.with_index do |i|
31
+ sum = (sum + ((index + i) ^ @spin) - 1) % (10 - i)
32
+ next array.delete_at(sum)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScatterSwap
4
+ VERSION = '1.0.0'
5
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bonio-scatter_swap
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - khiav reoy
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-12-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ScatterSwap is an integer hash function designed to have zero collisions,
14
+ achieve avalanche, and be reversible.
15
+ email:
16
+ - mrtmrt15xn@yahoo.com.tw
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".gitignore"
22
+ - ".rspec"
23
+ - Gemfile
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - bonio-scatter_swap.gemspec
28
+ - lib/bonio-scatter_swap.rb
29
+ - lib/scatter_swap.rb
30
+ - lib/scatter_swap/hasher.rb
31
+ - lib/scatter_swap/run.rb
32
+ - lib/scatter_swap/swapper.rb
33
+ - lib/scatter_swap/version.rb
34
+ homepage: https://github.com/khiav223577/scatter_swap
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ homepage_uri: https://github.com/khiav223577/scatter_swap
39
+ changelog_uri: https://github.com/khiav223577/scatter_swap/blob/master/CHANGELOG.md
40
+ source_code_uri: https://github.com/khiav223577/scatter_swap
41
+ documentation_uri: https://www.rubydoc.info/gems/scatter_swap
42
+ bug_tracker_uri: https://github.com/khiav223577/scatter_swap/issues
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.0.3
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Minimal perfect hash function for 10 digit integers
62
+ test_files: []