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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7339c2a602c6851a97eb2ae8e9a8fb0bba7acedf908cf9bdfa44b10258b6b52f
4
- data.tar.gz: dab0336c772360bdd50888e7aae4c26215f9fc8d423e1b6086322debd90f9334
3
+ metadata.gz: f9719f1d7ca53e6f2bdfb05df4d0f43e936c572ed687e841f63052c01aa0433b
4
+ data.tar.gz: bc58897ff42d8b542c03ffcc527fbfeac1c61fcc9a5074e1cc3399ab6632ea7c
5
5
  SHA512:
6
- metadata.gz: ca83d8402f7f5e9365e54fbf5b8c01d5557603701a8a9afae17f941dcbfc68f204079d391763c9d084db4e7d9c3886b4fde1e53de4f0ca6d63984cd649647ec6
7
- data.tar.gz: 5b6486223be628db1143c7b3864f510f39902cbb803a688c7891cd1933f86dffdc2c77af63654df2e827452e963ad7fa260b6ae58a69f43ba7ca8de7cd6dda6e
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 [![Gem Version](https://badge.fury.io/rb/shift_ciphers.svg)](http://badge.fury.io/rb/shift_ciphers) [![Build Status](https://travis-ci.org/TeWu/shift-ciphers.svg?branch=master)](https://travis-ci.org/TeWu/shift-ciphers)
2
- =======
3
-
4
- **Shift Ciphers** gem is simple, yet complete, implementation of [Caesar](https://en.wikipedia.org/wiki/Caesar_cipher) and [Vigenère](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher) ciphers.
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
- ... or instantiate cipher, and benefit from stored configuration info (e.g. `offset` for Caesar cipher, or `key` for Vigenère cipher):
29
-
30
- ```ruby
31
- caesar = ShiftCiphers::Caesar.new
32
- caesar.offset = 5
33
- encrypted = caesar.encrypt(plaintext)
34
- decrypted = caesar.decrypt(encrypted)
35
- decrypted == plaintext # Should be true
36
-
37
- vigenere = ShiftCiphers::Vigenere.new("my keyword")
38
- encrypted = vigenere.encrypt(plaintext)
39
- decrypted = vigenere.decrypt(encrypted)
40
- decrypted == plaintext # Should be true
41
- ```
42
-
43
- You can customize alphabet used by cipher:
44
-
45
- ```ruby
46
- plaintext = "ATTACKATDAWN"
47
- encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
48
- decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
49
- decrypted == plaintext # Should be true
50
- ```
51
-
52
- When you attempt to encrypt a string, which contains character that is not in the cipher's alphabet, then `ShiftCiphers::CipherError` is rised:
53
-
54
- ```ruby
55
- plaintext = "ATTACK!"
56
- encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
57
- # Raises ShiftCiphers::CipherError: Invalid input "ATTACK!". Character "!" is not in the alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
58
- ```
59
-
60
- You can avoid this exception by telling cipher to not 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):
61
-
62
- ```ruby
63
- plaintext = "ATTACK AT DAWN!"
64
- encrypted = ShiftCiphers::Vigenere.encrypt(plaintext, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", nonalphabet_char_strategy: :dont_encrypt)
65
- decrypted = ShiftCiphers::Vigenere.decrypt(encrypted, "KEYWORD", alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", nonalphabet_char_strategy: :dont_encrypt)
66
- puts plaintext # => ATTACK AT DAWN!
67
- puts encrypted # => KXRWQB DD HYSB!
68
- decrypted == plaintext # Should be true
69
- ```
1
+ Shift Ciphers [![Gem Version](https://badge.fury.io/rb/shift_ciphers.svg)](http://badge.fury.io/rb/shift_ciphers) [![Build Status](https://travis-ci.org/TeWu/shift-ciphers.svg?branch=master)](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
@@ -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, :encrypt)
14
- end
15
-
16
- def decrypt(ciphertext)
17
- process(ciphertext, :decrypt)
18
- end
19
-
20
- protected
21
-
22
- def process(text, direction)
23
- text.each_char.reduce("") do |ciphertext, char|
24
- char_idx = alphabet.index(char)
25
- if !char_idx.nil?
26
- rel_offset = offset * (direction == :encrypt ? 1 : -1)
27
- ciphertext << alphabet[(char_idx + rel_offset) % alphabet.size]
28
- else
29
- if nonalphabet_char_strategy == :dont_encrypt
30
- ciphertext << 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
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
@@ -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 = 2
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, :encrypt)
26
- end
27
-
28
- def decrypt(ciphertext)
29
- process(ciphertext, :decrypt)
30
- end
31
-
32
- protected
33
-
34
- def process(text, direction)
35
- key_offsets = @key_offsets.cycle
36
- text.each_char.reduce("") do |ciphertext, char|
37
- char_idx = alphabet.index(char)
38
- if !char_idx.nil?
39
- rel_offset = key_offsets.next * (direction == :encrypt ? 1 : -1)
40
- ciphertext << alphabet[(char_idx + rel_offset) % alphabet.size]
41
- else
42
- if nonalphabet_char_strategy == :dont_encrypt
43
- ciphertext << 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 validate_key(key, alphabet)
52
- key.each_char do |char|
53
- raise CipherError.new("Invalid key #{key.inspect}. Character #{char.inspect} is not in the alphabet: #{alphabet.inspect}") unless alphabet.include?(char)
54
- end
55
- end
56
-
57
- def set_key_offsets
58
- @key_offsets = @key.chars.map {|c| alphabet.index(c) }
59
- end
60
-
61
- class << self
62
- def encrypt(plaintext, key, **options)
63
- self.new(key, **options).encrypt(plaintext)
64
- end
65
-
66
- def decrypt(ciphertext, key, **options)
67
- self.new(key, **options).decrypt(ciphertext)
68
- end
69
- end
70
-
71
- end
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'
@@ -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.2
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-10 00:00:00.000000000 Z
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