Class: ECIES::Crypt
- Inherits:
-
Object
- Object
- ECIES::Crypt
- Defined in:
- lib/ecies/crypt.rb
Overview
Provides functionality for encrypting and decrypting messages using ECIES. Encapsulates the configuration parameters chosen for ECIES.
Constant Summary collapse
- DIGESTS =
The allowed digest algorithms for ECIES.
%w{SHA224 SHA256 SHA384 SHA512}- CIPHERS =
The allowed cipher algorithms for ECIES.
%w{AES-128-CBC AES-192-CBC AES-256-CBC AES-128-CTR AES-192-CTR AES-256-CTR}- IV =
The initialization vector used in ECIES. Quoting from sec1-v2: "When using ECIES, some exception are made. For the CBC and CTR modes, the initial value or initial counter are set to be zero and are omitted from the ciphertext. In general this practice is not advisable, but in the case of ECIES it is acceptable because the definition of ECIES implies the symmetric block cipher key is only to be used once.
("\x00" * 16).force_encoding(Encoding::BINARY)
Instance Method Summary collapse
-
#decrypt(key, encrypted_message) ⇒ String
Decrypts a message with a private key using ECIES.
-
#encrypt(key, message) ⇒ String
Encrypts a message to a public key using ECIES.
-
#initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half, kdf_digest: nil, mac_digest: nil, kdf_shared_info: '', mac_shared_info: '') ⇒ Crypt
constructor
Creates a new instance of Crypt.
-
#kdf(shared_secret, length) ⇒ String
Key-derivation function, compatible with ANSI-X9.63-KDF.
Constructor Details
#initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half, kdf_digest: nil, mac_digest: nil, kdf_shared_info: '', mac_shared_info: '') ⇒ Crypt
Creates a new instance of ECIES::Crypt.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/ecies/crypt.rb', line 38 def initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half, kdf_digest: nil, mac_digest: nil, kdf_shared_info: '', mac_shared_info: '') @cipher = OpenSSL::Cipher.new(cipher) @mac_digest = OpenSSL::Digest.new(mac_digest || digest) @kdf_digest = OpenSSL::Digest.new(kdf_digest || digest) @kdf_shared_info = kdf_shared_info @mac_shared_info = mac_shared_info CIPHERS.include?(@cipher.name) or raise "Cipher must be one of #{CIPHERS}" DIGESTS.include?(@mac_digest.name) or raise "Digest must be one of #{DIGESTS}" DIGESTS.include?(@kdf_digest.name) or raise "Digest must be one of #{DIGESTS}" [:half, :full].include?(mac_length) or raise "mac_length must be :half or :full" @mac_length = @mac_digest.digest_length @mac_length /= 2 if mac_length == :half end |
Instance Method Details
#decrypt(key, encrypted_message) ⇒ String
Decrypts a message with a private key using ECIES.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ecies/crypt.rb', line 88 def decrypt(key, ) key.private_key? or raise "Must have private key to decrypt" @cipher.reset group_copy = OpenSSL::PKey::EC::Group.new(key.group) group_copy.point_conversion_form = :compressed ephemeral_public_key_length = group_copy.generator.to_bn.to_s(2).bytesize ciphertext_length = .bytesize - ephemeral_public_key_length - @mac_length ciphertext_length > 0 or raise OpenSSL::PKey::ECError, "Encrypted message too short" ephemeral_public_key_text = .byteslice(0, ephemeral_public_key_length) ciphertext = .byteslice(ephemeral_public_key_length, ciphertext_length) mac = .byteslice(-@mac_length, @mac_length) ephemeral_public_key = OpenSSL::PKey::EC::Point.new(group_copy, OpenSSL::BN.new(ephemeral_public_key_text, 2)) shared_secret = key.dh_compute_key(ephemeral_public_key) key_pair = kdf(shared_secret, @cipher.key_len + @mac_length) cipher_key = key_pair.byteslice(0, @cipher.key_len) hmac_key = key_pair.byteslice(-@mac_length, @mac_length) computed_mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, ciphertext + @mac_shared_info).byteslice(0, @mac_length) computed_mac == mac or raise OpenSSL::PKey::ECError, "Invalid Message Authenticaton Code" @cipher.decrypt @cipher.iv = IV @cipher.key = cipher_key @cipher.update(ciphertext) + @cipher.final end |
#encrypt(key, message) ⇒ String
Encrypts a message to a public key using ECIES.
@param key [OpenSSL::EC:PKey] The public key.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/ecies/crypt.rb', line 59 def encrypt(key, ) key.public_key? or raise "Must have public key to encrypt" @cipher.reset group_copy = OpenSSL::PKey::EC::Group.new(key.group) group_copy.point_conversion_form = :compressed ephemeral_key = OpenSSL::PKey::EC.new(group_copy).generate_key shared_secret = ephemeral_key.dh_compute_key(key.public_key) key_pair = kdf(shared_secret, @cipher.key_len + @mac_length) cipher_key = key_pair.byteslice(0, @cipher.key_len) hmac_key = key_pair.byteslice(-@mac_length, @mac_length) @cipher.encrypt @cipher.iv = IV @cipher.key = cipher_key ciphertext = @cipher.update() + @cipher.final mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, ciphertext + @mac_shared_info).byteslice(0, @mac_length) ephemeral_key.public_key.to_bn.to_s(2) + ciphertext + mac end |
#kdf(shared_secret, length) ⇒ String
Key-derivation function, compatible with ANSI-X9.63-KDF
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/ecies/crypt.rb', line 127 def kdf(shared_secret, length) length >=0 or raise "length cannot be negative" return "" if length == 0 if length / @kdf_digest.digest_length >= 0xFF_FF_FF_FF raise "length too large" end io = StringIO.new(String.new) counter = 0 loop do counter += 1 counter_bytes = [counter].pack('N') io << @kdf_digest.digest(shared_secret + counter_bytes + @kdf_shared_info) if io.pos >= length return io.string.byteslice(0, length) end end end |