obfuscurity 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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +116 -0
- data/Rakefile +1 -0
- data/lib/obfuscurity.rb +43 -0
- data/lib/obfuscurity/version.rb +3 -0
- data/obfuscurity.gemspec +30 -0
- data/test/obfuscator_test.rb +52 -0
- metadata +107 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Graham Ashton
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Obfuscurity
|
2
|
+
|
3
|
+
Have you ever needed to generate a unique number for a piece of data (a
|
4
|
+
little like a primary key) that's exposed to your customers? You might
|
5
|
+
use your database's primary key, but you don't want to expose an
|
6
|
+
internal counter that could give away sensitive information.
|
7
|
+
|
8
|
+
An **order number in a billing system** is a good example; until you've
|
9
|
+
processed thousands of orders you won't want people to know just how
|
10
|
+
young your business it (in some cases, it doesn't instil confidence).
|
11
|
+
|
12
|
+
Or perhaps you don't want your competitors to be able to work out **how
|
13
|
+
many people have signed up for your web app**, but want to include a
|
14
|
+
customer number in the URL? If you were to use an incrementing field
|
15
|
+
from your database, that information becomes painfully apparent.
|
16
|
+
|
17
|
+
The obvious solution (generating a random number and checking to see
|
18
|
+
whether it's already in use) feels like a hack, and performance starts
|
19
|
+
to suffer (because you find yourself generating numbers that have
|
20
|
+
already been used) quicker than you might expect.
|
21
|
+
|
22
|
+
Luckily, there's an easy solution, as outlined in [this comment][comment]
|
23
|
+
on StackOverflow. Just in case you don't have access to StackOverflow
|
24
|
+
right now, this is what it says:
|
25
|
+
|
26
|
+
> Pick a 8 or 9 digit number at random, say 839712541. Then, take your
|
27
|
+
> order number's binary representation (for this example, I'm not using
|
28
|
+
> 2's complement), pad it out to the same number of bits (30), reverse it,
|
29
|
+
> and xor the flipped order number and the magic number.
|
30
|
+
|
31
|
+
[comment]: http://stackoverflow.com/a/612085/158841
|
32
|
+
|
33
|
+
(Don't worry if you didn't follow that)
|
34
|
+
|
35
|
+
It's rather ingenius, and allows you to take a random seed (e.g.
|
36
|
+
839712541) and a incrementing series of numbers, and convert them into a
|
37
|
+
seemingly random series. You can then convert them back again simply by
|
38
|
+
reversing the approach.
|
39
|
+
|
40
|
+
Of course, this **isn't** a secure approach. Anybody with a computer and
|
41
|
+
plenty of time would be able to work out the pattern.
|
42
|
+
|
43
|
+
If you've got some numbers that you seriously need to protect, use
|
44
|
+
cryptography. Obscurity provides [no security at all][wikipedia].
|
45
|
+
|
46
|
+
[wikipedia]: http://en.wikipedia.org/wiki/Security_through_obscurity
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
Add this line to your application's Gemfile:
|
51
|
+
|
52
|
+
gem 'obfuscurity'
|
53
|
+
|
54
|
+
And then execute:
|
55
|
+
|
56
|
+
$ bundle
|
57
|
+
|
58
|
+
Or install it yourself as:
|
59
|
+
|
60
|
+
$ gem install obfuscurity
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
|
64
|
+
To obfuscate the number you want to hide, make yourself a `Baffler`:
|
65
|
+
|
66
|
+
baffler = Obfuscurity::Baffler.new
|
67
|
+
baffler.obfuscate(1) # -> 302841629
|
68
|
+
baffler.obfuscate(2) # -> 571277085
|
69
|
+
|
70
|
+
If you later want to convert back to your primary key, use the clarify
|
71
|
+
method:
|
72
|
+
|
73
|
+
baffler.clarify(302841629) # -> 1
|
74
|
+
baffler.clarify(571277085) # -> 2
|
75
|
+
|
76
|
+
The `Baffler` object just happens to convert `1` to `302841629` because
|
77
|
+
of the seed that it's using (see the [StackOverflow comment][comment]
|
78
|
+
for details).
|
79
|
+
|
80
|
+
### Configuring behaviour
|
81
|
+
|
82
|
+
If you'd like to use a different sequence specify a different seed when
|
83
|
+
you create the `Baffler` instance:
|
84
|
+
|
85
|
+
baffler = Obfuscurity::Baffler.new(seed: 61493749)
|
86
|
+
|
87
|
+
You'll no doubt have noticed that the numbers produced by default are
|
88
|
+
rather large. The algorithm uses a fixed number of bits (30 by default),
|
89
|
+
so any number as large as `2 ** 30` could be returned by the `obfuscate`
|
90
|
+
method.
|
91
|
+
|
92
|
+
If you know you won't need anything like that many unique numbers you
|
93
|
+
can reduce the number of bits:
|
94
|
+
|
95
|
+
baffler = Obfuscurity::Baffler.new(max_bits: 16)
|
96
|
+
baffler.obfuscate(42) # -> 21505
|
97
|
+
|
98
|
+
Just be aware that the maximum number of unique numbers that you can
|
99
|
+
cope with is `2 ** max_bits`, or in the case of 16 bits, just 32768
|
100
|
+
(which isn't a lot).
|
101
|
+
|
102
|
+
If you attempt to obfuscate a number that's too big to fit in the number
|
103
|
+
space available (i.e. you exceed the value set for `max_bits`) a
|
104
|
+
`Obfuscurity::Error` exception will be raised.
|
105
|
+
|
106
|
+
## Contributing
|
107
|
+
|
108
|
+
1. Fork it
|
109
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
110
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
111
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
112
|
+
5. Create new Pull Request
|
113
|
+
|
114
|
+
## Credits
|
115
|
+
|
116
|
+
Thanks to @benlovell for suggesting the name.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/obfuscurity.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'obfuscurity/version'
|
2
|
+
|
3
|
+
module Obfuscurity
|
4
|
+
class Error < RuntimeError; end
|
5
|
+
|
6
|
+
class Baffler
|
7
|
+
def initialize(params = {})
|
8
|
+
@seed = params.fetch(:seed, 839712541)
|
9
|
+
@max_bits = params.fetch(:max_bits, 30)
|
10
|
+
check_size_of_number_space(@seed)
|
11
|
+
end
|
12
|
+
|
13
|
+
def obfuscate(number)
|
14
|
+
check_size_of_number_space(number)
|
15
|
+
seed_bits = number_to_bits(@seed)
|
16
|
+
xor_bits = (0 ... seed_bits.size).map { |i| number[i] ^ seed_bits[i] }
|
17
|
+
bits_to_number(xor_bits)
|
18
|
+
end
|
19
|
+
|
20
|
+
def clarify(number)
|
21
|
+
bits = number_to_bits(number ^ @seed)
|
22
|
+
bits_to_number(bits.reverse)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def number_to_bits(number)
|
27
|
+
[].tap do |bits|
|
28
|
+
(@max_bits - 1).downto(0) { |i| bits << number[i] }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def bits_to_number(bits)
|
33
|
+
bits.inject(0) { |result, bit| (result << 1) | bit }
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_size_of_number_space(number)
|
37
|
+
root = number ** (1.0 / @max_bits)
|
38
|
+
if root > 2
|
39
|
+
raise Error.new("#{number} requires more than #{@max_bits} bits")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/obfuscurity.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'obfuscurity/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "obfuscurity"
|
8
|
+
gem.version = Obfuscurity::VERSION
|
9
|
+
gem.authors = ["Graham Ashton"]
|
10
|
+
gem.email = ["graham@effectif.com"]
|
11
|
+
gem.description = <<-EOF
|
12
|
+
Sometimes exposing your app's internal counters (e.g. your database's
|
13
|
+
auto-incrementing primary keys) to the world is a bad idea. Maybe your
|
14
|
+
competitors will be able to work out how many orders you're making per
|
15
|
+
week, or your customers will be able to infer how many other customers
|
16
|
+
you've got. This gem will allow you to obscure those numbers so you can
|
17
|
+
use them in your URLs, your user interface, or as seemingly random order
|
18
|
+
numbers.
|
19
|
+
EOF
|
20
|
+
gem.summary = %q{Obfuscate your database ids, converting them to (seemingly) random numbers}
|
21
|
+
gem.homepage = "https://github.com/gma/obfuscurity"
|
22
|
+
|
23
|
+
gem.files = `git ls-files`.split($/)
|
24
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
25
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
26
|
+
gem.require_paths = ["lib"]
|
27
|
+
|
28
|
+
gem.add_development_dependency('nutrasuite')
|
29
|
+
gem.add_development_dependency('rake')
|
30
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'nutrasuite'
|
3
|
+
|
4
|
+
require_relative '../lib/obfuscurity'
|
5
|
+
|
6
|
+
class BafflerTest < MiniTest::Unit::TestCase
|
7
|
+
a 'Baffler' do
|
8
|
+
it 'should generate unique (repeatable) number from integer' do
|
9
|
+
# Wondering why the results are 302841629 and 571277085? See this
|
10
|
+
# thread on Stack Overflow:
|
11
|
+
#
|
12
|
+
# http://stackoverflow.com/questions/1179439/best-way-to-generate-order-numbers-for-an-online-store
|
13
|
+
|
14
|
+
2.times do
|
15
|
+
assert_equal 302841629, Obfuscurity::Baffler.new.obfuscate(1)
|
16
|
+
end
|
17
|
+
assert_equal 571277085, Obfuscurity::Baffler.new.obfuscate(2)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should convert an obfuscated number back into the original integer' do
|
21
|
+
baffler = Obfuscurity::Baffler.new
|
22
|
+
|
23
|
+
assert_equal 1, baffler.clarify(302841629)
|
24
|
+
assert_equal 2, baffler.clarify(571277085)
|
25
|
+
|
26
|
+
10.times do |i|
|
27
|
+
n = baffler.obfuscate(i)
|
28
|
+
assert_equal i, baffler.clarify(n)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'allow you to control the size of the number space' do
|
33
|
+
baffler = Obfuscurity::Baffler.new(max_bits: 16, seed: 1)
|
34
|
+
assert_equal 21505, baffler.obfuscate(42)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should ensure that seed doesn't exceed available bits" do
|
38
|
+
max_bits = 30
|
39
|
+
too_big = (2 ** max_bits) + 1
|
40
|
+
assert_raises(Obfuscurity::Error) do
|
41
|
+
Obfuscurity::Baffler.new(max_bits: max_bits, seed: too_big)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should ensure that obscured numbers don't exceed available bits" do
|
46
|
+
baffler = Obfuscurity::Baffler.new
|
47
|
+
assert_raises(Obfuscurity::Error) do
|
48
|
+
baffler.obfuscate((2 ** 30) + 1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: obfuscurity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Graham Ashton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nutrasuite
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ! 'Sometimes exposing your app''s internal counters (e.g. your database''s
|
47
|
+
|
48
|
+
auto-incrementing primary keys) to the world is a bad idea. Maybe your
|
49
|
+
|
50
|
+
competitors will be able to work out how many orders you''re making per
|
51
|
+
|
52
|
+
week, or your customers will be able to infer how many other customers
|
53
|
+
|
54
|
+
you''ve got. This gem will allow you to obscure those numbers so you can
|
55
|
+
|
56
|
+
use them in your URLs, your user interface, or as seemingly random order
|
57
|
+
|
58
|
+
numbers.
|
59
|
+
|
60
|
+
'
|
61
|
+
email:
|
62
|
+
- graham@effectif.com
|
63
|
+
executables: []
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- .gitignore
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- lib/obfuscurity.rb
|
73
|
+
- lib/obfuscurity/version.rb
|
74
|
+
- obfuscurity.gemspec
|
75
|
+
- test/obfuscator_test.rb
|
76
|
+
homepage: https://github.com/gma/obfuscurity
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
hash: 2796161447218621274
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
hash: 2796161447218621274
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.8.23
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Obfuscate your database ids, converting them to (seemingly) random numbers
|
106
|
+
test_files:
|
107
|
+
- test/obfuscator_test.rb
|