Skip to content

Commit

Permalink
Merge 253a01b into 5ab16e3
Browse files Browse the repository at this point in the history
  • Loading branch information
aldosolorzano committed Dec 26, 2018
2 parents 5ab16e3 + 253a01b commit 326ce58
Show file tree
Hide file tree
Showing 21 changed files with 848 additions and 15 deletions.
10 changes: 0 additions & 10 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,3 @@ source 'https://rubygems.org'

# Specify your gem's dependencies in mifiel.gemspec
gemspec

group :development, :test do
gem 'coveralls', require: false
gem 'guard', '2.13.0'
gem 'guard-rspec'
# compatible with ruby >= 1.9.3
gem 'guard-rubocop'
gem 'listen', '3.0.8'
gem 'terminal-notifier-guard'
end
7 changes: 7 additions & 0 deletions lib/core_extensions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require_relative './core_extensions/string/binary_hex'

module CoreExtensions
def self.load
::String.include CoreExtensions::String::BinaryHex
end
end
19 changes: 19 additions & 0 deletions lib/core_extensions/string/binary_hex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module CoreExtensions
module String
module BinaryHex
def bth
unpack('H*').first
end

def htb
Array(self).pack('H*')
end

def force_binary
return htb if match(/^[0-9A-F]+$/i).is_a? MatchData
return self if bth.match(/^[0-9A-F]+$/i).is_a? MatchData
raise ArgumentError, 'Invalid encoding, hex or binary'
end
end
end
end
1 change: 1 addition & 0 deletions lib/mifiel.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Mifiel
autoload :Template, 'mifiel/template'
autoload :Config, 'mifiel/config'
autoload :User, 'mifiel/user'
autoload :Crypto, 'mifiel/crypto'

BASE_URL = 'https://www.mifiel.com/api/v1'.freeze

Expand Down
34 changes: 34 additions & 0 deletions lib/mifiel/crypto.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require_relative '../core_extensions.rb'
CoreExtensions.load

module Mifiel
module Crypto
autoload :PBE, 'mifiel/crypto/pbe'
autoload :Response, 'mifiel/crypto/response'
autoload :AES, 'mifiel/crypto/aes'
autoload :ECIES, 'mifiel/crypto/ecies'
autoload :PKCS5, 'mifiel/crypto/pkcs5'

def self.decrypt(asn1, pass)
pkcs5 = Mifiel::Crypto::PKCS5.parse(asn1.force_binary)
params = pkcs5.values
params[:data] = params[:cipher_text]
params[:key] =
Mifiel::Crypto::PBE.derive_key({ password: pass }.merge(params.slice(:salt, :iterations, :key_size)))
Mifiel::Crypto::AES.decrypt(params.slice(:key, :data, :iv, :cipher))
end

def self.encrypt(document, password)
params = {
salt: Mifiel::Crypto::PBE.random_salt,
iterations: Mifiel::Crypto::PBE::ITERATIONS,
password: password
}
params[:key] = Mifiel::Crypto::PBE.derive_key(params)
params[:iv] = Mifiel::Crypto::AES.random_iv
params[:data] = document
params[:cipher_text] = Mifiel::Crypto::AES.encrypt(params.slice(:key, :iv, :data))
Mifiel::Crypto::PKCS5.new(params.slice(:salt, :iv, :iterations, :cipher_text))
end
end
end
52 changes: 52 additions & 0 deletions lib/mifiel/crypto/aes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'openssl'
module Mifiel
module Crypto
class AES
CIPHER = 256
CIPHERS = { 'AES-128-CBC' => 128, 'AES-192-CBC' => 192, 'AES-256-CBC' => 256 }.freeze

def self.random_iv(size = 16)
OpenSSL::Random.random_bytes(size)
end

def self.encrypt(cipher: CIPHER, key: nil, iv: nil, data: nil)
aes = Mifiel::Crypto::AES.new(cipher)
aes.encrypt(key: key, iv: iv, data: data)
end

def self.decrypt(cipher: CIPHER, key: nil, iv: nil, data: nil)
aes = Mifiel::Crypto::AES.new(cipher)
aes.decrypt(key: key, iv: iv, data: data)
end

