simply-aes 0.1.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.
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ if [ "$1" == '--about' ]; then
3
+ echo 'Validates staged changes with ruby-appraiser'
4
+ exit 0
5
+ fi
6
+
7
+ echo -e "\033[0;36mRuby Appraiser: running\033[0m"
8
+ bundle exec ruby-appraiser rubocop --mode=staged
9
+ result_code=$?
10
+ if [ $result_code -gt "0" ]; then
11
+ echo -en "\033[0;31m" # RED
12
+ echo "[✘] Ruby Appraiser found newly-created defects and "
13
+ echo " has blocked your commit."
14
+ echo " Fix the defects and commit again."
15
+ echo " To bypass, commit again with --no-verify."
16
+ echo -en "\033[0m" # RESET
17
+ exit $result_code
18
+ else
19
+ echo -en "\033[0;32m" # GREEN
20
+ echo "[✔] Ruby Appraiser ok"
21
+ echo -en "\033[0m" #RESET
22
+ fi
23
+ exit
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ if [ "$1" == '--about' ]; then
3
+ echo 'Runs project specs'
4
+ exit 0
5
+ fi
6
+
7
+ echo -e "\033[0;36mPre-commit hook: running specs...\033[0m"
8
+ bundle exec rake spec
9
+ result_code=$?
10
+ if [ $result_code -gt "0" ]; then
11
+ echo -en "\033[0;31m" # RED
12
+ echo "[✘] Specs have failed and blocked your commit."
13
+ echo " Fix the defects and try again."
14
+ echo " To bypass, commit again with --no-verify."
15
+ echo -en "\033[0m" # RESET
16
+ exit $result_code
17
+ else
18
+ echo -en "\033[0;32m" # GREEN
19
+ echo "[✔] Specs ok"
20
+ echo -en "\033[0m" #RESET
21
+ fi
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -0,0 +1,11 @@
1
+ Style/Encoding:
2
+ Exclude:
3
+ - spec/simply-aes/format_spec.rb
4
+ - spec/simply-aes/cipher_spec.rb
5
+ Style/FileName:
6
+ Exclude:
7
+ - lib/simply-aes.rb
8
+ Metrics/CyclomaticComplexity:
9
+ Severity: warning
10
+ Metrics/AbcSize:
11
+ Enabled: false
@@ -0,0 +1,7 @@
1
+ ---
2
+ language: ruby
3
+ script: "bundle exec rake spec"
4
+ rvm:
5
+ - 2.1.0
6
+ - 2.0.0
7
+ - 1.9.3
@@ -0,0 +1,31 @@
1
+ # Contributing
2
+
3
+ `SimplyAES` is [Apache-2-liennsed](LICENSE.md) and community contributions are welcome.
4
+
5
+ ## Git-Flow
6
+
7
+ `SimplyAES` follows the [git-flow][] branching model, which means that every commit on `master` is a release.
8
+ The default working branch is `develop`, so in general please keep feature pull-requests based against the current `develop`.
9
+ Hotfixes -- fixes to already-released builds -- should be based on `master` or the appropriate major version's maintenance branch.
10
+
11
+ - ensure your issue is not already addressed in an issue or pull-request
12
+ - fork simply-aes
13
+ - use the git-flow model to start your feature or hotfix
14
+ - make some commits (please include specs)
15
+ - submit a pull-request
16
+
17
+ ## Bug Reporting
18
+
19
+ Please include clear steps-to-reproduce.
20
+ Spec files are especially welcome; a failing spec can be contributed as a pull-request against `develop`.
21
+
22
+ ## Ruby Appraiser
23
+
24
+ `SimplyAES` uses the [ruby-appraiser][] gem via [pre-commit][] hook, which can be activated by installing [icefox/git-hooks][] and running `git-hooks --install` while in the repo.
25
+ Rubocop supplies strong guidelines;
26
+ use them to reduce defects as much as you can, but if you believe clarity will be sacrificed they can be bypassed with the `--no-verify` flag.
27
+
28
+ [git-flow]: http://nvie.com/posts/a-successful-git-branching-model/
29
+ [pre-commit]: .githooks/pre-commit/run-ruby-appraiser
30
+ [ruby-appraiser]: https://github.com/simplymeasured/ruby-appraiser
31
+ [icefox/git-hooks]: https://github.com/icefox/git-hooks
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simply-aes.gemspec
4
+ gemspec
@@ -0,0 +1,13 @@
1
+ Copyright 2013 Simply Measured
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,106 @@
1
+ Simply AES
2
+ ==========
3
+
4
+ Proper cryptography is easy to get wrong, and the Ruby stdlib adapters to OpenSSL's implentations of various encryption algorithms are often cumbersome to work with, expecting the developer to understand a great deal of terminology.
5
+
6
+ SimplyAES provides a simple, straight-forward interface for securely encrypting data in Ruby using key-based AES-256, the most secure variant of the [*Advanced Encryption Standard*][AES], complete with securely-generated initialisation vectors.
7
+
8
+ [AES]: http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
9
+
10
+ Installation
11
+ ------------
12
+
13
+ SimplyAES is available on rubygems.org:
14
+
15
+ `gem install simply-aes`
16
+
17
+ Basic Usage
18
+ -----------
19
+
20
+ ~~~ ruby
21
+ require 'simply-aes'
22
+
23
+ # Create a Cipher object; unless provided,
24
+ # a secure-random key will be generated.
25
+ cipher = SimplyAES.new # => <SimplyAES::Cipher:70293125591140>
26
+
27
+ # By default, the Cipher uses the Bytes formatter,
28
+ # so byte data is emitted as a string of raw bytes.
29
+ cipher.key # => "\x91\xD4\xEA=-\xC1\xB6\xE3\xDBP&\xDC\xB6\xFE\xDA\xF1\xF0L\x8Fz\e\xF7k]\x15\x9A\x9B8\xB1\xF3\xE3\xEE"
30
+
31
+ # All public methods take a `:format` option,
32
+ # which can be used to provide a format-helper;
33
+ # let's use `hex` which is easier to work with:
34
+ cipher.key(format: :hex) # => "91d4ea3d2dc1b6e3db5026dcb6fedaf1f04c8f7a1bf76b5d159a9b38b1f3e3ee"
35
+
36
+ # We can encrypt strings very easily:
37
+ secret = cipher.dump('Hello, World!', format: :hex) # => "521735c29ca1a6ae1a8fca49a9fb28ed8bf5d1bce3b39eb0286ea9c6b5dc286f"
38
+
39
+ # We can also decrypt strings; here,
40
+ # the format argument tells us how
41
+ # the ciphertext is formatted:
42
+ cipher.load(secret, format: :hex) # => 'Hello, World!'
43
+
44
+ # Attempting to load a ciphertext with an incorrect key emits an exception:
45
+ SimplyAES.new.load(secret, format: :hex) # !> SimplyAES::Cipher::LoadError
46
+ ~~~
47
+
48
+ Interoperability
49
+ ----------------
50
+
51
+ The ciphertext from SimplyAES can be decrypted easily by _any_ AES-compliant tool or library, with the following hints:
52
+
53
+ In SimplyAES, a secure-random initialisation vector is generated for each encryption unless explicitly given, and the 16-byte IV is returned with the ciphertext;
54
+ this means that two identical strings encrypted with the same key will have substantially different representations, making it harder for an attacker to correlate encrypted data.
55
+ Because the IV does not need to be kept [secret][iv-requirements], SimplyAES emits the iv+ciphertext as a single byte string.
56
+
57
+ ~~~
58
+ +-------- 16-byte IV ----------++--- unbounded payload size --->
59
+ | ||
60
+ 521735c29ca1a6ae1a8fca49a9fb28ed8bf5d1bce3b39eb0286ea9c6b5dc286f
61
+ ~~~
62
+
63
+ To decrypt AES ciphertext that has been encrypted using another library, simply prepend the IV (in the same format as the encrypted data) to the ciphertext:
64
+
65
+ ~~~ ruby
66
+ ciphertext = '8bf5d1bce3b39eb0286ea9c6b5dc286f'
67
+ iv = '521735c29ca1a6ae1a8fca49a9fb28ed'
68
+
69
+ cipher.load(iv+ciphertext, format: :hex) # => 'Hello, World!'
70
+ ~~~
71
+
72
+ To decrypt a SimplyAES iv+ciphertext payload, simply use the first 16 bytes as the IV, and the remaining as the ciphertext.
73
+
74
+ ~~~ ruby
75
+ # encoding: BINARY
76
+ key = "\x91\xD4\xEA=-\xC1\xB6\xE3\xDBP&\xDC\xB6\xFE\xDA\xF1\xF0L\x8Fz\e\xF7k]\x15\x9A\x9B8\xB1\xF3\xE3\xEE"
77
+
78
+ payload = "R\x175\xC2\x9C\xA1\xA6\xAE\x1A\x8F\xCAI\xA9\xFB(\xED\x8B\xF5\xD1\xBC\xE3\xB3\x9E\xB0(n\xA9\xC6\xB5\xDC(o"
79
+ iv = payload[0...16] # => "R\x175\xC2\x9C\xA1\xA6\xAE\x1A\x8F\xCAI\xA9\xFB(\xED"
80
+ ciphertext = payload[16..-1] # => "\x8B\xF5\xD1\xBC\xE3\xB3\x9E\xB0(n\xA9\xC6\xB5\xDC(o"
81
+
82
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
83
+ cipher.decrypt
84
+ cipher.key = key
85
+ cipher.iv = iv
86
+
87
+ decrypted = (cipher.update(ciphertext) + cipher.final) # => 'Hello, World!'
88
+ ~~~
89
+
90
+ Advanced Usage
91
+ --------------
92
+
93
+ ~~~ ruby
94
+ require 'simply-aes'
95
+
96
+ # The Cipher can be initialized with a default formatter:
97
+ cipher = SimplyAES.new(format: :base64)
98
+
99
+ # All byte-data respects the default format, unless overridden.
100
+ cipher.key # => "Okve+PUasFuqB7zONT3XgCz0adJN3a6gr58k+/rve1E="
101
+ ~~~
102
+
103
+ License
104
+ -------
105
+
106
+ SimplyAES is Apache 2 Licensed.
@@ -0,0 +1,3 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'simply-aes/version'
4
+ require 'simply-aes/cipher'
5
+
6
+ # SimplyAES provides a simple, straight-forward interface for securely
7
+ # encrypting data in Ruby using key-based AES-256, the most secure variant of
8
+ # the [*Advanced Encryption Standard*][AES], complete with securely-generated
9
+ # initialisation vectors.
10
+ module SimplyAES
11
+ # @see SimplyAES::Cipher#initialize
12
+ def self.new(*args)
13
+ Cipher.new(*args)
14
+ end
15
+ end
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+
3
+ require 'simply-aes/format'
4
+ require 'simply-aes/cipher/error'
5
+
6
+ require 'openssl'
7
+
8
+ module SimplyAES
9
+ # The Cipher is the heart of SimplyAES, and can be used to load ciphertext or
10
+ # to dump a string's ciphertext with a given key or a securely-generated one.
11
+ class Cipher
12
+ # @overload initialize(options)
13
+ # @overload initialize(key, options)
14
+ # @param key [String] a 32-byte (256-bit) string
15
+ # If not provided, a secure random key will be generated
16
+ # @param options [Hash{Symbol=>Object}]
17
+ # @option options [Symbol, SimplyAES::Format] (:bytes)
18
+ # The format is used to load provided data, including the given key,
19
+ # and as a default encoder/decoder of encrypted data; this can be
20
+ # overridden in the #load and #dump methods.
21
+ # @raise [SimplyAES::Cipher::Error]
22
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
23
+ def initialize(*args)
24
+ options = (args.last.is_a?(Hash) ? args.pop.dup : {})
25
+
26
+ @format = Format[options.delete(:format) { :bytes }]
27
+
28
+ # extract the given key, or securely generate one
29
+ @key = format.load(args.pop) unless args.empty?
30
+ @key ||= native_cipher.random_key
31
+
32
+ # validate initialisation
33
+ fail(ArgumentError, 'invalid key length') unless @key.bytesize == 32
34
+ fail(ArgumentError, 'wrong number of arguments') unless args.empty?
35
+ fail(ArgumentError, "unknown options: #{options}") unless options.empty?
36
+ rescue => err
37
+ raise Error, "failed to initialize #{self.class.name} (#{err})"
38
+ end
39
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
40
+
41
+ # @param options [Hash{Symbol=>Object}]
42
+ # @option options [Symbol] :format (default: self.format)
43
+ # @return [String] formatted string
44
+ def key(options = {})
45
+ format(options).dump(@key.dup)
46
+ end
47
+
48
+ # @param plaintext [String]
49
+ # @param options [Hash{Symbol=>Object}]
50
+ # @option options [String] :iv (default: secure random iv)
51
+ # up to 16 bytes, used as an initialisation vector
52
+ # @option options [Symbol] :format (default: self.format)
53
+ # @return iv_ciphertext [String] binary string
54
+ # @raise SimplyAES::Cipher::DumpError
55
+ def dump(plaintext, options = {})
56
+ encipher = native_cipher(:encrypt)
57
+
58
+ # ensure a 16-byte initialisation vector
59
+ iv = options.fetch(:iv) { encipher.random_iv }
60
+ fail(ArgumentError, 'iv must be 16 bytes') unless iv.bytesize == 16
61
+ encipher.iv = iv
62
+
63
+ ciphertext = encipher.update(plaintext) + encipher.final
64
+
65
+ format(options).dump(iv + ciphertext)
66
+ rescue => err
67
+ raise DumpError, err.message
68
+ end
69
+ alias_method(:encrypt, :dump)
70
+
71
+ # @param iv_ciphertext [String]
72
+ # @option options [Symbol] :format (default: self.format)
73
+ # @return plaintext [String]
74
+ # @raise SimplyAES::Cipher::LoadError
75
+ def load(iv_ciphertext, options = {})
76
+ @key || fail(ArgumentError, 'key not provided!')
77
+
78
+ # if the IV is given as an argument, inject it to the ciphertext
79
+ given_iv = options[:iv]
80
+ given_iv && (iv_ciphertext = given_iv + iv_ciphertext)
81
+
82
+ # shift the 16-byte initialisation vector from the front
83
+ iv, ciphertext = format(options).load(iv_ciphertext).unpack('a16a*')
84
+
85
+ decipher = native_cipher(:decrypt)
86
+ decipher.iv = iv
87
+
88
+ decipher.update(ciphertext) + decipher.final
89
+ rescue => err
90
+ raise LoadError, err.message
91
+ end
92
+ alias_method(:decrypt, :load)
93
+
94
+ # @api private
95
+ # @return [String]
96
+ def inspect
97
+ "<#{self.class.name}:#{__id__}>"
98
+ end
99
+
100
+ # @api private
101
+ # @return [SimplyAES::Format]
102
+ def format(options = {})
103
+ Format[options.fetch(:format) { @format }]
104
+ end
105
+
106
+ private
107
+
108
+ # Returns an AES-256-CBC OpenSSL::Cipher object pre-configured with the
109
+ # requested mode and our key; used internally in initialize, load,
110
+ # and dump.
111
+ #
112
+ # @api private
113
+ # @param mode [:encode, :decode]
114
+ # @return [OpenSSL::Cipher]
115
+ def native_cipher(mode = nil)
116
+ ::OpenSSL::Cipher.new('AES-256-CBC').tap do |cipher|
117
+ mode && cipher.public_send(mode)
118
+ @key && cipher.key = @key
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ module SimplyAES
4
+ # @see SimplyAES::Cipher
5
+ class Cipher
6
+ # SimplyAES::Cipher::Error is a wrapper for all
7
+ # errors raised by SimplyAES::Cipher
8
+ class Error < RuntimeError
9
+ # Back-port Ruby 2.1's Exception#cause
10
+ unless method_defined?(:cause)
11
+ def initialize(*args)
12
+ @cause = $! # rubocop:disable Style/SpecialGlobalVars
13
+ super
14
+ end
15
+ attr_reader :cause
16
+ end
17
+ end
18
+
19
+ LoadError = Class.new(Error)
20
+ DumpError = Class.new(Error)
21
+ end
22
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+ # rubocop:disable Style/ModuleFunction
3
+
4
+ module SimplyAES
5
+ # Implementations of SimplyAES::Format are formatting helpers,
6
+ # used by SimplyAES::Cipher to dump byte-strings and to load
7
+ # formatted byte-strings.
8
+ module Format
9
+ # @param formatted [String]
10
+ # @return bytestring [String]
11
+ def load(formatted)
12
+ return super if defined?(super)
13
+ fail(NotImplementedError)
14
+ end
15
+
16
+ # @param bytestring [String]
17
+ # @return formatted [String]
18
+ def dump(bytestring)
19
+ return super if defined?(super)
20
+ fail(NotImplementedError)
21
+ end
22
+
23
+ @implementations = {}
24
+
25
+ def self.included(implementation)
26
+ if (name = implementation.name)
27
+ # @todo support alternate-paths
28
+ short_name = name.split('::').last.downcase.to_sym
29
+ self[short_name] = implementation
30
+ end
31
+ end
32
+
33
+ def self.[]=(name, implementation)
34
+ fail(ArgumentError, "not a #{self}") unless implementation <= self
35
+ @implementations[name] = implementation
36
+ end
37
+
38
+ def self.[](name)
39
+ return name if name.is_a?(Module) && name <= self
40
+
41
+ @implementations.fetch(name) do
42
+ fail(ArgumentError, "Unknown format: #{name}")
43
+ end
44
+ end
45
+
46
+ # A Base64 implementation of SimplyAES::Format that emits
47
+ # strings *without* newlines and can handle concatenated-b64 strings
48
+ module Base64
49
+ extend self
50
+ require 'base64'
51
+ include Format
52
+
53
+ def load(formatted)
54
+ # Because Base64 has 3:4 raw:formated ratio, it doesn't always break
55
+ # cleanly on byte boundaries; add support for concatenated
56
+ # iv+ciphertext encoded payloads
57
+ formatted.scan(/[^=]+(?:=+|\Z)/m).map do |chunk|
58
+ ::Base64.decode64(chunk)
59
+ end.join
60
+ end
61
+
62
+ def dump(bytestring)
63
+ ::Base64.encode64(bytestring).tr("\n", '')
64
+ end
65
+ end
66
+
67
+ # A Hex implementation of SimplyAES::Format that emits
68
+ # strings *without* newlines and can handle concatenated-hex strings
69
+ module Hex
70
+ extend self
71
+ include Format
72
+
73
+ def load(formatted)
74
+ [formatted].pack('H*')
75
+ end
76
+
77
+ def dump(bytestring)
78
+ bytestring.unpack('H*')[0]
79
+ end
80
+ end
81
+
82
+ # The default implementation of SimplyAES::Format that reads and emits
83
+ # unformatted byte strings.
84
+ module Bytes
85
+ extend self
86
+ include Format
87
+
88
+ def load(formatted)
89
+ formatted.dup
90
+ end
91
+
92
+ def dump(bytestring)
93
+ bytestring.dup
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ # SimplyAES follows [semver](http://semver.org/).
4
+ module SimplyAES
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'simply-aes/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'simply-aes'
9
+ spec.version = SimplyAES::VERSION
10
+ spec.authors = ['Ryan Biesemeyer']
11
+ spec.email = ['ryan@simplymeasured.com']
12
+ spec.summary = 'Simple AES-256-driven encryption'
13
+ spec.homepage = 'https://github.com/simplymeasured/simply-aes'
14
+ spec.license = 'Apache 2'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec', '~> 3.0'
24
+ spec.add_development_dependency 'ruby-appraiser-rubocop'
25
+ end
@@ -0,0 +1,101 @@
1
+ # encoding: BINARY (prevent string literals from being trans-coded to UTF-8)
2
+
3
+ require 'simply-aes/cipher'
4
+
5
+ describe(SimplyAES::Cipher) do
6
+ # default keys
7
+ let(:key_hex) do
8
+ 'c3e6e6bbca5846ba4c1a4dd1953ccdc8a4e3fb0bfde7df8673de4f4f920e8df2'
9
+ end
10
+ let(:key) { SimplyAES::Format::Hex.load(key_hex) }
11
+
12
+ context '#key' do
13
+ context 'when initialised with a key' do
14
+ let(:cipher) { described_class.new(key_hex, format: :hex) }
15
+
16
+ subject { cipher }
17
+
18
+ it 'returns that key' do
19
+ expect(cipher.key).to eq(key_hex)
20
+ end
21
+ context('when a format is given') do
22
+ it 'formats the key' do
23
+ expect(cipher.key(format: :bytes)).to eq(key)
24
+ end
25
+ end
26
+ end
27
+
28
+ context 'when initialised without a key' do
29
+ let(:cipher) { described_class.new(format: :hex) }
30
+ it 'returns a securely-generated key' do
31
+ expect(cipher.key).to match(/[0-9a-f]{64}/)
32
+ end
33
+ it 'memoizes the securely-generated key' do
34
+ expect(cipher.key).to eq(cipher.key)
35
+ end
36
+ end
37
+ end
38
+
39
+ context '#dump' do
40
+ let(:cipher) { described_class.new(key_hex, format: :hex) }
41
+
42
+ # in order to make the output predictable, we need to use our own IV
43
+ context 'when an initialisation vector is given' do
44
+ let(:iv_hex) { 'babad50bea6035da71e9a0e076076860' }
45
+ let(:iv) { SimplyAES::Format::Hex.load(iv_hex) }
46
+ let(:decrypted) { 'Hello, World!' }
47
+ let(:result) { cipher.dump(decrypted, iv: iv, format: :bytes) }
48
+
49
+ context 'the encrypted value' do
50
+ subject { result }
51
+ it 'is prefixed by the IV' do
52
+ expect(result).to start_with(iv)
53
+ end
54
+ it 'is longer than the IV' do
55
+ expect(result.size).to be > (iv.size)
56
+ end
57
+ it 'matches the known value for the given key/iv pair' do
58
+ expect(result).to eq(
59
+ "\xBA\xBA\xD5\v\xEA`5\xDAq\xE9\xA0\xE0v\ah`\xE8\xCB\xF7+q\xB6" \
60
+ "\xA5\xF5\xCC\xAD\xB7\xEB\xA8\xB5s\xCD")
61
+ end
62
+ end
63
+ end
64
+
65
+ context 'dumping the same value multiple times' do
66
+ let(:decrypted) { 'Hello, World!' }
67
+
68
+ it 'emits different results, with different IVs' do
69
+ first = cipher.dump(decrypted, format: :bytes)
70
+ second = cipher.dump(decrypted, format: :bytes)
71
+
72
+ # @todo: better quantify "different"
73
+ expect(first).to_not eq(second)
74
+ end
75
+ end
76
+ end
77
+
78
+ context '#load' do
79
+ let(:cipher) { described_class.new(key_hex, format: :hex) }
80
+ let(:decrypted) { 'Hello, World!' }
81
+ let(:encrypted) do
82
+ "\xBA\xBA\xD5\v\xEA`5\xDAq\xE9\xA0\xE0v\ah`\xE8\xCB\xF7+q\xB6" \
83
+ "\xA5\xF5\xCC\xAD\xB7\xEB\xA8\xB5s\xCD"
84
+ end
85
+
86
+ context 'when given a value that had been encrypted with the same key' do
87
+ it 'decrypts the value' do
88
+ expect(cipher.load(encrypted, format: :bytes)).to eq(decrypted)
89
+ end
90
+ end
91
+ context 'when given a value that was NOT encrypted with the same key' do
92
+ let(:alternate_cipher) { SimplyAES::Cipher.new(format: :bytes) }
93
+ let(:alternate_encrypted) { alternate_cipher.dump(decrypted) }
94
+ it 'raises a SimplyAES::Cipher::LoadError' do
95
+ expect do
96
+ cipher.load(alternate_encrypted, format: :bytes)
97
+ end.to raise_exception(SimplyAES::Cipher::LoadError)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: BINARY (prevent string literals from being trans-coded to UTF-8)
2
+
3
+ require 'simply-aes/format'
4
+
5
+ shared_examples_for(SimplyAES::Format) do
6
+ let(:format) { described_class } # e.g., described_module
7
+ subject { format }
8
+ it { is_expected.to respond_to :load }
9
+ it { is_expected.to respond_to :dump }
10
+
11
+ context '#load' do
12
+ it 'loads the value' do
13
+ expect(format.load(formatted)).to eq(bytestring)
14
+ end
15
+ end
16
+
17
+ context '#dump' do
18
+ it 'dumps the value' do
19
+ expect(format.dump(bytestring)).to eq(formatted)
20
+ end
21
+ end
22
+
23
+ context 'implementation selecting' do
24
+ it 'can be loaded by name' do
25
+ expect(SimplyAES::Format[short_name]).to eq format
26
+ end
27
+ it 'can be loaded when given explicitly' do
28
+ expect(SimplyAES::Format[format]).to eq format
29
+ end
30
+ end
31
+ end
32
+
33
+ describe(SimplyAES::Format::Bytes) do
34
+ let(:formatted) { "\xff\x1c\xae" }
35
+ let(:bytestring) { "\xff\x1c\xae" }
36
+ let(:short_name) { :bytes }
37
+ it_should_behave_like(SimplyAES::Format)
38
+ end
39
+
40
+ describe(SimplyAES::Format::Base64) do
41
+ let(:formatted) { '/xyu' }
42
+ let(:bytestring) { "\xFF\x1C\xAE" }
43
+ let(:short_name) { :base64 }
44
+ it_should_behave_like(SimplyAES::Format)
45
+ end
46
+
47
+ describe(SimplyAES::Format::Hex) do
48
+ let(:formatted) { 'ff1cae' }
49
+ let(:bytestring) { "\xFF\x1C\xAE" }
50
+ let(:short_name) { :hex }
51
+ it_should_behave_like(SimplyAES::Format)
52
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simply-aes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Biesemeyer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-11-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.6'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.6'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: ruby-appraiser-rubocop
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description:
79
+ email:
80
+ - ryan@simplymeasured.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .githooks/pre-commit/run-ruby-appraiser
86
+ - .githooks/pre-commit/run-specs
87
+ - .gitignore
88
+ - .rubocop.yml
89
+ - .travis.yml
90
+ - CONTRIBUTING.md
91
+ - Gemfile
92
+ - LICENSE.md
93
+ - README.md
94
+ - Rakefile
95
+ - lib/simply-aes.rb
96
+ - lib/simply-aes/cipher.rb
97
+ - lib/simply-aes/cipher/error.rb
98
+ - lib/simply-aes/format.rb
99
+ - lib/simply-aes/version.rb
100
+ - simply-aes.gemspec
101
+ - spec/simply-aes/cipher_spec.rb
102
+ - spec/simply-aes/format_spec.rb
103
+ homepage: https://github.com/simplymeasured/simply-aes
104
+ licenses:
105
+ - Apache 2
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.23
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Simple AES-256-driven encryption
128
+ test_files:
129
+ - spec/simply-aes/cipher_spec.rb
130
+ - spec/simply-aes/format_spec.rb
131
+ has_rdoc: