ff1 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/spec/ff1_spec.rb ADDED
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FF1 do
4
+ it "has a version number" do
5
+ expect(FF1::VERSION).not_to be nil
6
+ end
7
+
8
+ describe FF1::Cipher do
9
+ let(:key) { "\x2B\x7E\x15\x16\x28\xAE\xD2\xA6\xAB\xF7\x15\x88\x09\xCF\x4F\x3C" }
10
+ let(:cipher) { FF1::Cipher.new(key, 10) }
11
+
12
+ describe "#initialize" do
13
+ it "accepts a valid 128-bit key" do
14
+ expect { FF1::Cipher.new(key, 10) }.not_to raise_error
15
+ end
16
+
17
+ it "accepts a valid 192-bit key" do
18
+ key_192 = "\x2B" * 24
19
+ expect { FF1::Cipher.new(key_192, 10) }.not_to raise_error
20
+ end
21
+
22
+ it "accepts a valid 256-bit key" do
23
+ key_256 = "\x2B" * 32
24
+ expect { FF1::Cipher.new(key_256, 10) }.not_to raise_error
25
+ end
26
+
27
+ it "rejects invalid key lengths" do
28
+ invalid_key = "\x2B" * 15
29
+ expect { FF1::Cipher.new(invalid_key, 10) }.to raise_error(FF1::Error, "Invalid key length")
30
+ end
31
+
32
+ it "rejects invalid radix values" do
33
+ expect { FF1::Cipher.new(key, 1) }.to raise_error(FF1::Error, "Invalid radix")
34
+ expect { FF1::Cipher.new(key, 65537) }.to raise_error(FF1::Error, "Invalid radix")
35
+ end
36
+
37
+ it "accepts valid radix values" do
38
+ expect { FF1::Cipher.new(key, 2) }.not_to raise_error
39
+ expect { FF1::Cipher.new(key, 16) }.not_to raise_error
40
+ expect { FF1::Cipher.new(key, 65536) }.not_to raise_error
41
+ end
42
+ end
43
+
44
+ describe "#encrypt and #decrypt" do
45
+ it "encrypts and decrypts correctly for numeric data" do
46
+ plaintext = "1234567890"
47
+ ciphertext = cipher.encrypt(plaintext)
48
+
49
+ expect(ciphertext).not_to eq(plaintext)
50
+ expect(ciphertext.length).to eq(plaintext.length)
51
+ expect(cipher.decrypt(ciphertext)).to eq(plaintext)
52
+ end
53
+
54
+ it "preserves the format of the data" do
55
+ plaintext = "9876543210"
56
+ ciphertext = cipher.encrypt(plaintext)
57
+
58
+ expect(ciphertext.length).to eq(plaintext.length)
59
+ ciphertext.each_char do |char|
60
+ expect(char).to match(/[0-9]/)
61
+ end
62
+ end
63
+
64
+ it "produces different ciphertext for different plaintexts" do
65
+ plaintext1 = "1234567890"
66
+ plaintext2 = "0987654321"
67
+
68
+ ciphertext1 = cipher.encrypt(plaintext1)
69
+ ciphertext2 = cipher.encrypt(plaintext2)
70
+
71
+ expect(ciphertext1).not_to eq(ciphertext2)
72
+ end
73
+
74
+ it "works with tweaks" do
75
+ plaintext = "1234567890"
76
+ tweak = "test"
77
+
78
+ ciphertext_with_tweak = cipher.encrypt(plaintext, tweak)
79
+ ciphertext_without_tweak = cipher.encrypt(plaintext)
80
+
81
+ expect(ciphertext_with_tweak).not_to eq(ciphertext_without_tweak)
82
+ expect(cipher.decrypt(ciphertext_with_tweak, tweak)).to eq(plaintext)
83
+ end
84
+
85
+ it "handles hexadecimal data with radix 16" do
86
+ hex_cipher = FF1::Cipher.new(key, 16)
87
+ plaintext = "ABCDEF123456"
88
+
89
+ ciphertext = hex_cipher.encrypt(plaintext)
90
+ expect(hex_cipher.decrypt(ciphertext)).to eq(plaintext)
91
+
92
+ ciphertext.each_char do |char|
93
+ expect(char).to match(/[0-9A-F]/)
94
+ end
95
+ end
96
+
97
+ it "raises error for input too short" do
98
+ expect { cipher.encrypt("1") }.to raise_error(FF1::Error, "Input length must be at least 2")
99
+ end
100
+
101
+ it "raises error for empty input" do
102
+ expect { cipher.encrypt("") }.to raise_error(FF1::Error, "Input cannot be empty")
103
+ end
104
+
105
+ it "raises error for nil input" do
106
+ expect { cipher.encrypt(nil) }.to raise_error(FF1::Error, "Input cannot be nil")
107
+ end
108
+
109
+ it "raises error for invalid characters" do
110
+ expect { cipher.encrypt("12A4") }.to raise_error(FF1::Error, /Invalid character/)
111
+ end
112
+
113
+ it "raises error for domain too small" do
114
+ # Create a cipher with radix 2 and input "10" -> 2^2 = 4 < 100
115
+ binary_cipher = FF1::Cipher.new(key, 2)
116
+ short_plaintext = "10"
117
+ expect { binary_cipher.encrypt(short_plaintext) }.to raise_error(FF1::Error, /Domain size too small/)
118
+ end
119
+
120
+ it "handles minimum valid domain size" do
121
+ plaintext = "12345" # 10^5 = 100,000 > 100
122
+ ciphertext = cipher.encrypt(plaintext)
123
+ expect(cipher.decrypt(ciphertext)).to eq(plaintext)
124
+ end
125
+ end
126
+
127
+ describe "consistency tests" do
128
+ it "encryption is deterministic" do
129
+ plaintext = "1234567890"
130
+
131
+ ciphertext1 = cipher.encrypt(plaintext)
132
+ ciphertext2 = cipher.encrypt(plaintext)
133
+
134
+ expect(ciphertext1).to eq(ciphertext2)
135
+ end
136
+
137
+ it "decryption is deterministic" do
138
+ plaintext = "1234567890"
139
+ ciphertext = cipher.encrypt(plaintext)
140
+
141
+ decrypted1 = cipher.decrypt(ciphertext)
142
+ decrypted2 = cipher.decrypt(ciphertext)
143
+
144
+ expect(decrypted1).to eq(decrypted2)
145
+ expect(decrypted1).to eq(plaintext)
146
+ end
147
+
148
+ it "handles various input lengths" do
149
+ ["123", "1234", "12345", "123456", "1234567", "12345678", "123456789", "1234567890"].each do |plaintext|
150
+ next if plaintext.length < 3 # Skip too short inputs
151
+
152
+ ciphertext = cipher.encrypt(plaintext)
153
+ decrypted = cipher.decrypt(ciphertext)
154
+
155
+ expect(decrypted).to eq(plaintext), "Failed for input: #{plaintext}"
156
+ expect(ciphertext.length).to eq(plaintext.length), "Length mismatch for: #{plaintext}"
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "different radix support" do
162
+ it "works with radix 2 (binary)" do
163
+ binary_cipher = FF1::Cipher.new(key, 2)
164
+ # Note: For radix 2, we need longer inputs to meet domain size requirements
165
+ # 2^7 = 128 > 100
166
+ plaintext = "1010101"
167
+
168
+ ciphertext = binary_cipher.encrypt(plaintext)
169
+ expect(binary_cipher.decrypt(ciphertext)).to eq(plaintext)
170
+
171
+ ciphertext.each_char do |char|
172
+ expect(char).to match(/[01]/)
173
+ end
174
+ end
175
+
176
+ it "works with radix 36" do
177
+ alpha_cipher = FF1::Cipher.new(key, 36)
178
+ # For testing, we'll use a simple numeric string that fits within radix 36
179
+ plaintext = "123ABC"
180
+
181
+ # Convert to proper format for radix 36 (0-9, then continue with character codes)
182
+ # For simplicity in this test, we'll just use numbers
183
+ plaintext = "123456"
184
+
185
+ ciphertext = alpha_cipher.encrypt(plaintext)
186
+ expect(alpha_cipher.decrypt(ciphertext)).to eq(plaintext)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,14 @@
1
+ require 'rspec'
2
+ require_relative '../lib/ff1'
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :rspec do |expectations|
6
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
7
+ end
8
+
9
+ config.mock_with :rspec do |mocks|
10
+ mocks.verify_partial_doubles = true
11
+ end
12
+
13
+ config.shared_context_metadata_behavior = :apply_to_host_groups
14
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ff1
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - FF1 Gem
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: A Ruby implementation of the FF1 Format Preserving Encryption algorithm
42
+ from NIST SP 800-38G
43
+ email:
44
+ - ahmed.abdelatife@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - DEMO.md
51
+ - IMPLEMENTATION.md
52
+ - LICENSE
53
+ - README.md
54
+ - examples/basic_usage.rb
55
+ - lib/ff1.rb
56
+ - lib/ff1/cipher.rb
57
+ - lib/ff1/modes.rb
58
+ - lib/ff1/version.rb
59
+ - spec/ff1_spec.rb
60
+ - spec/spec_helper.rb
61
+ homepage: https://github.com/a-abdellatif98/ff1
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.7.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.4.1
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: FF1 Format Preserving Encryption implementation
84
+ test_files: []