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)
Class Method Summary collapse
-
.private_key_from_hex(hex_string, ec_group = 'secp256k1') ⇒ OpenSSL::PKey::EC
Converts a hex-encoded private key to an
OpenSSL::PKey::EC. -
.public_key_from_hex(hex_string, ec_group = 'secp256k1') ⇒ OpenSSL::PKey::EC
Converts a hex-encoded public key to an
OpenSSL::PKey::EC.
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, shared_info_suffix) ⇒ String
Key-derivation function, compatible with ANSI-X9.63-KDF.
-
#to_s ⇒ String
A string representing this Crypt's parameters.
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 |
Class Method Details
.private_key_from_hex(hex_string, ec_group = 'secp256k1') ⇒ OpenSSL::PKey::EC
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).
Converts a hex-encoded private key to an OpenSSL::PKey::EC.
183 184 185 186 187 188 189 190 |
# File 'lib/ecies/crypt.rb', line 183 def self.private_key_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) key.private_key < ec_group.order or raise OpenSSL::PKey::ECError, "Private key greater than group's order" key.private_key > 1 or raise OpenSSL::PKey::ECError, "Private key too small" key end |
.public_key_from_hex(hex_string, ec_group = 'secp256k1') ⇒ OpenSSL::PKey::EC
Converts a hex-encoded public key to an OpenSSL::PKey::EC.
166 167 168 169 170 171 |
# File 'lib/ecies/crypt.rb', line 166 def self.public_key_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 |
Instance Method Details
#decrypt(key, encrypted_message) ⇒ String
Decrypts a message with a private key using ECIES.
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 120 |
# File 'lib/ecies/crypt.rb', line 89 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_octet = .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_octet, 2)) shared_secret = key.dh_compute_key(ephemeral_public_key) key_pair = kdf(shared_secret, @cipher.key_len + @mac_length, ephemeral_public_key_octet) 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.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# 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 ephemeral_public_key_octet = ephemeral_key.public_key.to_bn.to_s(2) shared_secret = ephemeral_key.dh_compute_key(key.public_key) key_pair = kdf(shared_secret, @cipher.key_len + @mac_length, ephemeral_public_key_octet) 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_public_key_octet + ciphertext + mac end |
#kdf(shared_secret, length, shared_info_suffix) ⇒ String
Key-derivation function, compatible with ANSI-X9.63-KDF
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/ecies/crypt.rb', line 130 def kdf(shared_secret, length, shared_info_suffix) 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 + shared_info_suffix) if io.pos >= length return io.string.byteslice(0, length) end end end |
#to_s ⇒ String
Returns A string representing this Crypt's parameters.
153 154 155 156 157 |
# File 'lib/ecies/crypt.rb', line 153 def to_s "KDF-#{@kdf_digest.name}_" + "HMAC-SHA-#{@mac_digest.digest_length * 8}-#{@mac_length * 8}_" + @cipher.name end |