gfc64 0.0.2 → 0.1.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 +4 -4
- data/README.md +71 -10
- data/lib/gfc64/active_record.rb +39 -0
- data/lib/gfc64.rb +3 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d7c2a4aaef604ecbe6d4c432f001bae3122383ab3c7e0d3e588db43b075da7a
|
4
|
+
data.tar.gz: 11d79c4899212011f01b93b2d1b86c7663b84cdb73184817aa997fb6988a1f05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dab37bcb93e364b9dcd98d1004e2b0c1bc7c62ad8d44650d3aedc80093564449972e8e1e1c550083baee464cd02bf7a6241a9cfa921fc5b771fbbe9c0c81c185
|
7
|
+
data.tar.gz: dba7ac573a35f604bc7ebb4d82e82d76becd983799318d5e1eda6a6cb867b5367adfbde0fa9dabead075299bd5e5d7c1611b61bf86559cacec4f641a337b589d
|
data/README.md
CHANGED
@@ -1,20 +1,39 @@
|
|
1
1
|
# GFC64
|
2
2
|
|
3
|
-
|
4
|
-
integers into... 64-bit integers!
|
3
|
+
Encrypt 64-bit integers into other 64-bit integers without collisions.
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
(e.g. `/customers/1`, `/customers/2`, ...), and you don't want to add another
|
9
|
-
column to store something auxiliary like a UUID.
|
5
|
+
Useful for hiding database auto-incrementing primary keys without needing
|
6
|
+
to store additional data (like a UUID field).
|
10
7
|
|
11
|
-
For example,
|
8
|
+
For example, say you have a web app where routes are stored like `/customers/:id`
|
9
|
+
so that you have exposed URL paths like this:
|
12
10
|
|
13
|
-
`/customers/1`
|
11
|
+
`/customers/1`
|
14
12
|
|
15
|
-
`/customers/2`
|
13
|
+
`/customers/2`
|
16
14
|
|
17
|
-
|
15
|
+
With this gem, you can (at view-time) encrypt the above IDs into different IDs
|
16
|
+
so that you get these paths instead, without storing any additional data:
|
17
|
+
|
18
|
+
`/customers/4552956331295818987` (the backend decrypts this into `/customers/1`)
|
19
|
+
|
20
|
+
`/customers/3833777695217202560` (the backend decrypts this into `/customers/2`)
|
21
|
+
|
22
|
+
All while keeping your auto-incrementing, sequential IDs in your database and
|
23
|
+
getting all the benefits of a standard database index.
|
24
|
+
|
25
|
+
The encryption is achieved by implementing a format-preserving
|
26
|
+
[Generalized Feistel Cipher][paper].
|
27
|
+
|
28
|
+
## Installation
|
29
|
+
|
30
|
+
Add to your `Gemfile`:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem "gfc64"
|
34
|
+
```
|
35
|
+
|
36
|
+
## General Usage
|
18
37
|
|
19
38
|
```ruby
|
20
39
|
key = SecureRandom.hex(32) # => "ffb5e3600fc27924f97dc055440403b10ce97160261f2a87eee576584cf942e5"
|
@@ -25,6 +44,48 @@ gfc.encrypt(2) # => 3833777695217202560
|
|
25
44
|
gfc.decrypt(3833777695217202560) # => 2
|
26
45
|
```
|
27
46
|
|
47
|
+
## Rails Usage
|
48
|
+
|
49
|
+
For Rails, there's an ActiveRecord mixin which adds `#gfc_id` and
|
50
|
+
`#to_param` methods and a `::find_gfc` class method. By setting `#to_param`,
|
51
|
+
resource path helpers like `customer_path(@customer)` automatically use the
|
52
|
+
GFC encrypted ID.
|
53
|
+
|
54
|
+
Example usage:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# app/models/customer.rb
|
58
|
+
|
59
|
+
class Customer < ApplicationRecord
|
60
|
+
include GFC64::ActiveRecord[GFC64.new(ENV['GFC_KEY'])]
|
61
|
+
# or the argument can be a proc/lambda if you need late binding:
|
62
|
+
# include GFC64::ActiveRecord[-> { GFC64.new(ENV['GFC_KEY']) }]
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
For retrieval, use `::find_gfc`:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# app/controllers/customers_controller.rb
|
70
|
+
|
71
|
+
class CustomersController < ApplicationController
|
72
|
+
def show
|
73
|
+
@customer = Customer.find_gfc(params[:id])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
## Potential drawbacks
|
79
|
+
|
80
|
+
Ruby's dynamic typing means we're just passing bare Integers around, so it's
|
81
|
+
possible to make a mistake where you write something like `Model.find(gfc_id)`
|
82
|
+
and return a completely unexpected record because you forgot to decrypt the ID.
|
83
|
+
|
84
|
+
With a type system it would be trivial to prevent these errors. However, the
|
85
|
+
space of 64-bit integers is very large, and encrypted IDs tend to occupy numbers
|
86
|
+
much higher than most web apps will ever reach, so more than likely this sort of
|
87
|
+
error would be discovered quickly as queries fail to find anything.
|
88
|
+
|
28
89
|
## Disclaimer
|
29
90
|
|
30
91
|
This code has not been vetted by a security audit or a professional
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GFC64
|
4
|
+
module ActiveRecord
|
5
|
+
def self.[](gfc)
|
6
|
+
Module.new do
|
7
|
+
include ActiveRecord
|
8
|
+
|
9
|
+
define_singleton_method :included do |klass|
|
10
|
+
klass.extend(ClassMethods)
|
11
|
+
klass.send(:include, InstanceMethods)
|
12
|
+
klass.f_gfc = gfc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
attr_accessor :f_gfc
|
19
|
+
|
20
|
+
def gfc
|
21
|
+
@gfc ||= f_gfc.respond_to?(:call) ? f_gfc.call : f_gfc
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_gfc(gfc_id)
|
25
|
+
find(gfc.decrypt(gfc_id))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def gfc_id
|
31
|
+
id && self.class.gfc.encrypt(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_param
|
35
|
+
gfc_id.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/gfc64.rb
CHANGED
@@ -4,7 +4,7 @@ require "openssl"
|
|
4
4
|
require "securerandom"
|
5
5
|
|
6
6
|
class GFC64
|
7
|
-
VERSION = "0.0
|
7
|
+
VERSION = "0.1.0".freeze
|
8
8
|
|
9
9
|
attr_reader :rounds, :key, :block_size
|
10
10
|
|
@@ -55,3 +55,5 @@ class GFC64
|
|
55
55
|
OpenSSL::Digest::SHA256.digest(key + [round].pack('L'))[0...16] # Truncate to 128 bits for AES key
|
56
56
|
end
|
57
57
|
end
|
58
|
+
|
59
|
+
require "gfc64/active_record"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gfc64
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abe Voelker
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Hides sequential primary key counts without resorting to UUIDs or GUIDs.
|
14
14
|
email: abe@abevoelker.com
|
@@ -18,6 +18,7 @@ extra_rdoc_files: []
|
|
18
18
|
files:
|
19
19
|
- README.md
|
20
20
|
- lib/gfc64.rb
|
21
|
+
- lib/gfc64/active_record.rb
|
21
22
|
homepage: https://github.com/abevoelker/gfc64
|
22
23
|
licenses:
|
23
24
|
- MIT
|
@@ -34,7 +35,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
35
|
requirements:
|
35
36
|
- - ">="
|
36
37
|
- !ruby/object:Gem::Version
|
37
|
-
version: 2.
|
38
|
+
version: 2.4.0
|
38
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
40
|
requirements:
|
40
41
|
- - ">="
|