gfc64 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +46 -0
- data/lib/gfc64.rb +57 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f93c96115de165e7cec9824e893e7254b6612c5d254c19d19c14899c7aa68ef1
|
4
|
+
data.tar.gz: 00fc60e41ebe8a82bd7de95386aaa097df8a468ecb13e1ad98c7a4b5a311f59c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b7b53943801d158fbc1e44c2a9bd6038aa4445047ebfc7de941f3dc583bb55120e9353dbea6d12de4253d1bc67f88c44e4a1c2326a549f4dd13a6a0842e2041
|
7
|
+
data.tar.gz: 6674b80a4457530858aa15b57d89b421431e81313066356d95013292257b160936efde74b27349f410d5cc002dc64bf5dfe9fb00f08af830d72c5dfb2268a617
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# GFC64
|
2
|
+
|
3
|
+
Format-preserving Generalized Feistel Cipher for 64-bit integers. Encrypts 64-bit
|
4
|
+
integers into... 64-bit integers!
|
5
|
+
|
6
|
+
Very useful for if you have sequential 64-bit integer database primary keys that
|
7
|
+
you want to expose in an app, but you don't want to leak count information
|
8
|
+
(e.g. `/customers/1`, `/customers/2`, ...), and you don't want to add another
|
9
|
+
column to store something auxiliary like a UUID.
|
10
|
+
|
11
|
+
For example, with this gem, routes like
|
12
|
+
|
13
|
+
`/customers/1` can become something like `/customers/4552956331295818987`, and
|
14
|
+
|
15
|
+
`/customers/2` can become something like `/customers/3833777695217202560`
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
key = SecureRandom.hex(32) # => "ffb5e3600fc27924f97dc055440403b10ce97160261f2a87eee576584cf942e5"
|
21
|
+
gfc = GFC64.new(key)
|
22
|
+
gfc.encrypt(1) # => 4552956331295818987
|
23
|
+
gfc.decrypt(4552956331295818987) # => 1
|
24
|
+
gfc.encrypt(2) # => 3833777695217202560
|
25
|
+
gfc.decrypt(3833777695217202560) # => 2
|
26
|
+
```
|
27
|
+
|
28
|
+
## Disclaimer
|
29
|
+
|
30
|
+
This code has not been vetted by a security audit or a professional
|
31
|
+
cryptographer so may be wrong and/or insecure.
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
This was written with AI tool assistance. If any code mirrors existing
|
36
|
+
copyrighted works, it was not my intent, and the copyright remains with the
|
37
|
+
original works. My contributions are MIT licensed.
|
38
|
+
|
39
|
+
## References
|
40
|
+
|
41
|
+
Black, John, and Phillip Rogaway. ["Ciphers with Arbitrary Finite Domains."][paper] In Topics in Cryptology — CT-RSA 2002, edited by Bart Preneel, 2271:114–30. Lecture Notes in Computer Science. Berlin, Heidelberg: Springer Berlin Heidelberg, 2002. https://doi.org/10.1007/3-540-45760-7_9.
|
42
|
+
|
43
|
+
Hat tip to [this Hacker News comment][hn] that led me to the idea and the paper.
|
44
|
+
|
45
|
+
[paper]: https://web.cs.ucdavis.edu/~rogaway/papers/subset.pdf
|
46
|
+
[hn]: https://news.ycombinator.com/item?id=27016779
|
data/lib/gfc64.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
class GFC64
|
7
|
+
VERSION = "0.0.2".freeze
|
8
|
+
|
9
|
+
attr_reader :rounds, :key, :block_size
|
10
|
+
|
11
|
+
def initialize(key, rounds: 3)
|
12
|
+
@key = key
|
13
|
+
@block_size = 8 # 64 bits
|
14
|
+
@rounds = rounds
|
15
|
+
end
|
16
|
+
|
17
|
+
def encrypt(number)
|
18
|
+
raise ArgumentError, 'Number must be a 64-bit integer' unless number.is_a?(Integer) && number.between?(0, 2**64 - 1)
|
19
|
+
|
20
|
+
# Split the number into two 32-bit halves
|
21
|
+
left, right = number >> 32, number & 0xFFFFFFFF
|
22
|
+
|
23
|
+
rounds.times do |round|
|
24
|
+
round_key = derive_round_key(key, round)
|
25
|
+
aes = OpenSSL::Cipher.new('AES-128-ECB').encrypt
|
26
|
+
aes.key = round_key
|
27
|
+
f = aes.update([right].pack('N')) + aes.final
|
28
|
+
left, right = right, left ^ f.unpack1('N')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Combine the two 32-bit halves back into a 64-bit number
|
32
|
+
(left << 32) | right
|
33
|
+
end
|
34
|
+
|
35
|
+
def decrypt(number)
|
36
|
+
raise ArgumentError, 'Number must be a 64-bit integer' unless number.is_a?(Integer) && number.between?(0, 2**64 - 1)
|
37
|
+
|
38
|
+
left, right = number >> 32, number & 0xFFFFFFFF
|
39
|
+
|
40
|
+
rounds.times do |round|
|
41
|
+
round_key = derive_round_key(key, rounds - round - 1)
|
42
|
+
aes = OpenSSL::Cipher.new('AES-128-ECB').encrypt
|
43
|
+
aes.key = round_key
|
44
|
+
f = aes.update([left].pack('N')) + aes.final
|
45
|
+
left, right = right ^ f.unpack1('N'), left
|
46
|
+
end
|
47
|
+
|
48
|
+
(left << 32) | right
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def derive_round_key(key, round)
|
54
|
+
# A simple round key derivation using SHA256 and the round number
|
55
|
+
OpenSSL::Digest::SHA256.digest(key + [round].pack('L'))[0...16] # Truncate to 128 bits for AES key
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gfc64
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abe Voelker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-11-03 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Hides sequential primary key counts without resorting to UUIDs or GUIDs.
|
14
|
+
email: abe@abevoelker.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- lib/gfc64.rb
|
21
|
+
homepage: https://github.com/abevoelker/gfc64
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata:
|
25
|
+
homepage_uri: https://github.com/abevoelker/gfc64
|
26
|
+
documentation_uri: https://rubydoc.info/github/abevoelker/gfc64
|
27
|
+
source_code_uri: https://github.com/abevoelker/gfc64
|
28
|
+
bug_tracker_uri: https://github.com/abevoelker/gfc64/issues
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.1.0
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.4.10
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Generalized Feistel Cipher for 64-bit integers
|
48
|
+
test_files: []
|