def self.build_cipher(cipher)
return OpenSSL::Cipher.new(cipher) if cipher.is_a? String
OpenSSL::Cipher::AES.new(cipher, :CBC)
rescue
raise Mifiel::AESError, 'Cipher not supported'
end

attr_accessor :cipher

def initialize(cipher_id = CIPHER)
@cipher = Mifiel::Crypto::AES.build_cipher(cipher_id)
end

def encrypt(key: nil, iv: nil, data: nil)
iv ||= Mifiel::Crypto::AES.random_iv(size)
cipher_final(key, iv, data, action: :encrypt)
end

def decrypt(key: nil, iv: nil, data: nil)
cipher_final(key, iv, data, action: :decrypt)
end

def cipher_final(key, iv, message, action: :encrypt)
@cipher.send(action)
@cipher.iv = iv
@cipher.key = key
@cipher.update(message) + @cipher.final
end
end
end
end
113 changes: 113 additions & 0 deletions lib/mifiel/crypto/ecies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# This class was based on https://github.com/jamoes/ecies
module Mifiel
module Crypto
class ECIES
# The allowed digest algorithms for ECIES.
DIGESTS = %w(SHA224 SHA256 SHA384 SHA512).freeze

# The allowed cipher algorithms for ECIES.
CIPHERS = %w(AES-128-CBC AES-192-CBC AES-256-CBC).freeze
IV_SIZE = 16

def initialize(cipher: 'AES-256-CBC', kdf_digest: 'SHA512', mac_digest: 'SHA256')
raise Mifiel::ECError, "Cipher must be one of #{CIPHERS}" unless CIPHERS.include?(cipher)
raise Mifiel::ECError, "Cipher must be one of #{DIGESTS}" unless DIGESTS.include?(mac_digest)
raise Mifiel::ECError, "Cipher must be one of #{DIGESTS}" unless DIGESTS.include?(kdf_digest)

@cipher = OpenSSL::Cipher.new(cipher)
@mac_digest = OpenSSL::Digest.new(mac_digest)
@kdf_digest = OpenSSL::Digest.new(kdf_digest)
@mac_length = @mac_digest.digest_length
end

def cipher_final(key, iv, message, action: :encrypt)
@cipher.reset
@cipher.send(action)
@cipher.iv = iv
@cipher.key = key
@cipher.update(message) + @cipher.final
end

def generate_keys(shared_secret)
key_pair = @kdf_digest.digest(shared_secret)
cipher_key = key_pair.byteslice(0, @cipher.key_len)
hmac_key = key_pair.byteslice(-@mac_length, @mac_length)
{ cipher_key: cipher_key, hmac_key: hmac_key }
end

def compute_mac(hmac_key, ephemeral_public_key_octet, ciphertext, iv)
OpenSSL::HMAC.digest(@mac_digest, hmac_key, iv + ephemeral_public_key_octet + ciphertext)
end

# Encrypts a message to a public key using ECIES.
# @param key [OpenSSL::EC:PKey] The public key.
# @param message [String] The plain-text message.
# @return [String] The octet string of the encrypted message.
def encrypt(key, message, iv: nil) # rubocop:disable Metrics/AbcSize
iv ||= OpenSSL::Random.random_bytes(IV_SIZE)
ephemeral_key = OpenSSL::PKey::EC.new(key.group).generate_key
ephemeral_public_key_octet = ephemeral_key.public_key.to_bn.to_s(2)
keys = generate_keys(ephemeral_key.dh_compute_key(key.public_key))

ciphertext = cipher_final(keys[:cipher_key], iv, message)
mac = compute_mac(keys[:hmac_key], ephemeral_public_key_octet, ciphertext, iv)
iv + ephemeral_public_key_octet + ciphertext + mac
end

