aead 1.3.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.
- data/.gitignore +8 -0
- data/.travis.yml +3 -0
- data/.yardopts +2 -0
- data/Gemfile +7 -0
- data/Guardfile +10 -0
- data/LICENSE.md +22 -0
- data/README.md +57 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/aead.gemspec +34 -0
- data/ext/openssl/cipher/aead/.gitignore +4 -0
- data/ext/openssl/cipher/aead/aead.c +156 -0
- data/ext/openssl/cipher/aead/extconf.rb +6 -0
- data/lib/aead.rb +4 -0
- data/lib/aead/cipher.rb +221 -0
- data/lib/aead/cipher/aes_256_cbc_hmac_sha_256.rb +30 -0
- data/lib/aead/cipher/aes_256_ctr_hmac_sha_256.rb +30 -0
- data/lib/aead/cipher/aes_256_gcm.rb +45 -0
- data/lib/aead/cipher/aes_hmac.rb +69 -0
- data/lib/aead/nonce.rb +270 -0
- data/lib/openssl/cipher/.gitignore +0 -0
- data/spec/aead/cipher/aes_256_cbc_hmac_sha_256_spec.rb +142 -0
- data/spec/aead/cipher/aes_256_ctr_hmac_sha_256_spec.rb +142 -0
- data/spec/aead/cipher/aes_256_gcm_spec.rb +133 -0
- data/spec/aead/cipher_spec.rb +61 -0
- data/spec/aead/nonce_spec.rb +124 -0
- data/spec/spec_helper.rb +26 -0
- metadata +287 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
===============
|
3
|
+
|
4
|
+
Copyright (c) 2012 OneLogin, Inc.
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# aead #
|
2
|
+
|
3
|
+
Ruby library for generating AEAD (authenticated encryption with
|
4
|
+
associated data) ciphertexts.
|
5
|
+
|
6
|
+
## Description ##
|
7
|
+
|
8
|
+
Modern encryption best practices encourage the use of authenticated
|
9
|
+
encryption: ciphertext contents should be authenticated during the
|
10
|
+
decryption process, preventing either malicious or unintentional
|
11
|
+
silent corruption.
|
12
|
+
|
13
|
+
This library provides an extension to the Ruby OpenSSL bindings that
|
14
|
+
allows access to the GCM mode supported by OpenSSL (in versions higher
|
15
|
+
than 1.0.0). For Rubies linked against older versions of OpenSSL, a
|
16
|
+
mode is provided to perform AES-256-CTR with HMAC-SHA-256, as
|
17
|
+
[encouraged by Colin Percival](http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html).
|
18
|
+
|
19
|
+
## Getting Started ##
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'aead'
|
23
|
+
|
24
|
+
# currently, AES-256-GCM and AES-256-CTR-HMAC-SHA-256 are supported
|
25
|
+
mode = AEAD::Cipher.new('AES-256-GCM')
|
26
|
+
key = cipher.generate_key
|
27
|
+
nonce = cipher.generate_nonce
|
28
|
+
|
29
|
+
cipher = mode.new(key)
|
30
|
+
aead = cipher.encrypt(nonce, 'authentication data', 'plaintext')
|
31
|
+
plaintext = cipher.decrypt(nonce, 'authentication data', aead)
|
32
|
+
```
|
33
|
+
|
34
|
+
If any of the key, nonce, authentication data, or ciphertext has been
|
35
|
+
altered, the ciphertext will fail to decrypt and an exception will be
|
36
|
+
raised.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
cipher.decrypt(nonce, 'authentication data', aead.succ) # => ArgumentError
|
40
|
+
```
|
41
|
+
|
42
|
+
## Security Guidelines ##
|
43
|
+
|
44
|
+
Nonces should *never* be used to encrypt more than one plaintext with
|
45
|
+
the same key.
|
46
|
+
|
47
|
+
Nonces generated through the API provided by this gem are guaranteed
|
48
|
+
to be unique as long as the state file is not corrupted or
|
49
|
+
removed. The state file is located in `/var/tmp/ruby-aead`.
|
50
|
+
|
51
|
+
Nonce generation is thread-safe and tolerates being performed
|
52
|
+
simultaneously in separate processes.
|
53
|
+
|
54
|
+
If you believe you have discovered a security vulnerability in this
|
55
|
+
gem, please email `security@onelogin.com` with a description. We
|
56
|
+
follow responsible disclosure guidelines, and will work with you to
|
57
|
+
quickly find a resolution.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/version_task'
|
3
|
+
require 'yard'
|
4
|
+
|
5
|
+
task :default => %w{ build test }
|
6
|
+
|
7
|
+
task :build do
|
8
|
+
Dir.chdir('ext/openssl/cipher/aead') do
|
9
|
+
system %{ruby extconf.rb}
|
10
|
+
system %{make}
|
11
|
+
system %{cp aead.#{RbConfig::CONFIG['DLEXT']} ../../../../lib/openssl/cipher}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
t.libs.push 'lib'
|
17
|
+
t.libs.push 'spec'
|
18
|
+
|
19
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
20
|
+
t.verbose = true
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'ruby'
|
24
|
+
require 'cane/rake_task'
|
25
|
+
|
26
|
+
task :default => :cane
|
27
|
+
|
28
|
+
Cane::RakeTask.new do |t|
|
29
|
+
t.add_threshold 'coverage/coverage.txt', :>=, 100
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::VersionTask.new do |t|
|
34
|
+
t.with_git_tag = true
|
35
|
+
end
|
36
|
+
|
37
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
38
|
+
# --no-stats applies only to the `yard stats` command, so to include
|
39
|
+
# it we have to disable automatic stat generation and do it
|
40
|
+
# ourselves
|
41
|
+
t.options << '--no-stats'
|
42
|
+
t.after = lambda do
|
43
|
+
stats = YARD::CLI::Stats.new
|
44
|
+
stats.run '--list-undoc'
|
45
|
+
end
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.3.0
|
data/aead.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'aead'
|
5
|
+
gem.version = Pathname.new(__FILE__).join('../VERSION').read.chomp
|
6
|
+
|
7
|
+
gem.author = 'Stephen Touset'
|
8
|
+
gem.email = 'stephen@touset.org'
|
9
|
+
|
10
|
+
gem.homepage = 'https://github.com/onelogin/aead'
|
11
|
+
gem.summary = %{Ruby library to generate AEADs}
|
12
|
+
gem.description = %{Ruby library to generate AEADs}
|
13
|
+
|
14
|
+
gem.bindir = 'script'
|
15
|
+
gem.files = `git ls-files` .split("\n")
|
16
|
+
gem.extensions = `git ls-files -- ext/*.rb`.split("\n")
|
17
|
+
gem.executables = `git ls-files -- script/*`.split("\n").map {|e| e[7..-1] }
|
18
|
+
gem.test_files = `git ls-files -- spec/*` .split("\n")
|
19
|
+
|
20
|
+
gem.add_dependency 'macaddr', '~> 1'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'bundler'
|
23
|
+
gem.add_development_dependency 'cane'
|
24
|
+
gem.add_development_dependency 'guard'
|
25
|
+
gem.add_development_dependency 'guard-minitest'
|
26
|
+
gem.add_development_dependency 'guard-yard'
|
27
|
+
gem.add_development_dependency 'markdown'
|
28
|
+
gem.add_development_dependency 'minitest'
|
29
|
+
gem.add_development_dependency 'rake'
|
30
|
+
gem.add_development_dependency 'redcarpet'
|
31
|
+
gem.add_development_dependency 'simplecov'
|
32
|
+
gem.add_development_dependency 'yard'
|
33
|
+
gem.add_development_dependency 'version'
|
34
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <openssl/ssl.h>
|
3
|
+
#include <openssl/err.h>
|
4
|
+
|
5
|
+
VALUE dOSSL;
|
6
|
+
VALUE eCipherError;
|
7
|
+
|
8
|
+
#define GetCipherInit(obj, ctx) do { \
|
9
|
+
Data_Get_Struct((obj), EVP_CIPHER_CTX, (ctx)); \
|
10
|
+
} while (0)
|
11
|
+
|
12
|
+
#define GetCipher(obj, ctx) do { \
|
13
|
+
GetCipherInit((obj), (ctx)); \
|
14
|
+
if (!(ctx)) { \
|
15
|
+
ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
|
16
|
+
} \
|
17
|
+
} while (0)
|
18
|
+
|
19
|
+
static VALUE
|
20
|
+
ossl_make_error(VALUE exc, const char *fmt, va_list args)
|
21
|
+
{
|
22
|
+
char buf[BUFSIZ];
|
23
|
+
const char *msg;
|
24
|
+
long e;
|
25
|
+
int len = 0;
|
26
|
+
|
27
|
+
#ifdef HAVE_ERR_PEEK_LAST_ERROR
|
28
|
+
e = ERR_peek_last_error();
|
29
|
+
#else
|
30
|
+
e = ERR_peek_error();
|
31
|
+
#endif
|
32
|
+
if (fmt) {
|
33
|
+
len = vsnprintf(buf, BUFSIZ, fmt, args);
|
34
|
+
}
|
35
|
+
if (len < BUFSIZ && e) {
|
36
|
+
if (dOSSL == Qtrue) /* FULL INFO */
|
37
|
+
msg = ERR_error_string(e, NULL);
|
38
|
+
else
|
39
|
+
msg = ERR_reason_error_string(e);
|
40
|
+
len += snprintf(buf+len, BUFSIZ-len, "%s%s", (len ? ": " : ""), msg);
|
41
|
+
}
|
42
|
+
if (dOSSL == Qtrue){ /* show all errors on the stack */
|
43
|
+
while ((e = ERR_get_error()) != 0){
|
44
|
+
rb_warn("error on stack: %s", ERR_error_string(e, NULL));
|
45
|
+
}
|
46
|
+
}
|
47
|
+
ERR_clear_error();
|
48
|
+
|
49
|
+
if(len > BUFSIZ) len = rb_long2int(strlen(buf));
|
50
|
+
return rb_exc_new(exc, buf, len);
|
51
|
+
}
|
52
|
+
|
53
|
+
void
|
54
|
+
ossl_raise(VALUE exc, const char *fmt, ...)
|
55
|
+
{
|
56
|
+
va_list args;
|
57
|
+
VALUE err;
|
58
|
+
va_start(args, fmt);
|
59
|
+
err = ossl_make_error(exc, fmt, args);
|
60
|
+
va_end(args);
|
61
|
+
rb_exc_raise(err);
|
62
|
+
}
|
63
|
+
|
64
|
+
static VALUE
|
65
|
+
ossl_cipher_set_aad(VALUE self, VALUE data)
|
66
|
+
{
|
67
|
+
EVP_CIPHER_CTX *ctx;
|
68
|
+
char *in = NULL;
|
69
|
+
int in_len = 0;
|
70
|
+
int out_len = 0;
|
71
|
+
|
72
|
+
StringValue(data);
|
73
|
+
|
74
|
+
in = (unsigned char *) RSTRING_PTR(data);
|
75
|
+
in_len = RSTRING_LENINT(data);
|
76
|
+
|
77
|
+
GetCipher(self, ctx);
|
78
|
+
|
79
|
+
if (!EVP_CipherUpdate(ctx, NULL, &out_len, in, in_len))
|
80
|
+
ossl_raise(eCipherError, NULL);
|
81
|
+
|
82
|
+
return self;
|
83
|
+
}
|
84
|
+
|
85
|
+
static VALUE
|
86
|
+
ossl_cipher_get_tag(VALUE self)
|
87
|
+
{
|
88
|
+
EVP_CIPHER_CTX *ctx;
|
89
|
+
VALUE tag;
|
90
|
+
|
91
|
+
tag = rb_str_new(NULL, 16);
|
92
|
+
|
93
|
+
GetCipher(self, ctx);
|
94
|
+
|
95
|
+
#ifndef EVP_CTRL_GCM_GET_TAG
|
96
|
+
ossl_raise(eCipherError, "your version of OpenSSL doesn't support GCM");
|
97
|
+
#else
|
98
|
+
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, (unsigned char *)RSTRING_PTR(tag)))
|
99
|
+
ossl_raise(eCipherError, NULL);
|
100
|
+
#endif
|
101
|
+
|
102
|
+
return tag;
|
103
|
+
}
|
104
|
+
|
105
|
+
static VALUE
|
106
|
+
ossl_cipher_set_tag(VALUE self, VALUE data)
|
107
|
+
{
|
108
|
+
EVP_CIPHER_CTX *ctx;
|
109
|
+
char *in = NULL;
|
110
|
+
int in_len = 0;
|
111
|
+
|
112
|
+
StringValue(data);
|
113
|
+
|
114
|
+
in = (unsigned char *) RSTRING_PTR(data);
|
115
|
+
in_len = RSTRING_LENINT(data);
|
116
|
+
|
117
|
+
GetCipher(self, ctx);
|
118
|
+
|
119
|
+
#ifndef EVP_CTRL_GCM_SET_TAG
|
120
|
+
ossl_raise(eCipherError, "your version of OpenSSL doesn't support GCM");
|
121
|
+
#else
|
122
|
+
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, in_len, in))
|
123
|
+
ossl_raise(eCipherError, NULL);
|
124
|
+
#endif
|
125
|
+
|
126
|
+
return data;
|
127
|
+
}
|
128
|
+
|
129
|
+
static VALUE
|
130
|
+
ossl_cipher_verify(VALUE self)
|
131
|
+
{
|
132
|
+
EVP_CIPHER_CTX *ctx;
|
133
|
+
int out_len = 0;
|
134
|
+
|
135
|
+
GetCipher(self, ctx);
|
136
|
+
|
137
|
+
if (!EVP_CipherUpdate(ctx, NULL, &out_len, NULL, 0))
|
138
|
+
ossl_raise(eCipherError, "ciphertext failed authentication step");
|
139
|
+
|
140
|
+
return rb_str_new(0, 0);
|
141
|
+
}
|
142
|
+
|
143
|
+
void
|
144
|
+
Init_aead(void)
|
145
|
+
{
|
146
|
+
VALUE mOSSL = rb_define_module("OpenSSL");
|
147
|
+
VALUE mOSSLCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
|
148
|
+
VALUE eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError);
|
149
|
+
|
150
|
+
eCipherError = rb_define_class_under(mOSSLCipher, "CipherError", eOSSLError);
|
151
|
+
|
152
|
+
rb_define_method(mOSSLCipher, "aad=", ossl_cipher_set_aad, 1);
|
153
|
+
rb_define_method(mOSSLCipher, "gcm_tag", ossl_cipher_get_tag, 0);
|
154
|
+
rb_define_method(mOSSLCipher, "gcm_tag=", ossl_cipher_set_tag, 1);
|
155
|
+
rb_define_method(mOSSLCipher, "verify", ossl_cipher_verify, 0);
|
156
|
+
}
|
data/lib/aead.rb
ADDED
data/lib/aead/cipher.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'aead'
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'openssl/cipher/aead'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Wraps AEAD ciphers in a simplified interface.
|
9
|
+
#
|
10
|
+
class AEAD::Cipher
|
11
|
+
autoload :AES_256_GCM, 'aead/cipher/aes_256_gcm'
|
12
|
+
autoload :AES_256_CBC_HMAC_SHA_256, 'aead/cipher/aes_256_cbc_hmac_sha_256'
|
13
|
+
autoload :AES_256_CTR_HMAC_SHA_256, 'aead/cipher/aes_256_ctr_hmac_sha_256'
|
14
|
+
|
15
|
+
#
|
16
|
+
# Returns a particular Cipher implementation.
|
17
|
+
#
|
18
|
+
# @param [String] algorithm the AEAD implementation to use
|
19
|
+
# @return [Class] the cipher implementation
|
20
|
+
#
|
21
|
+
def self.new(algorithm)
|
22
|
+
# run normal Class#new if we're being called from a subclass
|
23
|
+
return super unless self == AEAD::Cipher
|
24
|
+
|
25
|
+
# TODO: ciphers should register themselves, as opposed to using a
|
26
|
+
# potentiall-unsafe const_get
|
27
|
+
self.const_get algorithm.tr('-', '_').upcase
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Returns a securely-generated key of appropriate length for the
|
32
|
+
# current Cipher.
|
33
|
+
#
|
34
|
+
# @return [String] a random key
|
35
|
+
#
|
36
|
+
def self.generate_key
|
37
|
+
SecureRandom.random_bytes(self.key_len)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns a unique nonce for the current Cipher.
|
42
|
+
#
|
43
|
+
# @return [String] a random key
|
44
|
+
#
|
45
|
+
def self.generate_nonce
|
46
|
+
AEAD::Nonce.generate
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Does a constant-time comparison between two strings. Useful to
|
51
|
+
# avoid timing attacks when comparing a generated signature against
|
52
|
+
# a user-provided signature.
|
53
|
+
#
|
54
|
+
# @param [String] left any string
|
55
|
+
# @param [String] right any string
|
56
|
+
# @return [Boolean] whether or not the strings are equal
|
57
|
+
#
|
58
|
+
def self.signature_compare(left, right)
|
59
|
+
# short-circuit if the lengths are inequal
|
60
|
+
return false if left.to_s.bytesize != right.bytesize
|
61
|
+
|
62
|
+
# Constant-time string comparison algorithm:
|
63
|
+
# 1. Break both strings into bytes
|
64
|
+
# 2. Subtract the strings from one-another, byte by byte
|
65
|
+
# (any non-equal bytes will subtract to a nonzero value)
|
66
|
+
# 3. OR the XOR'd bytes together
|
67
|
+
# 4. If the result is nonzero, the strings differed.
|
68
|
+
left = left.bytes.to_a
|
69
|
+
right = right.bytes.to_a
|
70
|
+
result = 0
|
71
|
+
|
72
|
+
left.length.times do |i|
|
73
|
+
result |= left[i] - right[i]
|
74
|
+
end
|
75
|
+
|
76
|
+
result == 0
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# The length of keys of encryption keys used by the current Cipher.
|
81
|
+
#
|
82
|
+
# @return [Integer] the length of keys in bytes
|
83
|
+
#
|
84
|
+
def key_len
|
85
|
+
self.class.key_len
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# The length of nonces used by the current Cipher.
|
90
|
+
#
|
91
|
+
# @return [Integer] the length of nonces in bytes
|
92
|
+
#
|
93
|
+
def nonce_len
|
94
|
+
self.class.nonce_len
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Encrypts a plaintext using the current Cipher.
|
99
|
+
#
|
100
|
+
# IMPORTANT: Do not ever encrypt data using the same nonce more than
|
101
|
+
# once given a particular secret key. Doing so will violate the
|
102
|
+
# security guarantees of the AEAD cipher.
|
103
|
+
#
|
104
|
+
# @param [String] nonce a unique nonce, never before used with the
|
105
|
+
# current encryption key
|
106
|
+
# @param [String, nil] aad arbitrary additional authentication data that
|
107
|
+
# must later be provided to decrypt the aead
|
108
|
+
# @param [String] plaintext arbitrary plaintext
|
109
|
+
# @return [String] the generated AEAD
|
110
|
+
#
|
111
|
+
def encrypt(nonce, aad, plaintext)
|
112
|
+
_verify_nonce_bytesize(nonce, self.nonce_len)
|
113
|
+
_verify_plaintext_presence(plaintext)
|
114
|
+
|
115
|
+
self._encrypt(
|
116
|
+
_pad_nonce(nonce),
|
117
|
+
aad,
|
118
|
+
plaintext
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Decrypts a plaintext using the current Cipher.
|
124
|
+
#
|
125
|
+
# @param [String] nonce the nonce used when encrypting the AEAD
|
126
|
+
# @param [String] aad the additional authentication data used when
|
127
|
+
# encrypting the AEAD
|
128
|
+
# @param [String] aead the encrypted AEAD
|
129
|
+
# @return [String] the original plaintext
|
130
|
+
#
|
131
|
+
def decrypt(nonce, aad, aead)
|
132
|
+
_verify_nonce_bytesize(nonce, self.nonce_len)
|
133
|
+
|
134
|
+
self._decrypt(
|
135
|
+
_pad_nonce(nonce),
|
136
|
+
aad,
|
137
|
+
_extract_ciphertext(aead, self.tag_len),
|
138
|
+
_extract_tag(aead, self.tag_len)
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
# The OpenSSL algorithm to be used by the cipher.
|
145
|
+
attr_accessor :algorithm
|
146
|
+
|
147
|
+
# The secret key provided by the user.
|
148
|
+
attr_accessor :key
|
149
|
+
|
150
|
+
#
|
151
|
+
# Initializes the cipher.
|
152
|
+
#
|
153
|
+
# @param [String] algorithm the full encryption mode to be used in
|
154
|
+
# calls to {#cipher}.
|
155
|
+
# @param [String] key the encryption key supplied by the user
|
156
|
+
# @return [Cipher] the initialized Cipher
|
157
|
+
#
|
158
|
+
def initialize(algorithm, key)
|
159
|
+
_verify_key_bytesize(key, self.key_len)
|
160
|
+
|
161
|
+
self.algorithm = algorithm.dup.freeze
|
162
|
+
self.key = key.dup.freeze
|
163
|
+
|
164
|
+
self.freeze
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
#
|
169
|
+
# Yields the {OpenSSL::Cipher} for the current {#algorithm}
|
170
|
+
#
|
171
|
+
def cipher(direction)
|
172
|
+
yield OpenSSL::Cipher.new(algorithm).send(direction)
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# The length of initialization vectors used by the current Cipher.
|
177
|
+
#
|
178
|
+
# @return [Integer] the length of initialization vectors in bytes
|
179
|
+
#
|
180
|
+
def iv_len
|
181
|
+
self.class.iv_len
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# The length of authentication tags generated by the current Cipher.
|
186
|
+
#
|
187
|
+
# @return [Integer] the length of authentication tags in bytes
|
188
|
+
#
|
189
|
+
def tag_len
|
190
|
+
self.class.tag_len
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def _verify_key_bytesize(key, key_len)
|
196
|
+
raise ArgumentError, "key must be at least #{key_len} bytes" unless
|
197
|
+
key.bytesize >= key_len
|
198
|
+
end
|
199
|
+
|
200
|
+
def _verify_nonce_bytesize(nonce, nonce_len)
|
201
|
+
raise ArgumentError, "nonce must be at least #{nonce_len} bytes" unless
|
202
|
+
nonce.bytesize == nonce_len
|
203
|
+
end
|
204
|
+
|
205
|
+
def _verify_plaintext_presence(plaintext)
|
206
|
+
raise ArgumentError, 'plaintext must not be empty' unless
|
207
|
+
not plaintext.nil? and not plaintext.empty?
|
208
|
+
end
|
209
|
+
|
210
|
+
def _pad_nonce(nonce)
|
211
|
+
nonce.rjust(self.iv_len, "\0")
|
212
|
+
end
|
213
|
+
|
214
|
+
def _extract_ciphertext(ciphertext, tag_len)
|
215
|
+
ciphertext[ 0 .. -tag_len - 1 ].to_s
|
216
|
+
end
|
217
|
+
|
218
|
+
def _extract_tag(ciphertext, tag_len)
|
219
|
+
ciphertext[ -tag_len .. -1 ].to_s
|
220
|
+
end
|
221
|
+
end
|