simply-aes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: