present-cipher 0.2.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: 36f722bbd51bc59a36df3f047589f45fc0cabec26320e8101da317292615d992
4
- data.tar.gz: 2b6d314798df04c1e8ed76c87c7ec829992e52aa4b51c26b015c2e566a607663
3
+ metadata.gz: 8d413a6ed44a1ceb352368fa7f8e7f6276514a46a92c0838e3b93af3f9ed4343
4
+ data.tar.gz: fb9b9276e79f3cb470f739b78ffcf7257c92e4cebb661396a810ea9f47ac15fb
5
5
  SHA512:
6
- metadata.gz: 29819e47113016e1486b164a519d262632384aa049bd8f77c3df2bd480fc3db073df177c4e95fa14640823a1fd2e42f2764dede07bbfbff11d2392ef72e47056
7
- data.tar.gz: 025d530a9eb07b8e09b5ecf65dd170b7c0f3312953cdbd58b7ce66c91bf1ae65639ec24523e42f8c731f140f96cb7ec516c88827cea91b1329d52532946c253b
6
+ metadata.gz: db2022abb3b908aec2e94aadf22603db90693de82c6ac2415df6f7b39e4330341b6aeac32f4857b7aa777997e73499f74aa5a34fd01465589d1da6483a1eae3a
7
+ data.tar.gz: b22482efef37294160ed4e5d3c8af74e8b137d9f5cb5184b00e54491bb848c05ed09ac62c24ac903615a04080967b28d7940b76ff05c21b67c900317fc8ef432
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
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
+
3
14
  ## [0.2.0] - 2022-11-29
4
15
 
5
16
  ### 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.
@@ -3,6 +3,9 @@ module Present
3
3
  class Error < StandardError
4
4
  end
5
5
 
6
+ class NotSupportedError < Error
7
+ end
8
+
6
9
  class KeyError < Error
7
10
  end
8
11
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Present
4
4
  class Cipher
5
- VERSION = "0.2.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
@@ -5,11 +5,13 @@ require_relative "cipher/version"
5
5
 
6
6
  module Present
7
7
  class Cipher
8
- KEY_BYTESIZE = 10
9
- KEY_BITSIZE = KEY_BYTESIZE * 8
8
+ KEY_BITSIZE_80 = 80
9
+ KEY_BITSIZE_128 = 128
10
+ BLOCK_BITSIZE = 64
10
11
 
11
- BLOCK_BYTESIZE = 8
12
- BLOCK_BITSIZE = BLOCK_BYTESIZE * 8
12
+ KEY_BYTESIZE_80 = KEY_BITSIZE_80 / 8
13
+ KEY_BYTESIZE_128 = KEY_BITSIZE_128 / 8
14
+ BLOCK_BYTESIZE = BLOCK_BITSIZE / 8
13
15
 
14
16
  S_BOX = [
15
17
  0x0C, 0x05, 0x06, 0x0b, 0x09, 0x00, 0x0a, 0x0d, 0x03, 0x0e, 0x0f, 0x08, 0x04, 0x07, 0x01, 0x02,
@@ -27,15 +29,13 @@ module Present
27
29
  INVERSE_P_BOX = 0.upto(P_BOX.length - 1).map { |n| P_BOX.index(n) }
28
30
 
29
31
  def initialize(key)
30
- bitsize = key.bytesize * 8
31
- raise KeyError, "key length is invalid (expected #{KEY_BITSIZE} bits; got #{bitsize})" if bitsize != KEY_BITSIZE
32
+ validate(key, as: :key)
32
33
 
33
- @key = key
34
+ @key = key.dup
34
35
  end
35
36
 
36
37
  def encrypt(bytes)
37
- bitsize = bytes.bytesize * 8
38
- raise BlockError, "block length is invalid (expected #{BLOCK_BITSIZE} bits; got #{bitsize})" if bitsize != BLOCK_BITSIZE
38
+ validate(bytes, as: :block)
39
39
 
40
40
  bits = bytes.unpack1("Q>")
41
41
 
@@ -51,8 +51,7 @@ module Present
51
51
  end
52
52
 
53
53
  def decrypt(bytes)
54
- bitsize = bytes.bytesize * 8
55
- raise BlockError, "block length is invalid (expected #{BLOCK_BITSIZE} bits; got #{bitsize})" if bitsize != BLOCK_BITSIZE
54
+ validate(bytes, as: :block)
56
55
 
57
56
  bits = bytes.unpack1("Q>")
58
57
 
@@ -69,27 +68,80 @@ module Present
69
68
 
70
69
  private
71
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
+
72
89
  def round_keys
73
90
  @round_keys ||= begin
74
- 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
75
101
 
76
- bits = @key.unpack1("B80").to_i(2)
102
+ def round_keys_80(key)
103
+ round_keys = []
77
104
 
78
- 1.upto(32) do |i|
79
- round_keys << (bits >> 16)
105
+ register = key.unpack1("B80").to_i(2)
80
106
 
81
- # Rotate 61 bits to the left.
82
- bits = ((bits & ((1 << 19) - 1)) << 61) | (bits >> 19)
107
+ 1.upto(32) do |i|
108
+ round_keys << (register >> 16)
83
109
 
84
- # Push the top four bits through the substitution box.
85
- 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)
86
112
 
87
- # XOR the round counter into bits 19 to 15.
88
- bits = ((bits >> 20) << 20) | ((((bits >> 15) & ((1 << 5) - 1)) ^ i) << 15) | (bits & ((1 << 15) - 1))
89
- end
113
+ # Push the leftmost four bits through the substitution box.
114
+ register = (S_BOX[(register >> 76)] << 76) | (register & ((1 << 76) - 1))
90
115
 
91
- round_keys
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))
92
118
  end
119
+
120
+ round_keys
121
+ end
122
+
123
+ def round_keys_128(key)
124
+ round_keys = []
125
+
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))
142
+ end
143
+
144
+ round_keys
93
145
  end
94
146
 
95
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.2.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-30 00:00:00.000000000 Z
11
+ date: 2022-12-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: