shift_ciphers 0.0.2 → 0.0.3
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/LICENSE +20 -20
- data/README.md +83 -69
- data/lib/shift_ciphers/alphabets.rb +11 -11
- data/lib/shift_ciphers/caesar.rb +48 -48
- data/lib/shift_ciphers/error.rb +2 -2
- data/lib/shift_ciphers/hardened_vigenere.rb +55 -0
- data/lib/shift_ciphers/version.rb +9 -9
- data/lib/shift_ciphers/vigenere.rb +76 -71
- data/lib/shift_ciphers.rb +6 -5
- data/shift_ciphers.gemspec +25 -25
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9719f1d7ca53e6f2bdfb05df4d0f43e936c572ed687e841f63052c01aa0433b
|
4
|
+
data.tar.gz: bc58897ff42d8b542c03ffcc527fbfeac1c61fcc9a5074e1cc3399ab6632ea7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e4876ec51d94454024e8caa69e7a2e36d0cf25890c74a2ff75288be68d0ea38aebe3d5234736db32251f6fbf72653df2bfc41ad45bbea02f86a6c4b2969e237
|
7
|
+
data.tar.gz: 73bc9aed8e221b46a6186375de7b3658df0661c37494fe9c33f2bea53deb74db3f77473defb732bc056f672dfe430eba11d48f90478f1c724dbe37dc2f69733c
|
data/LICENSE
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2018 Tomasz Więch
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
13
|
-
copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Tomasz Więch
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,69 +1,83 @@
|
|
1
|
-
Shift Ciphers [](http://badge.fury.io/rb/shift_ciphers) [](https://travis-ci.org/TeWu/shift-ciphers)
|
2
|
-
=======
|
3
|
-
|
4
|
-
**Shift Ciphers** gem is simple, yet complete, implementation of [Caesar]
|
5
|
-
|
6
|
-
Installation
|
7
|
-
-------
|
8
|
-
|
9
|
-
gem install shift_ciphers
|
10
|
-
|
11
|
-
Basic usage
|
12
|
-
-------
|
13
|
-
|
14
|
-
```ruby
|
15
|
-
require 'shift_ciphers'
|
16
|
-
|
17
|
-
plaintext = "Attack at dawn!"
|
18
|
-
|
19
|
-
encrypted = ShiftCiphers::Caesar.encrypt(plaintext, offset: 5)
|
20
|
-
decrypted = ShiftCiphers::Caesar.decrypt(encrypted, offset: 5)
|
21
|
-
decrypted == plaintext # Should be true
|
22
|
-
|
23
|
-
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "my keyword")
|
24
|
-
decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "my keyword")
|
25
|
-
decrypted == plaintext # Should be true
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
```
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
decrypted
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
encrypted =
|
48
|
-
decrypted =
|
49
|
-
decrypted == plaintext # Should be true
|
50
|
-
```
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
```ruby
|
55
|
-
plaintext = "
|
56
|
-
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
1
|
+
Shift Ciphers [](http://badge.fury.io/rb/shift_ciphers) [](https://travis-ci.org/TeWu/shift-ciphers)
|
2
|
+
=======
|
3
|
+
|
4
|
+
**Shift Ciphers** gem is simple, yet complete, implementation of classic [Caesar][1] and [Vigenère][2] ciphers. It also features custom, hardened version of Vigenère cipher, which uses [autokey scheme][3] and [PRNG][4]s.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
-------
|
8
|
+
|
9
|
+
gem install shift_ciphers
|
10
|
+
|
11
|
+
Basic usage
|
12
|
+
-------
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
require 'shift_ciphers'
|
16
|
+
|
17
|
+
plaintext = "Attack at dawn!"
|
18
|
+
|
19
|
+
encrypted = ShiftCiphers::Caesar.encrypt(plaintext, offset: 5) # => "Fyyfhp%fy%ifBs^"
|
20
|
+
decrypted = ShiftCiphers::Caesar.decrypt(encrypted, offset: 5) # => "Attack at dawn!"
|
21
|
+
decrypted == plaintext # Should be true
|
22
|
+
|
23
|
+
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "my keyword") # => "W!0uqS3yU=zI3H{"
|
24
|
+
decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "my keyword") # => "Attack at dawn!"
|
25
|
+
decrypted == plaintext # Should be true
|
26
|
+
|
27
|
+
encrypted = ShiftCiphers::HardenedVigenere.encrypt(plaintext, "my keyword") # => "Z6tappN^Ap[o&Ns"
|
28
|
+
decrypted = ShiftCiphers::HardenedVigenere.decrypt(encrypted, "my keyword") # => "Attack at dawn!"
|
29
|
+
decrypted == plaintext # Should be true
|
30
|
+
```
|
31
|
+
|
32
|
+
... or instantiate a cipher, and benefit from stored configuration info (e.g. `offset` for Caesar cipher, or `key` for Vigenère):
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
caesar = ShiftCiphers::Caesar.new
|
36
|
+
caesar.offset = 5
|
37
|
+
encrypted = caesar.encrypt(plaintext) # => "Fyyfhp%fy%ifBs^"
|
38
|
+
decrypted = caesar.decrypt(encrypted) # => "Attack at dawn!"
|
39
|
+
decrypted == plaintext # Should be true
|
40
|
+
|
41
|
+
vigenere = ShiftCiphers::Vigenere.new("my keyword")
|
42
|
+
encrypted = vigenere.encrypt(plaintext) # => "W!0uqS3yU=zI3H{"
|
43
|
+
decrypted = vigenere.decrypt(encrypted) # => "Attack at dawn!"
|
44
|
+
decrypted == plaintext # Should be true
|
45
|
+
|
46
|
+
strong_vigenere = ShiftCiphers::HardenedVigenere.new("my keyword")
|
47
|
+
encrypted = strong_vigenere.encrypt(plaintext) # => "Z6tappN^Ap[o&Ns"
|
48
|
+
decrypted = strong_vigenere.decrypt(encrypted) # => "Attack at dawn!"
|
49
|
+
decrypted == plaintext # Should be true
|
50
|
+
```
|
51
|
+
|
52
|
+
You can customize alphabet used by cipher:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
plaintext = "ATTACKATDAWN"
|
56
|
+
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ") # => "KXRWQBDDHYSB"
|
57
|
+
decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ") # => "ATTACKATDAWN"
|
58
|
+
decrypted == plaintext # Should be true
|
59
|
+
```
|
60
|
+
|
61
|
+
When you attempt to encrypt a string, which contains character that is not in the cipher's alphabet, then `ShiftCiphers::CipherError` is rised:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
plaintext = "ATTACK!"
|
65
|
+
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
66
|
+
# Raises ShiftCiphers::CipherError: Invalid input "ATTACK!". Character "!" is not in the alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
67
|
+
```
|
68
|
+
|
69
|
+
You can avoid this exception by telling cipher not to encrypt characters which are not in its alphabet. This is done by passing `nonalphabet_char_strategy` argument to `encrypt`/`decrypt` class method (or by using `nonalphabet_char_strategy=` instance method):
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
plaintext = "ATTACK AT DAWN!"
|
73
|
+
encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", nonalphabet_char_strategy: :dont_encrypt)
|
74
|
+
decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", nonalphabet_char_strategy: :dont_encrypt)
|
75
|
+
puts plaintext # => ATTACK AT DAWN!
|
76
|
+
puts encrypted # => KXRWQB DD HYSB!
|
77
|
+
decrypted == plaintext # Should be true
|
78
|
+
```
|
79
|
+
|
80
|
+
[1]: https://en.wikipedia.org/wiki/Caesar_cipher
|
81
|
+
[2]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
|
82
|
+
[3]: https://en.wikipedia.org/wiki/Autokey_cipher
|
83
|
+
[4]: https://en.wikipedia.org/wiki/Pseudorandom_number_generator
|
@@ -1,12 +1,12 @@
|
|
1
|
-
module ShiftCiphers
|
2
|
-
module Alphabets
|
3
|
-
NUMS = (0..9).to_a.map(&:to_s).join
|
4
|
-
LOWER_ALPHA = ('a'..'z').to_a.join
|
5
|
-
UPPER_ALPHA = ('A'..'Z').to_a.join
|
6
|
-
ALPHA = LOWER_ALPHA + UPPER_ALPHA
|
7
|
-
ALPHANUMS = NUMS + ALPHA
|
8
|
-
SPECIAL = " !@#$%^&*()-_=+{}[];:'\",./<>?"
|
9
|
-
|
10
|
-
DEFAULT = ALPHANUMS + SPECIAL
|
11
|
-
end
|
1
|
+
module ShiftCiphers
|
2
|
+
module Alphabets
|
3
|
+
NUMS = (0..9).to_a.map(&:to_s).join
|
4
|
+
LOWER_ALPHA = ('a'..'z').to_a.join
|
5
|
+
UPPER_ALPHA = ('A'..'Z').to_a.join
|
6
|
+
ALPHA = LOWER_ALPHA + UPPER_ALPHA
|
7
|
+
ALPHANUMS = NUMS + ALPHA
|
8
|
+
SPECIAL = " !@#$%^&*()-_=+{}[];:'\",./<>?"
|
9
|
+
|
10
|
+
DEFAULT = ALPHANUMS + SPECIAL
|
11
|
+
end
|
12
12
|
end
|
data/lib/shift_ciphers/caesar.rb
CHANGED
@@ -1,49 +1,49 @@
|
|
1
|
-
module ShiftCiphers
|
2
|
-
class Caesar
|
3
|
-
DEFAULT_OFFSET = 13
|
4
|
-
attr_accessor :offset, :alphabet, :nonalphabet_char_strategy
|
5
|
-
|
6
|
-
def initialize(offset: DEFAULT_OFFSET, alphabet: Alphabets::DEFAULT, nonalphabet_char_strategy: :error)
|
7
|
-
@offset = offset
|
8
|
-
@alphabet = alphabet
|
9
|
-
@nonalphabet_char_strategy = nonalphabet_char_strategy
|
10
|
-
end
|
11
|
-
|
12
|
-
def encrypt(plaintext)
|
13
|
-
process(plaintext,
|
14
|
-
end
|
15
|
-
|
16
|
-
def decrypt(ciphertext)
|
17
|
-
process(ciphertext,
|
18
|
-
end
|
19
|
-
|
20
|
-
protected
|
21
|
-
|
22
|
-
def process(text,
|
23
|
-
text.each_char.reduce("") do |
|
24
|
-
char_idx = alphabet.index(char)
|
25
|
-
if !char_idx.nil?
|
26
|
-
rel_offset = offset * (
|
27
|
-
|
28
|
-
else
|
29
|
-
if nonalphabet_char_strategy == :dont_encrypt
|
30
|
-
|
31
|
-
else
|
32
|
-
raise CipherError.new("Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class << self
|
39
|
-
def encrypt(plaintext, **options)
|
40
|
-
self.new(**options).encrypt(plaintext)
|
41
|
-
end
|
42
|
-
|
43
|
-
def decrypt(ciphertext, **options)
|
44
|
-
self.new(**options).decrypt(ciphertext)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
1
|
+
module ShiftCiphers
|
2
|
+
class Caesar
|
3
|
+
DEFAULT_OFFSET = 13
|
4
|
+
attr_accessor :offset, :alphabet, :nonalphabet_char_strategy
|
5
|
+
|
6
|
+
def initialize(offset: DEFAULT_OFFSET, alphabet: Alphabets::DEFAULT, nonalphabet_char_strategy: :error)
|
7
|
+
@offset = offset
|
8
|
+
@alphabet = alphabet
|
9
|
+
@nonalphabet_char_strategy = nonalphabet_char_strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
def encrypt(plaintext)
|
13
|
+
process(plaintext, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt(ciphertext)
|
17
|
+
process(ciphertext, false)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def process(text, encrypting = true)
|
23
|
+
text.each_char.reduce("") do |result, char|
|
24
|
+
char_idx = alphabet.index(char)
|
25
|
+
if !char_idx.nil?
|
26
|
+
rel_offset = offset * (encrypting ? 1 : -1)
|
27
|
+
result << alphabet[(char_idx + rel_offset) % alphabet.size]
|
28
|
+
else
|
29
|
+
if nonalphabet_char_strategy == :dont_encrypt
|
30
|
+
result << char
|
31
|
+
else
|
32
|
+
raise CipherError.new("Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def encrypt(plaintext, **options)
|
40
|
+
self.new(**options).encrypt(plaintext)
|
41
|
+
end
|
42
|
+
|
43
|
+
def decrypt(ciphertext, **options)
|
44
|
+
self.new(**options).decrypt(ciphertext)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
49
|
end
|
data/lib/shift_ciphers/error.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module ShiftCiphers
|
2
|
-
class CipherError < StandardError; end
|
1
|
+
module ShiftCiphers
|
2
|
+
class CipherError < StandardError; end
|
3
3
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ShiftCiphers
|
2
|
+
class HardenedVigenere < Vigenere
|
3
|
+
|
4
|
+
def initialize(key, alphabet: Alphabets::DEFAULT, nonalphabet_char_strategy: :error)
|
5
|
+
validate_key(key, alphabet)
|
6
|
+
@key = key
|
7
|
+
@alphabet = alphabet
|
8
|
+
@nonalphabet_char_strategy = nonalphabet_char_strategy
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def process(text, encrypting = true)
|
14
|
+
offsets_stream = create_offsets_stream
|
15
|
+
plaintext_char = ""
|
16
|
+
text.each_char.reduce("") do |result, char|
|
17
|
+
char_idx = alphabet.index(char)
|
18
|
+
if !char_idx.nil?
|
19
|
+
rel_offset = offsets_stream.next(plaintext_char) * (encrypting ? 1 : -1)
|
20
|
+
result_char = alphabet[(char_idx + rel_offset) % alphabet.size]
|
21
|
+
plaintext_char = encrypting ? char : result_char
|
22
|
+
result << result_char
|
23
|
+
else
|
24
|
+
if nonalphabet_char_strategy == :dont_encrypt
|
25
|
+
result << char
|
26
|
+
else
|
27
|
+
raise CipherError.new("Invalid input #{text.inspect}. Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_offsets_stream
|
34
|
+
RandomOffsetsStream.new(key, alphabet.size - 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class RandomOffsetsStream
|
39
|
+
SEEDING_RAND_MAX = 2**32-1
|
40
|
+
|
41
|
+
def initialize(key, max)
|
42
|
+
@random = key.bytes.reduce(Random.new(0)) do |random, byte|
|
43
|
+
Random.new(random.rand(SEEDING_RAND_MAX) ^ byte)
|
44
|
+
end
|
45
|
+
@max = max
|
46
|
+
end
|
47
|
+
|
48
|
+
def next(plaintext_char)
|
49
|
+
plaintext_byte = plaintext_char.bytes.reduce(0){|a,e| a ^ e}
|
50
|
+
@random = Random.new(@random.rand(SEEDING_RAND_MAX) ^ plaintext_byte)
|
51
|
+
@random.rand(@max)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
module ShiftCiphers
|
2
|
-
module Version
|
3
|
-
MAJOR = 0
|
4
|
-
MINOR = 0
|
5
|
-
PATCH =
|
6
|
-
LABEL = nil
|
7
|
-
end
|
8
|
-
|
9
|
-
VERSION = ([Version::MAJOR, Version::MINOR, Version::PATCH, Version::LABEL].compact * '.').freeze
|
1
|
+
module ShiftCiphers
|
2
|
+
module Version
|
3
|
+
MAJOR = 0
|
4
|
+
MINOR = 0
|
5
|
+
PATCH = 3
|
6
|
+
LABEL = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
VERSION = ([Version::MAJOR, Version::MINOR, Version::PATCH, Version::LABEL].compact * '.').freeze
|
10
10
|
end
|
@@ -1,72 +1,77 @@
|
|
1
|
-
module ShiftCiphers
|
2
|
-
class Vigenere
|
3
|
-
attr_accessor :key, :alphabet, :nonalphabet_char_strategy
|
4
|
-
|
5
|
-
def initialize(key, alphabet: Alphabets::DEFAULT, nonalphabet_char_strategy: :error)
|
6
|
-
validate_key(key, alphabet)
|
7
|
-
@key = key
|
8
|
-
@alphabet = alphabet
|
9
|
-
@nonalphabet_char_strategy = nonalphabet_char_strategy
|
10
|
-
set_key_offsets
|
11
|
-
end
|
12
|
-
|
13
|
-
def key=(key)
|
14
|
-
validate_key(key, alphabet)
|
15
|
-
@key = key
|
16
|
-
set_key_offsets
|
17
|
-
end
|
18
|
-
|
19
|
-
def alphabet=(alphabet)
|
20
|
-
validate_key(key, alphabet)
|
21
|
-
@alphabet = alphabet
|
22
|
-
end
|
23
|
-
|
24
|
-
def encrypt(plaintext)
|
25
|
-
process(plaintext,
|
26
|
-
end
|
27
|
-
|
28
|
-
def decrypt(ciphertext)
|
29
|
-
process(ciphertext,
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
|
34
|
-
def process(text,
|
35
|
-
|
36
|
-
text.each_char.reduce("") do |
|
37
|
-
char_idx = alphabet.index(char)
|
38
|
-
if !char_idx.nil?
|
39
|
-
rel_offset =
|
40
|
-
|
41
|
-
else
|
42
|
-
if nonalphabet_char_strategy == :dont_encrypt
|
43
|
-
|
44
|
-
else
|
45
|
-
raise CipherError.new("Invalid input #{text.inspect}. Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def
|
52
|
-
key.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
1
|
+
module ShiftCiphers
|
2
|
+
class Vigenere
|
3
|
+
attr_accessor :key, :alphabet, :nonalphabet_char_strategy
|
4
|
+
|
5
|
+
def initialize(key, alphabet: Alphabets::DEFAULT, nonalphabet_char_strategy: :error)
|
6
|
+
validate_key(key, alphabet)
|
7
|
+
@key = key
|
8
|
+
@alphabet = alphabet
|
9
|
+
@nonalphabet_char_strategy = nonalphabet_char_strategy
|
10
|
+
set_key_offsets
|
11
|
+
end
|
12
|
+
|
13
|
+
def key=(key)
|
14
|
+
validate_key(key, alphabet)
|
15
|
+
@key = key
|
16
|
+
set_key_offsets
|
17
|
+
end
|
18
|
+
|
19
|
+
def alphabet=(alphabet)
|
20
|
+
validate_key(key, alphabet)
|
21
|
+
@alphabet = alphabet
|
22
|
+
end
|
23
|
+
|
24
|
+
def encrypt(plaintext)
|
25
|
+
process(plaintext, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
def decrypt(ciphertext)
|
29
|
+
process(ciphertext, false)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def process(text, encrypting = true)
|
35
|
+
offsets_stream = create_offsets_stream
|
36
|
+
text.each_char.reduce("") do |result, char|
|
37
|
+
char_idx = alphabet.index(char)
|
38
|
+
if !char_idx.nil?
|
39
|
+
rel_offset = offsets_stream.next * (encrypting ? 1 : -1)
|
40
|
+
result << alphabet[(char_idx + rel_offset) % alphabet.size]
|
41
|
+
else
|
42
|
+
if nonalphabet_char_strategy == :dont_encrypt
|
43
|
+
result << char
|
44
|
+
else
|
45
|
+
raise CipherError.new("Invalid input #{text.inspect}. Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_key_offsets
|
52
|
+
@key_offsets = @key.chars.map {|c| alphabet.index(c) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_offsets_stream
|
56
|
+
@key_offsets.cycle
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_key(key, alphabet)
|
60
|
+
key.each_char do |char|
|
61
|
+
raise CipherError.new("Invalid key #{key.inspect}. Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}") unless alphabet.include?(char)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class << self
|
67
|
+
def encrypt(plaintext, key, **options)
|
68
|
+
self.new(key, **options).encrypt(plaintext)
|
69
|
+
end
|
70
|
+
|
71
|
+
def decrypt(ciphertext, key, **options)
|
72
|
+
self.new(key, **options).decrypt(ciphertext)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
72
77
|
end
|
data/lib/shift_ciphers.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'shift_ciphers/version'
|
2
|
-
require 'shift_ciphers/alphabets'
|
3
|
-
require 'shift_ciphers/error'
|
4
|
-
require 'shift_ciphers/caesar'
|
5
|
-
require 'shift_ciphers/vigenere'
|
1
|
+
require 'shift_ciphers/version'
|
2
|
+
require 'shift_ciphers/alphabets'
|
3
|
+
require 'shift_ciphers/error'
|
4
|
+
require 'shift_ciphers/caesar'
|
5
|
+
require 'shift_ciphers/vigenere'
|
6
|
+
require 'shift_ciphers/hardened_vigenere'
|
data/shift_ciphers.gemspec
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
|
3
|
-
lib = File.expand_path('../lib', __FILE__)
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require 'shift_ciphers/version'
|
6
|
-
|
7
|
-
Gem::Specification.new do |s|
|
8
|
-
s.name = "shift_ciphers"
|
9
|
-
s.version = ShiftCiphers::VERSION
|
10
|
-
s.authors = ["Tomasz Więch"]
|
11
|
-
s.email = ["tewu.dev@gmail.com"]
|
12
|
-
|
13
|
-
s.summary = "Implementation of Caesar and Vigenere ciphers"
|
14
|
-
s.description = "Implementation of Caesar and Vigenere ciphers"
|
15
|
-
s.homepage = "https://github.com/TeWu/shift-ciphers"
|
16
|
-
s.license = "MIT"
|
17
|
-
|
18
|
-
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(doc|test|spec|features|bin)/|Rakefile|Gemfile*|CHANGELOG}) || f.start_with?('.') }
|
19
|
-
s.require_paths = ["lib"]
|
20
|
-
|
21
|
-
s.required_ruby_version = ">= 2.1.0"
|
22
|
-
|
23
|
-
s.add_development_dependency "bundler", "~> 1.16"
|
24
|
-
s.add_development_dependency "pry", "~> 0.11"
|
25
|
-
s.add_development_dependency "rspec", "~> 3.8"
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'shift_ciphers/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "shift_ciphers"
|
9
|
+
s.version = ShiftCiphers::VERSION
|
10
|
+
s.authors = ["Tomasz Więch"]
|
11
|
+
s.email = ["tewu.dev@gmail.com"]
|
12
|
+
|
13
|
+
s.summary = "Implementation of Caesar and Vigenere ciphers"
|
14
|
+
s.description = "Implementation of Caesar and Vigenere ciphers"
|
15
|
+
s.homepage = "https://github.com/TeWu/shift-ciphers"
|
16
|
+
s.license = "MIT"
|
17
|
+
|
18
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(doc|test|spec|features|bin)/|Rakefile|Gemfile*|CHANGELOG}) || f.start_with?('.') }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.required_ruby_version = ">= 2.1.0"
|
22
|
+
|
23
|
+
s.add_development_dependency "bundler", "~> 1.16"
|
24
|
+
s.add_development_dependency "pry", "~> 0.11"
|
25
|
+
s.add_development_dependency "rspec", "~> 3.8"
|
26
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shift_ciphers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomasz Więch
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- lib/shift_ciphers/alphabets.rb
|
66
66
|
- lib/shift_ciphers/caesar.rb
|
67
67
|
- lib/shift_ciphers/error.rb
|
68
|
+
- lib/shift_ciphers/hardened_vigenere.rb
|
68
69
|
- lib/shift_ciphers/version.rb
|
69
70
|
- lib/shift_ciphers/vigenere.rb
|
70
71
|
- shift_ciphers.gemspec
|