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 +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +15 -4
- data/lib/present/cipher/error.rb +3 -0
- data/lib/present/cipher/version.rb +1 -1
- data/lib/present/cipher.rb +75 -23
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d413a6ed44a1ceb352368fa7f8e7f6276514a46a92c0838e3b93af3f9ed4343
|
4
|
+
data.tar.gz: fb9b9276e79f3cb470f739b78ffcf7257c92e4cebb661396a810ea9f47ac15fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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.
|
data/lib/present/cipher/error.rb
CHANGED
data/lib/present/cipher.rb
CHANGED
@@ -5,11 +5,13 @@ require_relative "cipher/version"
|
|
5
5
|
|
6
6
|
module Present
|
7
7
|
class Cipher
|
8
|
-
|
9
|
-
|
8
|
+
KEY_BITSIZE_80 = 80
|
9
|
+
KEY_BITSIZE_128 = 128
|
10
|
+
BLOCK_BITSIZE = 64
|
10
11
|
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
102
|
+
def round_keys_80(key)
|
103
|
+
round_keys = []
|
77
104
|
|
78
|
-
|
79
|
-
round_keys << (bits >> 16)
|
105
|
+
register = key.unpack1("B80").to_i(2)
|
80
106
|
|
81
|
-
|
82
|
-
|
107
|
+
1.upto(32) do |i|
|
108
|
+
round_keys << (register >> 16)
|
83
109
|
|
84
|
-
|
85
|
-
|
110
|
+
# Rotate 61 bits to the left.
|
111
|
+
register = ((register & ((1 << 19) - 1)) << 61) | (register >> 19)
|
86
112
|
|
87
|
-
|
88
|
-
|
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
|
-
|
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.
|
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
|
+
date: 2022-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|