present-cipher 0.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4d550b256ef6e8fa10b22344cf852ebf870cc78e6e56b586dd0c04eb115a2cf
4
- data.tar.gz: 3deea46829f0af9b682ba519b015e9966ea2a93f0b1136099bc8f706f9362a21
3
+ metadata.gz: 8d413a6ed44a1ceb352368fa7f8e7f6276514a46a92c0838e3b93af3f9ed4343
4
+ data.tar.gz: fb9b9276e79f3cb470f739b78ffcf7257c92e4cebb661396a810ea9f47ac15fb
5
5
  SHA512:
6
- metadata.gz: 6d0766eb00c53da4ec27b2589c285d5ce345f790e688f8331151de56ca2d99ad89ec0fc888762e757eb5893f74985cb8a516b802e055a484d3f49da4a098b80e
7
- data.tar.gz: b78a7a4f805f175ddfda9274fd81aa9c289adf4131e5a1c6b2770ec6b8634466c303d9e7e36c3cf0b87ebe99d5d7cef4cf8f4ff2d9709d0973fb2ea1e7a0e227
6
+ metadata.gz: db2022abb3b908aec2e94aadf22603db90693de82c6ac2415df6f7b39e4330341b6aeac32f4857b7aa777997e73499f74aa5a34fd01465589d1da6483a1eae3a
7
+ data.tar.gz: b22482efef37294160ed4e5d3c8af74e8b137d9f5cb5184b00e54491bb848c05ed09ac62c24ac903615a04080967b28d7940b76ff05c21b67c900317fc8ef432
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2022-12-02
4
+
5
+ ### Added
6
+
7
+ - Support for encrypting 64-bit plaintexts using 128-bit keys.
8
+ - Support for decrypting 64-bit ciphertexts using 128-bit keys.
9
+
10
+ ### Changed
11
+
12
+ - DRYed up key and block validation.
13
+
14
+ ## [0.2.0] - 2022-11-29
15
+
16
+ ### Added
17
+
18
+ - Key and block length validation.
19
+
3
20
  ## [0.1.0] - 2022-11-26
4
21
 
