Skip to content

Commit

Permalink
Merge 6fcf630 into 5ab16e3
Browse files Browse the repository at this point in the history
  • Loading branch information
aldosolorzano committed Dec 13, 2018
2 parents 5ab16e3 + 6fcf630 commit 730317d
Show file tree
Hide file tree
Showing 16 changed files with 622 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
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
18 changes: 18 additions & 0 deletions lib/mifiel/crypto.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Mifiel
module Crypto
autoload :PBE, 'mifiel/crypto/pbe'
autoload :Response, 'mifiel/crypto/response'
autoload :AES, 'mifiel/crypto/aes'
autoload :ECIES, 'mifiel/crypto/ecies'
end
end

class String
def bth
unpack('H*').first
end

def htb
Array(self).pack('H*')
end
end
56 changes: 56 additions & 0 deletions lib/mifiel/crypto/aes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require 'openssl'
module Mifiel
module Crypto
class AES
CIPHER = 256
SIZE = 16

def self.random_iv(size = SIZE)
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

attr_accessor :cipher, :require_args

def initialize(cipher_type = CIPHER)
@require_args = [:key, :iv, :data]
@cipher = OpenSSL::Cipher::AES.new(cipher_type, :CBC)
end

def random_iv(size = SIZE)
Mifiel::Crypto::AES.random_iv(size)
end

def encrypt(key: nil, iv: nil, data: nil)
iv ||= random_iv
Encrypted.new(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

class Encrypted < Mifiel::Crypto::Response
def to_str
data
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
52 changes: 52 additions & 0 deletions lib/mifiel/crypto/pbe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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

def self.generate
pbe = Crypto::PBE.new
password = pbe.random_password
salt = pbe.random_salt
pbe.derived_key(password, salt)
end

def initialize(i = 1000)
@iterations = i
@digest = OpenSSL::Digest::SHA256.new
end

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

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

def derived_key(password, salt, sizeKey = 24)
args = {
password: password,
salt: salt,
iterations: @iterations,
sizeKey: sizeKey,
digest: @digest
}
PKCS5.new(args)
end
end

class PKCS5 < Mifiel::Crypto::Response
attr_reader :key

def initialize(args)
@key = OpenSSL::PKCS5.pbkdf2_hmac(*args.values)
@data = key # key attribute is syntax sugar
end
end
end
end
19 changes: 19 additions & 0 deletions lib/mifiel/crypto/response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Mifiel
module Crypto
class Response
attr_reader :data

def ==(other)
data == other.data
end

def initialize(data)
@data = data
end

def to_hex
data.unpack('H*').first
end
end
end
end
1 change: 1 addition & 0 deletions lib/mifiel/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ module Mifiel
PrivateKeyError = Class.new StandardError
NotPrivateKeyError = Class.new ArgumentError
NoSignatureError = Class.new StandardError
ECError = Class.new StandardError
end
3 changes: 1 addition & 2 deletions mifiel.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ Gem::Specification.new do |spec|

spec.add_development_dependency 'bump', '~> 0.5', '>= 0.5.3'
spec.add_development_dependency 'bundler', '~> 1.6'
spec.add_development_dependency 'byebug', '~> 9.0', '< 9.0.6'
spec.add_development_dependency 'pry-byebug', '~> 3.4', '>= 3.3.0'
spec.add_development_dependency 'coveralls', '~> 0.8', '>= 0.8.22'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '~> 3.1', '>= 3.1.7'
spec.add_development_dependency 'rubocop', '0.47.1'
Expand Down
68 changes: 68 additions & 0 deletions spec/fixtures/aes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"valid": [
{
"algorithm":128,
"key": "1234567890123456",
"dataToEncrypt": "cifrado de Prueba",
"iv": "6976207465737469",
"encrypted": "e69522e578832e378221b541431e64e6e704c17d8b2bdda598ebd3c716836d03"
},
{
"algorithm":128,
"key": "derivedKEY234556",
"dataToEncrypt": "test de cifrado",
"iv": "3436353837383837",
"encrypted": "2b27f69570049490f5fee3bed8c045d9"
},
{
"algorithm":128,
"key": "a9r82*9rn5Flp3/o",
"dataToEncrypt": "test de cifrado",
"iv": "1353837383861646",
"encrypted": "1b6a81b3ce5eb6ae3a239d0ee51e2699"
},

{
"algorithm":192,
"key": "123456789012345678901234",
"dataToEncrypt": "cifrado de Prueba AES-192",
"iv": "6466736438373435",
"encrypted": "0cf4ea5765b42b9088c901064c07ba7a6eec3030609673830fcbd8ac5cc77edd"
},
{
"algorithm":192,
"key": "derivedKEY234556iksRtryr",
"dataToEncrypt": "test de cifrado AES-192",
"iv": "7266736438373435",
"encrypted": "fd0fec1268dbb111babc5b8a15397ad946af61561eb6d40c36068c04ddf5d008"
},
{
"algorithm":192,
"key": "*854FrGTH/hgf_4f6h9v4dfg",
"dataToEncrypt": "encrypted decrypted data",
"iv": "2e39746438373435",
"encrypted": "43e7e6f7a56a73a60d5b619e590b91de3c79e765a37f893f122a6b3e9497f8f6"
},
{
"algorithm": 256,
"key": "12345678901234567890123456789012",
"dataToEncrypt": "cifrado de Prueba AES-256",
"iv": "39382e2e2d6438rf",
"encrypted": "60ea45d407514d4e67ef0eb12b2df8e5ea02f156cdea71eeafda0b0b54092d63"
},
{
"algorithm":256,
"key": "derivedKEY234556iksRtryrtg578hfr",
"dataToEncrypt": "test de cifrado AES-256",
"iv": "7266736438373435",
"encrypted": "6d2e185278b5a87da86941b390ac7baffee5e87ae86d877350edbe9a6077e396"
},
{
"algorithm":256,
"key": "*854FrGTH/hgf_4f6h9v4dfg*&jr-jew",
"dataToEncrypt": "test de cifrado",
"iv": "2e39746438373435",
"encrypted": "3c3dc5a978f756a576a952b4eb3ca868"
}
]
}
Loading

0 comments on commit 730317d

Please sign in to comment.