# Decrypts a message with a private key using ECIES.
# @param key [OpenSSL::EC:PKey] The private key.
# @param encrypted_message [String] Octet string of the encrypted message.
# @return [String] The plain-text message.
def decrypt(key, encrypted_message) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
iv = encrypted_message.byteslice(0, IV_SIZE)
group_copy = OpenSSL::PKey::EC::Group.new(key.group)
ephemeral_public_key_length = group_copy.generator.to_bn.to_s(2).bytesize
ciphertext_length = encrypted_message.bytesize - ephemeral_public_key_length - @mac_length - IV_SIZE
raise Mifiel::ECError, 'Encrypted message too short' unless ciphertext_length > 0

ephemeral_public_key_octet = encrypted_message.byteslice(iv.bytesize, ephemeral_public_key_length)
ciphertext = encrypted_message.byteslice((ephemeral_public_key_length + iv.bytesize), ciphertext_length)
ephemeral_public_key = OpenSSL::PKey::EC::Point.new(group_copy, OpenSSL::BN.new(ephemeral_public_key_octet, 2))
keys = generate_keys(key.dh_compute_key(ephemeral_public_key))

mac = encrypted_message.byteslice(-@mac_length, @mac_length)
computed_mac = compute_mac(keys[:hmac_key], ephemeral_public_key_octet, ciphertext, iv)
raise Mifiel::ECError, 'Invalid mac' unless computed_mac == mac
cipher_final(keys[:cipher_key], iv, ciphertext, action: :decrypt)
end

# Converts a hex-encoded public key to an `OpenSSL::PKey::EC`.
#
# @param hex_string [String] The hex-encoded public key.
# @param ec_group [OpenSSL::PKey::EC::Group,String] The elliptical curve
# group for this public key.
# @return [OpenSSL::PKey::EC] The public key.
# @raise [OpenSSL::PKey::EC::Point::Error] If the public key is invalid.
def self.public_from_hex(hex_string, ec_group = 'secp256k1')
ec_group = OpenSSL::PKey::EC::Group.new(ec_group) if ec_group.is_a?(String)
key = OpenSSL::PKey::EC.new(ec_group)
key.public_key = OpenSSL::PKey::EC::Point.new(ec_group, OpenSSL::BN.new(hex_string, 16))
key
end

# Converts a hex-encoded private key to an `OpenSSL::PKey::EC`.
#
# @param hex_string [String] The hex-encoded private key.
# @param ec_group [OpenSSL::PKey::EC::Group,String] The elliptical curve
# group for this private key.
# @return [OpenSSL::PKey::EC] The private key.
# @note The returned key only contains the private component. In order to
# populate the public component of the key, you must compute it as
# follows: `key.public_key = key.group.generator.mul(key.private_key)`.
# @raise [::ECError] If the private key is invalid.
def self.private_from_hex(hex_string, ec_group = 'secp256k1')
ec_group = OpenSSL::PKey::EC::Group.new(ec_group) if ec_group.is_a?(String)
key = OpenSSL::PKey::EC.new(ec_group)
key.private_key = OpenSSL::BN.new(hex_string, 16)
raise Mifiel::ECError, 'Private key greater than group order' unless key.private_key < ec_group.order
raise Mifiel::ECError, 'Private key too small' unless key.private_key > 1
key
end
end
end
end
34 changes: 34 additions & 0 deletions lib/mifiel/crypto/pbe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'openssl'
require 'securerandom'

module Mifiel
module Crypto
class PBE
ALPHA_NUM = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a
SPECIALS = ['-', '_', '+', '=', '#', '&', '*', '.'].freeze
CHARS = ALPHA_NUM + SPECIALS
ITERATIONS = 1000

def self.random_password(length = 32)
CHARS.sort_by { SecureRandom.random_number }.join[0...length]
end

def self.random_salt(size = 16)
SecureRandom.random_bytes(size)
end

def self.derive_key(password:, salt:, key_size: 32, iterations: ITERATIONS)
args = {
password: password,
salt: salt,
iterations: iterations,
key_size: key_size,
digest: OpenSSL::Digest::SHA256.new
}
OpenSSL::PKCS5.pbkdf2_hmac(*args.values)
rescue => e
raise Mifiel::PBError, e.message || 'Unable to derive key.'
end
end
end
end
Loading

0 comments on commit 326ce58

Please sign in to comment.