5
22
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- present-cipher (0.1.0)
4
+ present-cipher (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -14,6 +14,21 @@ If bundler is not being used to manage dependencies, install the gem by executin
14
14
 
15
15
  ## Usage
16
16
 
17
+ Present::Cipher encrypts 64-bit plaintexts and decrypts 64-bit ciphertexts using 80- or 128-bit keys.
18
+
19
+ ### Generating a Key
20
+
21
+ Using `SecureRandom`:
22
+
23
+ ```
24
+ require "present/cipher"
25
+ require "securerandom"
26
+
27
+ key = SecureRandom.bytes(Present::Cipher::KEY_BYTESIZE_80) # Make sure to save this value somewhere safe!
28
+ ```
29
+
30
+ If you prefer, pass `Present::Cipher::KEY_BYTESIZE_128` to generate a 128-bit key.
31
+
17
32
  ### Encrypting
18
33
 
19
34
  ```
@@ -32,10 +47,6 @@ cipher = Present::Cipher.new(key)
32
47
  plaintext = cipher.decrypt(ciphertext)
33
48
  ```
34
49
 
35
- ### Caveats
36
-
37
- Present::Cipher currently supports only 80-bit keys and 64-bit plaintexts and ciphertexts.
38
-
39
50
  ## Development
40
51
 
41
52
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,15 @@
1
+ module Present
2
+ class Cipher
3
+ class Error < StandardError
4
+ end
5
+
6
+ class NotSupportedError < Error
7
+ end
8
+
9
+ class KeyError < Error
10
+ end
11
+
12
+ class BlockError < Error
13
+ end
14
+ end
15
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Present
4
4
  class Cipher
5
- VERSION = "0.1.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
@@ -1,11 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "cipher/error"
3
4
  require_relative "cipher/version"
4
5
 
5
6
  module Present
6
7
  class Cipher
8
+ KEY_BITSIZE_80 = 80
9
+ KEY_BITSIZE_128 = 128
10
+ BLOCK_BITSIZE = 64
11
+
12
+ KEY_BYTESIZE_80 = KEY_BITSIZE_80 / 8
13
+ KEY_BYTESIZE_128 = KEY_BITSIZE_128 / 8
14
+ BLOCK_BYTESIZE = BLOCK_BITSIZE / 8
15
+
7
16
  S_BOX = [
8
- 0x0C, 0x05, 0x06, 0x0b, 0x09, 0x00, 0x0a, 0x0d, 0x03, 0x0e, 0x0f, 0x08, 0x04, 0x07, 0x01, 0x02
17
+ 0x0C, 0x05, 0x06, 0x0b, 0x09, 0x00, 0x0a, 0x0d, 0x03, 0x0e, 0x0f, 0x08, 0x04, 0x07, 0x01, 0x02,
9
18
  ]
10
19
 
11
20
  INVERSE_S_BOX = 0.upto(S_BOX.length - 1).map { |n| S_BOX.index(n) }
@@ -20,10 +29,14 @@ module Present
20
29
  INVERSE_P_BOX = 0.upto(P_BOX.length - 1).map { |n| P_BOX.index(n) }
21
30
 
22
31
  def initialize(key)
23
- @key = key
32
+ validate(key, as: :key)
33
+
34
+ @key = key.dup
24
35
  end
25
36
 
26
37
  def encrypt(bytes)
38
+ validate(bytes, as: :block)
39
+
27
40
  bits = bytes.unpack1("Q>")
28
41
 
29
42
  0.upto(30) do |i|
@@ -38,6 +51,8 @@ module Present
38
51
  end
39
52
 
40
53
  def decrypt(bytes)
54
+ validate(bytes, as: :block)
55
+
41
56
  bits = bytes.unpack1("Q>")
42
57
 
43
58
  31.downto(1) do |i|
@@ -53,27 +68,80 @@ module Present
53
68
 
54
69
  private
55
70
 
71
+ def validate(bytes, parameters)
72
+ bitsize = bytes.bytesize * 8
73
+ aspect = parameters[:as]
74
+
75
+ case aspect
76
+ when :key
77
+ klass = KeyError
78
+ bitsizes = [KEY_BITSIZE_80, KEY_BITSIZE_128]
79
+ when :block
80
+ klass = BlockError
81
+ bitsizes = [BLOCK_BITSIZE]
82
+ else
83
+ raise ArgumentError, "aspect passed via :as must be one of :key or :block"
84
+ end
85
+
86
+ raise klass, "#{aspect} length is invalid (expected #{bitsizes.join(" or ")} bits; got #{bitsize})" unless bitsizes.include?(bitsize)
87
+ end
88
+
56
89
  def round_keys
57
90
  @round_keys ||= begin
58
- round_keys = []
91
+ case @key.bytesize
92
+ when KEY_BYTESIZE_80
93
+ round_keys_80(@key)
94
+ when KEY_BYTESIZE_128
95
+ round_keys_128(@key)
96
+ else
97
+ raise NotSupportedError
98
+ end
99
+ end
100
+ end
59
101
 
60
- bits = @key.unpack1("B80").to_i(2)
102
+ def round_keys_80(key)
103
+ round_keys = []
61
104
 
62
- 1.upto(32) do |i|
63
- round_keys << (bits >> 16)
105
+ register = key.unpack1("B80").to_i(2)
64
106
 
65
- # Rotate 61 bits to the left.
66
- bits = ((bits & ((1 << 19) - 1)) << 61) | (bits >> 19)
107
+ 1.upto(32) do |i|
108
+ round_keys << (register >> 16)
67
109
 
68
- # Push the top four bits through the substitution box.
69
- bits = (S_BOX[(bits >> 76)] << 76) | (bits & ((1 << 76) - 1))
110
+ # Rotate 61 bits to the left.
111
+ register = ((register & ((1 << 19) - 1)) << 61) | (register >> 19)
70
112
 
71
- # XOR the round counter into bits 19 to 15.
72
- bits = ((bits >> 20) << 20) | ((((bits >> 15) & ((1 << 5) - 1)) ^ i) << 15) | (bits & ((1 << 15) - 1))
73
- end
113
+ # Push the leftmost four bits through the substitution box.
114
+ register = (S_BOX[(register >> 76)] << 76) | (register & ((1 << 76) - 1))
115
+
116
+ # XOR the round counter into registers 19 through 15.
117
+ register = ((register >> 20) << 20) | ((((register >> 15) & ((1 << 5) - 1)) ^ i) << 15) | (register & ((1 << 15) - 1))
118
+ end
119
+
120
+ round_keys
121
+ end
122
+
123
+ def round_keys_128(key)
124
+ round_keys = []
74
125
 
75
- round_keys
126
+ register = key.unpack1("B128").to_i(2)
127
+
128
+ 1.upto(32) do |i|
129
+ round_keys << (register >> 64)
130
+
131
+ # Rotate 61 bits to the left.
132
+ register = ((register & ((1 << 67) - 1)) << 61) | (register >> 67)
133
+
134
+ # Push the leftmost four bits through the substitution box.
135
+ register = (S_BOX[(register >> 124)] << 124) | (register & ((1 << 124) - 1))
136
+
137
+ # Push the next leftmost four bits through the substitution box.
138
+ register = ((register >> 124) << 124) | (S_BOX[(register >> 120) & ((1 << 4) - 1)] << 120) | (register & ((1 << 120) - 1))
139
+
140
+ # XOR the round counter into registers 66 through 62.
141
+ register = ((register >> 67) << 67) | ((((register >> 62) & ((1 << 5) - 1)) ^ i) << 62) | (register & ((1 << 62) - 1))
76
142
  end
143
+
144
+ round_keys
77
145
  end
78
146
 
79
147
  def apply_round_key(bits, key)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: present-cipher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henry Phan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-26 00:00:00.000000000 Z
11
+ date: 2022-12-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -25,8 +25,8 @@ files:
25
25
  - README.md
26
26
  - Rakefile
27
27
  - lib/present/cipher.rb
28
+ - lib/present/cipher/error.rb
28
29
  - lib/present/cipher/version.rb
29
- - present-cipher.gemspec
30
30
  - sig/present/cipher.rbs
31
31
  homepage: https://codeberg.org/htp/present-cipher
32
32
  licenses:
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/present/cipher/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "present-cipher"
7
- spec.version = Present::Cipher::VERSION
8
- spec.authors = ["Henry Phan"]
9
- spec.email = ["henry@henryphan.com"]
10
-
11
- spec.summary = "A Ruby implementation of the PRESENT block cipher."
12
- spec.homepage = "https://codeberg.org/htp/present-cipher"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = ">= 2.6.0"
15
-
16
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
-
18
- spec.metadata["homepage_uri"] = spec.homepage
19
- spec.metadata["source_code_uri"] = "https://codeberg.org/htp/present-cipher"
20
- spec.metadata["changelog_uri"] = "https://codeberg.org/htp/present-cipher/blob/main/CHANGELOG.md"
21
-
22
- # Specify which files should be added to the gem when it is released.
23
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
- spec.files = Dir.chdir(__dir__) do
25
- `git ls-files -z`.split("\x0").reject do |f|
26
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
27
- end
28
- end
29
- spec.bindir = "exe"
30
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
32
- end