enmail 0.1.0 → 0.2.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.
- checksums.yaml +5 -5
- data/.editorconfig +21 -0
- data/.gitignore +164 -9
- data/.gitmodules +3 -0
- data/.rubocop.yml +26 -627
- data/.travis.yml +86 -3
- data/.yardopts +6 -0
- data/Gemfile +10 -1
- data/LICENSE.txt +21 -0
- data/README.adoc +150 -0
- data/Rakefile +119 -0
- data/bin/rspec +1 -0
- data/bin/setup +18 -1
- data/ci/install_botan.sh +28 -0
- data/ci/install_json_c.sh +32 -0
- data/ci/install_rnp.sh +31 -0
- data/docs/GPGMEAdapter.adoc +68 -0
- data/docs/RNPAdapter.adoc +45 -0
- data/enmail.gemspec +21 -8
- data/lib/enmail.rb +25 -4
- data/lib/enmail/adapters/base.rb +14 -0
- data/lib/enmail/adapters/gpgme.rb +81 -0
- data/lib/enmail/adapters/gpgme_requirements.rb +3 -0
- data/lib/enmail/adapters/rnp.rb +109 -0
- data/lib/enmail/adapters/rnp_requirements.rb +3 -0
- data/lib/enmail/dependency_constraints.rb +9 -0
- data/lib/enmail/extensions/message_transport_encoding_restrictions.rb +20 -0
- data/lib/enmail/helpers/message_manipulation.rb +73 -0
- data/lib/enmail/helpers/rfc1847.rb +103 -0
- data/lib/enmail/helpers/rfc3156.rb +60 -0
- data/lib/enmail/version.rb +4 -1
- metadata +99 -24
- data/README.md +0 -115
- data/lib/enmail/certificate_finder.rb +0 -75
- data/lib/enmail/config.rb +0 -21
- data/lib/enmail/configuration.rb +0 -80
- data/lib/enmail/enmailable.rb +0 -43
- data/lib/enmail/key.rb +0 -53
- data/lib/enmail/mail_ext/message.rb +0 -18
- data/lib/mail/secure/mail_interceptors/pgp.rb +0 -53
- data/lib/mail/secure/models/key.rb +0 -5
- data/lib/mail/secure/pgp_mailable.rb +0 -107
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# (c) Copyright 2018 Ribose Inc.
|
4
|
+
#
|
5
|
+
|
6
|
+
# Based on:
|
7
|
+
# https://github.com/riboseinc/ruby-rnp/blob/52d6113458cb095cf7811/ci/install.sh
|
8
|
+
|
9
|
+
set -eux
|
10
|
+
|
11
|
+
: "${CORES:=2}"
|
12
|
+
: "${MAKE:=make}"
|
13
|
+
|
14
|
+
jsonc_build="${DEPS_BUILD_DIR}/json-c"
|
15
|
+
|
16
|
+
if [ ! -e "${JSONC_PREFIX}/lib/libjson-c.so" ] && \
|
17
|
+
[ ! -e "${JSONC_PREFIX}/lib/libjson-c.dylib" ]; then
|
18
|
+
|
19
|
+
if [ -d "${jsonc_build}" ]; then
|
20
|
+
rm -rf "${jsonc_build}"
|
21
|
+
fi
|
22
|
+
|
23
|
+
mkdir -p "${jsonc_build}"
|
24
|
+
pushd ${jsonc_build}
|
25
|
+
wget https://s3.amazonaws.com/json-c_releases/releases/json-c-0.12.1.tar.gz -O json-c.tar.gz
|
26
|
+
tar xzf json-c.tar.gz --strip 1
|
27
|
+
|
28
|
+
autoreconf -ivf
|
29
|
+
env CFLAGS="-fno-omit-frame-pointer -g" ./configure --prefix="${JSONC_PREFIX}"
|
30
|
+
${MAKE} -j${CORES} install
|
31
|
+
popd
|
32
|
+
fi
|
data/ci/install_rnp.sh
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# (c) Copyright 2018 Ribose Inc.
|
4
|
+
#
|
5
|
+
|
6
|
+
# Based on:
|
7
|
+
# https://github.com/riboseinc/ruby-rnp/blob/52d6113458cb095cf7811/ci/install.sh
|
8
|
+
|
9
|
+
set -eux
|
10
|
+
|
11
|
+
: "${CORES:=2}"
|
12
|
+
: "${MAKE:=make}"
|
13
|
+
|
14
|
+
rnp_build="${DEPS_BUILD_DIR}/rnp"
|
15
|
+
|
16
|
+
if [ ! -e "${RNP_PREFIX}/lib/librnp.so" ] && \
|
17
|
+
[ ! -e "${RNP_PREFIX}/lib/librnp.dylib" ]; then
|
18
|
+
|
19
|
+
git clone https://github.com/riboseinc/rnp ${rnp_build}
|
20
|
+
pushd "${rnp_build}"
|
21
|
+
git checkout "$RNP_VERSION"
|
22
|
+
cmake \
|
23
|
+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
24
|
+
-DBUILD_SHARED_LIBS=yes \
|
25
|
+
-DBUILD_TESTING=no \
|
26
|
+
-DCMAKE_PREFIX_PATH="${BOTAN_PREFIX};${JSONC_PREFIX}" \
|
27
|
+
-DCMAKE_INSTALL_PREFIX="${RNP_PREFIX}" \
|
28
|
+
.
|
29
|
+
${MAKE} -j${CORES} install
|
30
|
+
popd
|
31
|
+
fi
|
@@ -0,0 +1,68 @@
|
|
1
|
+
= GPGME Adapter Guide
|
2
|
+
|
3
|
+
`GPGME` adapter provides OpenPGP-compliant cryptography via
|
4
|
+
https://gnupg.org/software/gpgme/index.html[GnuPG Made Easy] library.
|
5
|
+
|
6
|
+
== Dependencies
|
7
|
+
|
8
|
+
This adapter requries two additional pieces of software to be installed:
|
9
|
+
|
10
|
+
1. GnuPG, version 2.2
|
11
|
+
2. `https://rubygems.org/gems/gpgme[gpgme]` gem
|
12
|
+
|
13
|
+
== Options
|
14
|
+
|
15
|
+
Following adapter-specific options are supported:
|
16
|
+
|
17
|
+
`signer`::
|
18
|
+
Optional. User id or e-mail which identifies key which will be used for message
|
19
|
+
signing. By default, first address from mail's From field is used.
|
20
|
+
`key_password`::
|
21
|
+
Optional. Password for signer's key. Must be a string.
|
22
|
+
|
23
|
+
== Non-standard home directory location
|
24
|
+
|
25
|
+
GnuPG home directory is a place where configuration, keyrings, etc. are stored.
|
26
|
+
By default, GnuPG home directory is located in `$HOME/.gnupg`. You can change
|
27
|
+
it in a following way:
|
28
|
+
|
29
|
+
[source,ruby]
|
30
|
+
----
|
31
|
+
::GPGME::Engine.home_dir = 'path/to/home_dir'
|
32
|
+
----
|
33
|
+
|
34
|
+
Be advised that this setting is global. Hence, if you use GPGME outside EnMail
|
35
|
+
as well, your other logic will be affected. One possible workaround is to sign
|
36
|
+
e-mails in a different process. This should be fairly easy to achieve in Rails,
|
37
|
+
as mailing is often handed to some kind of background job processor which runs
|
38
|
+
in its own process. Nevertheless, consider switching to RNP adapter if this
|
39
|
+
limitation poses a problem.
|
40
|
+
|
41
|
+
== Using `gpg.conf`
|
42
|
+
|
43
|
+
GPGME API accepts little configuration options. Instead, it reads preferences
|
44
|
+
from a `gpg.conf` file located in GnuPG home directory (usually `$HOME/.gnupg`).
|
45
|
+
You may override defaults there, i.e. set preferred keys or algorithms.
|
46
|
+
Refer to GnuPG documentation for
|
47
|
+
https://www.gnupg.org/documentation/manuals/gnupg/GPG-Configuration.html[more
|
48
|
+
information about configuration files], or for
|
49
|
+
https://www.gnupg.org/documentation/manuals/gnupg/GPG-Options.html[list of
|
50
|
+
available options]. Also, you will find some nice example `gpg.conf` in this
|
51
|
+
https://stackoverflow.com/a/34923350/304175[Stack Overflow answer].
|
52
|
+
|
53
|
+
== Native extensions
|
54
|
+
|
55
|
+
The `gpgme` gem includes C extensions.
|
56
|
+
|
57
|
+
== Issue tracker
|
58
|
+
|
59
|
+
Bugs, feature requests, and other issues are tracked with `adapter: gpgme`
|
60
|
+
label: https://github.com/riboseinc/enmail/issues?q=is%3Aissue+is%3Aopen+label%3A%22adapter%3A+gpgme%22
|
61
|
+
|
62
|
+
== External links
|
63
|
+
|
64
|
+
* https://tools.ietf.org/html/rfc1847[RFC 1847 "Security Multiparts for MIME"]
|
65
|
+
* https://tools.ietf.org/html/rfc3156[RFC 3156 "MIME Security with OpenPGP"]
|
66
|
+
* https://gnupg.org[GNU Privacy Guard home site]
|
67
|
+
* https://gnupg.org/software/gpgme/index.html[GPGME (GnuPG Made Easy) library home site]
|
68
|
+
* https://github.com/ueno/ruby-gpgme[Ruby bindings for GPGME library]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
= RNP Adapter Guide
|
2
|
+
|
3
|
+
`RNP` adapter provides OpenPGP-compliant cryptography via
|
4
|
+
https://www.rnpgp.com/[RNP] library.
|
5
|
+
|
6
|
+
== Dependencies
|
7
|
+
|
8
|
+
This adapter requries two additional pieces of software to be installed:
|
9
|
+
|
10
|
+
1. RNP library, version 0.9.2 or newer
|
11
|
+
2. `https://rubygems.org/gems/rnp[rnp]` gem, version 1.0.1 or newer
|
12
|
+
|
13
|
+
== Options
|
14
|
+
|
15
|
+
Following adapter-specific options are supported:
|
16
|
+
|
17
|
+
`homedir`::
|
18
|
+
Optional. Path to RNP home directory, which contains public and secret
|
19
|
+
keyrings. In most situations, RNP is able to read GnuPG home directories,
|
20
|
+
hence it's common to set it to `<your_home_directory>/.gpg`. Defaults to
|
21
|
+
`<your_home_directory>/.rnp`.
|
22
|
+
`signer`::
|
23
|
+
Optional. User id or e-mail which identifies key which will be used for message
|
24
|
+
signing. By default, first address from mail's From field is used.
|
25
|
+
`key_password`::
|
26
|
+
Optional. Password for signer's key. Can be a string or proc, see
|
27
|
+
`rnp` gem documentation for `Rnp#password_provider=`.
|
28
|
+
|
29
|
+
== Native extensions
|
30
|
+
|
31
|
+
The `rnp` gem depends on `https://github.com/ffi/ffi[ffi]` gem, which includes
|
32
|
+
native extensions.
|
33
|
+
|
34
|
+
== Issue tracker
|
35
|
+
|
36
|
+
Bugs, feature requests, and other issues are tracked with `adapter: rnp`
|
37
|
+
label: https://github.com/riboseinc/enmail/issues?q=is%3Aissue+is%3Aopen+label%3A%22adapter%3A+rnp%22
|
38
|
+
|
39
|
+
== External links
|
40
|
+
|
41
|
+
* https://tools.ietf.org/html/rfc1847[RFC 1847 "Security Multiparts for MIME"]
|
42
|
+
* https://tools.ietf.org/html/rfc3156[RFC 3156 "MIME Security with OpenPGP"]
|
43
|
+
* https://www.rnpgp.com[RNP library home site]
|
44
|
+
* https://github.com/riboseinc/rnp[RNP library on GitHub]
|
45
|
+
* https://github.com/riboseinc/ruby-rnp[Ruby bindings for RNP library]
|
data/enmail.gemspec
CHANGED
@@ -1,16 +1,21 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
2
|
+
|
3
|
+
# (c) Copyright 2018 Ribose Inc.
|
4
|
+
#
|
5
|
+
|
6
|
+
lib = File.expand_path("lib", __dir__)
|
3
7
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
8
|
require "enmail/version"
|
9
|
+
require "enmail/dependency_constraints"
|
5
10
|
|
6
11
|
Gem::Specification.new do |spec|
|
7
12
|
spec.name = "enmail"
|
8
13
|
spec.version = EnMail::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.email = ["
|
14
|
+
spec.authors = ["Ribose Inc."]
|
15
|
+
spec.email = ["open.source@ribose.com"]
|
11
16
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
17
|
+
spec.summary = "Encrypted Email in Ruby"
|
18
|
+
spec.description = "Encrypted Email in Ruby"
|
14
19
|
spec.homepage = "https://github.com/riboseinc/enmail"
|
15
20
|
spec.license = "MIT"
|
16
21
|
|
@@ -22,10 +27,18 @@ Gem::Specification.new do |spec|
|
|
22
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
28
|
spec.require_paths = ["lib"]
|
24
29
|
|
30
|
+
# There is no reason for 2.6.4 to be a minimum supported version of Mail,
|
31
|
+
# except for that this gem was never tested against older versions.
|
32
|
+
# Mail 2.6.4 has been released on March 23, 2016, hence should be considered
|
33
|
+
# old enough. Nevertheless, pull requests which extend compatibility will be
|
34
|
+
# accepted.
|
25
35
|
spec.add_dependency "mail", "~> 2.6.4"
|
26
36
|
|
27
|
-
spec.add_development_dependency "bundler", "
|
28
|
-
spec.add_development_dependency "
|
37
|
+
spec.add_development_dependency "bundler", ">= 1.14", "< 3.0"
|
38
|
+
spec.add_development_dependency "gpgme", *EnMail::DependencyConstraints::GPGME
|
39
|
+
spec.add_development_dependency "pry", ">= 0.10.3", "< 0.12"
|
40
|
+
spec.add_development_dependency "rake", ">= 10", "< 13"
|
41
|
+
spec.add_development_dependency "rnp", *EnMail::DependencyConstraints::RNP
|
29
42
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
-
spec.add_development_dependency "
|
43
|
+
spec.add_development_dependency "rspec-pgp_matchers", "~> 0.1.1"
|
31
44
|
end
|
data/lib/enmail.rb
CHANGED
@@ -1,7 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
require "mail"
|
5
|
+
|
6
|
+
require "enmail/version"
|
7
|
+
require "enmail/dependency_constraints"
|
8
|
+
|
9
|
+
require "enmail/helpers/message_manipulation"
|
10
|
+
require "enmail/helpers/rfc1847"
|
11
|
+
require "enmail/helpers/rfc3156"
|
12
|
+
|
13
|
+
require "enmail/adapters/base"
|
14
|
+
require "enmail/adapters/gpgme"
|
15
|
+
require "enmail/adapters/rnp"
|
16
|
+
|
17
|
+
require "enmail/extensions/message_transport_encoding_restrictions"
|
4
18
|
|
5
19
|
module EnMail
|
6
|
-
|
20
|
+
module_function
|
21
|
+
|
22
|
+
def protect(mode, message, adapter:, **options)
|
23
|
+
adapter_obj = adapter.new(options)
|
24
|
+
adapter_obj.public_send mode, message
|
25
|
+
end
|
7
26
|
end
|
27
|
+
|
28
|
+
Mail::Message.prepend EnMail::Extensions::MessageTransportEncodingRestrictions
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
module EnMail
|
5
|
+
module Adapters
|
6
|
+
# Secures e-mails according to {RFC 3156 "MIME Security with
|
7
|
+
# OpenPGP"}[https://tools.ietf.org/html/rfc3156].
|
8
|
+
#
|
9
|
+
# This adapter uses {GnuPG Made Easy
|
10
|
+
# (GPGME)}[https://www.gnupg.org/software/gpgme/index.html] library via
|
11
|
+
# interface provided by {gpgme gem}[https://github.com/ueno/ruby-gpgme].
|
12
|
+
class GPGME < Base
|
13
|
+
include Helpers::MessageManipulation
|
14
|
+
include Helpers::RFC1847
|
15
|
+
include Helpers::RFC3156
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
require_relative "gpgme_requirements"
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def compute_signature(text, signer)
|
25
|
+
plain = ::GPGME::Data.new(text)
|
26
|
+
output = ::GPGME::Data.new
|
27
|
+
mode = ::GPGME::SIG_MODE_DETACH
|
28
|
+
hash_algorithm = nil
|
29
|
+
|
30
|
+
with_ctx(password: options[:key_password]) do |ctx|
|
31
|
+
signer_keys = ::GPGME::Key.find(:secret, signer, :sign)
|
32
|
+
ctx.add_signer(*signer_keys)
|
33
|
+
|
34
|
+
begin
|
35
|
+
ctx.sign(plain, output, mode)
|
36
|
+
hash_algorithm_num = ctx.sign_result.signatures[0].hash_algo
|
37
|
+
hash_algorithm = ::GPGME.hash_algo_name(hash_algorithm_num)
|
38
|
+
rescue ::GPGME::Error::UnusableSecretKey => exc
|
39
|
+
# TODO Copy-pasted from GPGME gem. Needs any test coverage.
|
40
|
+
exc.keys = ctx.sign_result.invalid_signers
|
41
|
+
raise exc
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
output.seek(0)
|
46
|
+
|
47
|
+
["pgp-#{hash_algorithm.downcase}", output.to_s]
|
48
|
+
end
|
49
|
+
|
50
|
+
def encrypt_string(text, recipients)
|
51
|
+
build_crypto.encrypt(text, recipients: recipients)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sign_and_encrypt_string(text, signer, recipients)
|
55
|
+
build_crypto.encrypt(
|
56
|
+
text,
|
57
|
+
sign: true,
|
58
|
+
signers: [signer],
|
59
|
+
recipients: recipients,
|
60
|
+
password: options[:key_password],
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_crypto
|
65
|
+
::GPGME::Crypto.new(default_gpgme_options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_ctx(options, &block)
|
69
|
+
ctx_options = default_gpgme_options.merge(options)
|
70
|
+
::GPGME::Ctx.new(ctx_options, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_gpgme_options
|
74
|
+
{
|
75
|
+
armor: true,
|
76
|
+
pinentry_mode: ::GPGME::PINENTRY_MODE_LOOPBACK,
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# (c) Copyright 2018 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
module EnMail
|
5
|
+
module Adapters
|
6
|
+
# Secures e-mails according to {RFC 3156 "MIME Security with
|
7
|
+
# OpenPGP"}[https://tools.ietf.org/html/rfc3156].
|
8
|
+
#
|
9
|
+
# This adapter uses {RNP}[https://www.rnpgp.com/] library via
|
10
|
+
# {ruby-rnp gem}[https://github.com/riboseinc/ruby-rnp].
|
11
|
+
#
|
12
|
+
# NOTE: `Rnp` instances are not thread-safe, and neither this adapter is.
|
13
|
+
# Any adapter instance should be accessed by at most one thread at a time.
|
14
|
+
class RNP < Base
|
15
|
+
include Helpers::MessageManipulation
|
16
|
+
include Helpers::RFC1847
|
17
|
+
include Helpers::RFC3156
|
18
|
+
|
19
|
+
attr_reader :rnp
|
20
|
+
|
21
|
+
def initialize(*args)
|
22
|
+
require_relative "rnp_requirements"
|
23
|
+
super
|
24
|
+
@rnp = build_rnp_and_load_keys
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def compute_signature(text, signer)
|
30
|
+
signer_key = find_key_for(signer, need_secret: true)
|
31
|
+
|
32
|
+
signature = rnp.detached_sign(
|
33
|
+
signers: [signer_key],
|
34
|
+
input: build_input(text),
|
35
|
+
armored: true,
|
36
|
+
hash: hash_algorithm,
|
37
|
+
)
|
38
|
+
|
39
|
+
["pgp-#{hash_algorithm.downcase}", signature]
|
40
|
+
end
|
41
|
+
|
42
|
+
def encrypt_string(text, recipients)
|
43
|
+
recipient_keys =
|
44
|
+
recipients.map { |r| find_key_for(r, need_public: true) }
|
45
|
+
|
46
|
+
rnp.encrypt(
|
47
|
+
recipients: recipient_keys,
|
48
|
+
input: build_input(text),
|
49
|
+
armored: true,
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def sign_and_encrypt_string(text, signer, recipients)
|
54
|
+
signer_key = find_key_for(signer, need_secret: true)
|
55
|
+
recipient_keys =
|
56
|
+
recipients.map { |r| find_key_for(r, need_public: true) }
|
57
|
+
|
58
|
+
rnp.encrypt_and_sign(
|
59
|
+
recipients: recipient_keys,
|
60
|
+
signers: signer_key,
|
61
|
+
input: build_input(text),
|
62
|
+
armored: true,
|
63
|
+
hash: hash_algorithm,
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_key_for(email, need_public: false, need_secret: false)
|
68
|
+
rnp.each_keyid do |keyid|
|
69
|
+
key = rnp.find_key(keyid: keyid)
|
70
|
+
next if need_public && !key.public_key_present?
|
71
|
+
next if need_secret && !key.secret_key_present?
|
72
|
+
|
73
|
+
key.each_userid do |userid|
|
74
|
+
return key if userid.include?(email)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def build_input(text)
|
81
|
+
::Rnp::Input.from_string(text)
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_rnp_and_load_keys
|
85
|
+
public_info, secret_info = homedir_info.values_at(:public, :secret)
|
86
|
+
|
87
|
+
rnp = Rnp.new(public_info[:format], secret_info[:format])
|
88
|
+
|
89
|
+
[public_info, secret_info].each do |keyring_info|
|
90
|
+
input = ::Rnp::Input.from_path(keyring_info[:path])
|
91
|
+
rnp.load_keys(format: keyring_info[:format], input: input)
|
92
|
+
end
|
93
|
+
|
94
|
+
rnp.password_provider = options[:key_password]
|
95
|
+
|
96
|
+
rnp
|
97
|
+
end
|
98
|
+
|
99
|
+
def homedir_info
|
100
|
+
@homedir_info ||=
|
101
|
+
::Rnp.homedir_info(options[:homedir] || Rnp.default_homedir)
|
102
|
+
end
|
103
|
+
|
104
|
+
def hash_algorithm
|
105
|
+
"SHA512"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|