obfuscate_id 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Nathan Amick
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,77 @@
1
+ # ObfuscateId
2
+
3
+ ObfuscateId is a simple Ruby on Rails plugin that hides your seqential Active Record ids. Although having nothing to do with security, it can be used to make database record id information non-obvious.
4
+
5
+ For new websites, you may not want to give away information about how many people are signed up. Every website has a third user, but that third user doesn't have to know he is the third user.
6
+
7
+ ObfuscateId turns a URL like this:
8
+
9
+ http://example.com/users/3
10
+
11
+ into something like:
12
+
13
+ http://example.com/users/2356513904
14
+
15
+ ObfuscateId mixes up the ids in a simple, reversable hashing algorithm so that it can then automatically revert the hashed number back to 3 for record lookup without having to store a hash or tag in the database. No migrations needed.
16
+
17
+ If you have the opposite problem, and your site is scaling well, you might not want to leak that you are getting 50 new posts a minute.
18
+
19
+ ObfuscateId turns your sequential Active Record ids into non-sequential, random looking, numeric ids.
20
+
21
+ # post 7000
22
+ http://example.com/posts/5270192353
23
+ # post 7001
24
+ http://example.com/posts/7107163820
25
+ # post 7002
26
+ http://example.com/posts/3296163828
27
+
28
+ ## Features
29
+
30
+ * Extreemly simple. A single line of code in the model turns it on.
31
+ * Transforms normal seqential ids into random-looking ten digit numerical strings.
32
+ * Gently masks resource ids while retaining a cleaner look than using an encrypted hash.
33
+ * No migrations or database changes are needed. The record is still stored in the database with its original id.
34
+ * Fast, no heavy calculation.
35
+
36
+
37
+ ## Installation
38
+
39
+ Add the gem to your Gemfile.
40
+
41
+ gem "obfuscate_id"
42
+
43
+ Run bundler.
44
+
45
+ bundle install
46
+
47
+ Then, in your model, add a single line.
48
+
49
+ class Post < ActiveRecord::Base
50
+ obfuscate_id
51
+ end
52
+
53
+ ## Customization
54
+
55
+ If you want your obfuscated ids to be different than some other website using the same plugin, you can throw a random number (spin) at obfuscate_id to make it hash out unique ids for your app.
56
+
57
+ class Post < ActiveRecord::Base
58
+ obfuscate_id :spin => 89238723
59
+ end
60
+
61
+ This is also useful for making different models in the same app have different obfuscated ids.
62
+
63
+ ## How it works
64
+
65
+ ObfuscateId pairs each number, from 0 to 9999999999, with one and only one number in that same range. That other number is paired back to the first. This is an example of a minimal perfect hash function. Within a set of ten billion numbers, it simply maps every number to a different 10 digit number, and back again.
66
+
67
+ ObfuscateId switches the plain record id to the obfuscated id in the models `to_param` method.
68
+
69
+ It then augments Active Record's `find` method on models that have have been initiated with the `obfuscate_id` method to quickly reverse this obfuscated id back to the plain id before building the database query. This means no migrations or changes to the database.
70
+
71
+ ## Limitations
72
+
73
+ * This is not security. ObfuscateId was created to lightly mask record id numbers for the casual user. If you need to really secure your database ids (hint, you probably don't), you need to use real encryption like AES.
74
+ * Works for up to ten billion database records. ObfuscateId simply maps every integer below ten billion to some other number below ten billion.
75
+ * To properly generate obfuscated urls, make sure you trigger the model's `to_param` method by passing in the whole object rather than just the id; do this: `post_path(@post)` not this: `post_path(@post.id)`.
76
+ * Rails uses the real id rather than `to_param` in some places. A simple view-source on a form will often show the real id. This can be avoided by taking certain precautions.
77
+
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ObfuscateId'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
@@ -0,0 +1,53 @@
1
+ module ObfuscateId
2
+
3
+ def obfuscate_id(options = {})
4
+ require 'obfuscate_id/scatter_swap'
5
+ extend ClassMethods
6
+ include InstanceMethods
7
+ cattr_accessor :obfuscate_id_spin
8
+ self.obfuscate_id_spin = (options[:spin] || obfuscate_id_default_spin)
9
+ end
10
+
11
+ def self.hide(id)
12
+ ScatterSwap.hash(id)
13
+ end
14
+
15
+ def self.show(id)
16
+ ScatterSwap.reverse_hash(id)
17
+ end
18
+
19
+
20
+ module ClassMethods
21
+ def find(*args)
22
+ if has_obfuscated_id?
23
+ args[0] = ObfuscateId.show(args[0])
24
+ end
25
+ super(*args)
26
+ end
27
+
28
+ def has_obfuscated_id?
29
+ true
30
+ end
31
+
32
+ # Generate a default spin from the Model name
33
+ # This makes it easy to drop obfuscate_id onto any model
34
+ # and produce different obfuscated ids for different models
35
+ def obfuscate_id_default_spin
36
+ alphabet = Array("a".."z")
37
+ number = name.split("").collect do |char|
38
+ alphabet.index(char)
39
+ end
40
+ number.join.to_i
41
+ end
42
+
43
+ end
44
+
45
+ module InstanceMethods
46
+ def to_param
47
+ ObfuscateId.hide(self.id)
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+ ActiveRecord::Base.extend ObfuscateId
@@ -0,0 +1,93 @@
1
+ require './scatter_swap.rb'
2
+
3
+ # This file isn't really part of the library, its pretty much spike code..
4
+ #
5
+ # While developing this, I used this file to visualize what was going on with the numbers
6
+ #
7
+ # You can uncomment various methods at the bottom and then run it like this:
8
+ #
9
+ # watch -n1 ruby run_scatter_swap.rb
10
+ #
11
+ # tweak the code a bit and see instant visual changes in the generated numbers
12
+
13
+ def visualize_scatter_and_unscatter
14
+ # change this number to experiment with different values
15
+ rotations = 99
16
+
17
+ original = ScatterSwap.arrayify(123456789)
18
+ scattered = []
19
+ unscattered = []
20
+ puts original.join
21
+ puts "rotate!(#{rotations})"
22
+ 10.times do
23
+ puts original.rotate!(rotations).join + "->" + scattered.push(original.pop).join
24
+ end
25
+ puts "scattered"
26
+ puts scattered.join
27
+ 10.times do
28
+ puts unscattered.push(scattered.pop).join + "->" + unscattered.rotate!(rotations * -1).join
29
+ end
30
+
31
+ puts unscattered.join
32
+ end
33
+
34
+
35
+ def visualize_swapper_map
36
+ puts "swapper map"
37
+ 10.times do |index|
38
+ key = 1
39
+ puts ScatterSwap.swapper_map(index).join.to_s
40
+ end
41
+ end
42
+
43
+ def visualize_hash
44
+ puts "hash"
45
+ 40.times do |integer|
46
+ output = "|"
47
+ 3.times do |index|
48
+ output += " #{(integer + (123456789 * index)).to_s.rjust(5, ' ')}"
49
+ output += " => #{hashed = ScatterSwap.hash(integer + (123456789 * index) ) }"
50
+ output += " => #{ScatterSwap.reverse_hash(hashed) } |"
51
+ end
52
+ puts output
53
+ end
54
+ end
55
+
56
+
57
+ def visualize_scatter
58
+ puts "original scattered unscattered"
59
+ 20.times do |integer|
60
+ output = ""
61
+ 2.times do |index|
62
+ original = ScatterSwap.arrayify(integer + (123456789 * index))
63
+ scattered = ScatterSwap.scatter(original.clone)
64
+ unscattered = ScatterSwap.unscatter(scattered.clone)
65
+ output += "#{original.join}"
66
+ output += " => #{scattered.join}"
67
+ output += " => #{unscattered.join} | "
68
+ end
69
+ puts output
70
+ end
71
+ end
72
+
73
+
74
+ # find hash for lots of spins
75
+ def visualize_spin
76
+ 2000.times do |original|
77
+ hashed_values = []
78
+ 9000000000.times do |spin|
79
+ hashed = ScatterSwap.hash(original, spin)
80
+ if hashed_values.include? hashed
81
+ puts "collision: #{original} - #{spin} - #{hashed}"
82
+ break
83
+ end
84
+ hashed_values.push hashed
85
+ end
86
+ end
87
+ end
88
+
89
+ #visualize_spin
90
+ #visualize_hash
91
+ #visualize_scatter_and_unscatter
92
+ #visualize_scatter
93
+ #visualize_swapper_map
@@ -0,0 +1,138 @@
1
+ class ScatterSwap
2
+ # This is the hashing function behind ObfuscateId.
3
+ # 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 realize this library is what is known as a Minimal perfect hash function:
11
+ # http://en.wikipedia.org/wiki/Perfect_hash_function#Minimal_perfect_hash_function
12
+ #
13
+ # I welcome all improvements :-)
14
+ #
15
+ # If you have some comments or suggestions, please contact me on github
16
+ # https://github.com/namick
17
+ #
18
+ # - nathan amick
19
+ #
20
+ #
21
+ # This library is built for integers that can be expressed with 10 digits:
22
+ # It zero pads smaller numbers... so the number one is expressed with:
23
+ # 0000000001
24
+ # The biggest number it can deal with is:
25
+ # 9999999999
26
+ #
27
+ # Since we are working with a limited sequential set of input integers, 10 billion,
28
+ # this algorithm will suffice for simple id obfuscation for many web apps.
29
+ # The largest value that Ruby on Rails default id, Mysql INT type, is just over 2 billion (2147483647)
30
+ # which is the same as 2 to the power of 31 minus 1, but considerably less than 10 billion.
31
+ #
32
+ # ScatterSwap is an integer hash function designed to have:
33
+ # - zero collisions ( http://en.wikipedia.org/wiki/Perfect_hash_function )
34
+ # - achieve avalanche ( http://en.wikipedia.org/wiki/Avalanche_effect )
35
+ # - reversable
36
+ #
37
+ # We do that by combining two distinct strategies.
38
+ #
39
+ # 1. Scattering - whereby we take the whole number, slice it up into 10 digits
40
+ # and rearange their places, yet retaining the same 10 digits. This allows
41
+ # us to preserve the sum of all the digits, regardless of order. This sum acts
42
+ # as a key to reverse this scattering effect.
43
+ #
44
+ # 2. Swapping - when dealing with many sequential numbers that we don't want
45
+ # to look similar, scattering wont do us much good because so many of the
46
+ # digits are the same; it deoesn't help to scatter 9 zeros around, so we need
47
+ # to swap out each of those zeros for something else.. something different
48
+ # for each place in the 10 digit array; for this, we need a map so that we
49
+ # can reverse it.
50
+
51
+ # Convience class method pointing to the instance method
52
+ def self.hash(plain_integer, spin = 0)
53
+ new(plain_integer, spin).hash
54
+ end
55
+
56
+ # Convience class method pointing to the instance method
57
+ def self.reverse_hash(hashed_integer, spin = 0)
58
+ new(hashed_integer, spin).reverse_hash
59
+ end
60
+
61
+ def initialize(original_integer, spin = 0)
62
+ @original_integer = original_integer
63
+ @spin = spin
64
+ zero_pad = original_integer.to_s.rjust(10, '0')
65
+ @working_array = zero_pad.split("").collect {|d| d.to_i}
66
+ end
67
+
68
+ attr_accessor :working_array
69
+
70
+ # obfuscates an integer up to 10 digits in length
71
+ def hash
72
+ swap
73
+ scatter
74
+ completed_string
75
+ end
76
+
77
+ # de-obfuscates an integer
78
+ def reverse_hash
79
+ unscatter
80
+ unswap
81
+ completed_string
82
+ end
83
+
84
+ def completed_string
85
+ @working_array.join
86
+ end
87
+
88
+ # We want a unique map for each place in the original number
89
+ def swapper_map(index)
90
+ array = (0..9).to_a
91
+ 10.times.collect.with_index do |i|
92
+ array.rotate!(index + i ^ spin).pop
93
+ end
94
+ end
95
+
96
+ # Using a unique map for each of the ten places,
97
+ # we swap out one number for another
98
+ def swap
99
+ @working_array = @working_array.collect.with_index do |digit, index|
100
+ swapper_map(index)[digit]
101
+ end
102
+ end
103
+
104
+ # Reverse swap
105
+ def unswap
106
+ @working_array = @working_array.collect.with_index do |digit, index|
107
+ swapper_map(index).rindex(digit)
108
+ end
109
+ end
110
+
111
+ # Rearrange the order of each digit in a reversable way by using the
112
+ # sum of the digits (which doesn't change regardless of order)
113
+ # as a key to record how they were scattered
114
+ def scatter
115
+ sum_of_digits = @working_array.inject(:+).to_i
116
+ @working_array = 10.times.collect do
117
+ @working_array.rotate!(spin ^ sum_of_digits).pop
118
+ end
119
+ end
120
+
121
+ # Reverse the scatter
122
+ def unscatter
123
+ scattered_array = @working_array
124
+ sum_of_digits = scattered_array.inject(:+).to_i
125
+ @working_array = []
126
+ @working_array.tap do |unscatter|
127
+ 10.times do
128
+ unscatter.push scattered_array.pop
129
+ unscatter.rotate! (sum_of_digits ^ spin) * -1
130
+ end
131
+ end
132
+ end
133
+
134
+ # Add some spice so that different apps can have differently mapped hashes
135
+ def spin
136
+ @spin || 0
137
+ end
138
+ end
@@ -0,0 +1,3 @@
1
+ module ObfuscateId
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :obfuscate_id do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: obfuscate_id
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Amick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: &9066640 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *9066640
25
+ - !ruby/object:Gem::Dependency
26
+ name: sqlite3
27
+ requirement: &9063100 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *9063100
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec-rails
38
+ requirement: &9061600 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *9061600
47
+ - !ruby/object:Gem::Dependency
48
+ name: capybara
49
+ requirement: &9058580 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *9058580
58
+ - !ruby/object:Gem::Dependency
59
+ name: guard-rspec
60
+ requirement: &9057040 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *9057040
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-spork
71
+ requirement: &9055460 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *9055460
80
+ description: ObfuscateId is a simple Ruby on Rails plugin that hides your seqential
81
+ Active Record ids. Although having nothing to do with security, it can be used
82
+ to make database record id information non-obvious.
83
+ email:
84
+ - github@nathanamick.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - lib/tasks/obfuscate_id_tasks.rake
90
+ - lib/obfuscate_id/scatter_swap.rb
91
+ - lib/obfuscate_id/run_scatter_swap.rb
92
+ - lib/obfuscate_id/version.rb
93
+ - lib/obfuscate_id.rb
94
+ - MIT-LICENSE
95
+ - Rakefile
96
+ - README.md
97
+ homepage: ''
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ segments:
110
+ - 0
111
+ hash: -372583255443185690
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ segments:
119
+ - 0
120
+ hash: -372583255443185690
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 1.8.6
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: A simple Rails plugin that lightly masks seqential ActiveRecord ids
127
+ test_files: []