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
-
.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
.public_key_from_hex(hex_string, ec_group = 'secp256k1') ⇒ OpenSSL::PKey::EC
Converts a hex-encoded public key to an OpenSSL::PKey::EC
.
161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/ecies/crypt.rb', line 161 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) sequence = OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.Sequence([ OpenSSL::ASN1.ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(ec_group.curve_name), ]), OpenSSL::ASN1.BitString([hex_string].pack('H*')), ]) OpenSSL::PKey::EC.new(sequence.to_der) end |
Instance Method Details
#decrypt(key, encrypted_message) ⇒ String
Decrypts a message with a private key using ECIES.
87 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 |
# File 'lib/ecies/crypt.rb', line 87 def decrypt(key, ) key.private_key? or raise "Must have private key to decrypt" @cipher.reset ephemeral_public_key_length = key.group.generator.to_octet_string(:compressed).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(key.group, 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 |
# File 'lib/ecies/crypt.rb', line 59 def encrypt(key, ) key.public_key? or raise "Must have public key to encrypt" @cipher.reset ephemeral_key = OpenSSL::PKey::EC.generate(key.group) ephemeral_public_key_octet = ephemeral_key.public_key.to_octet_string(:compressed) 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
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/ecies/crypt.rb', line 125 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.
148 149 150 151 152 |
# File 'lib/ecies/crypt.rb', line 148 def to_s "KDF-#{@kdf_digest.name}_" + "HMAC-SHA-#{@mac_digest.digest_length * 8}-#{@mac_length * 8}_" + @cipher.name end |