Class: Sygna::Crypt
- Inherits:
-
Object
- Object
- Sygna::Crypt
- Defined in:
- lib/sygna/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{SHA1 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_digest: nil) ⇒ Crypt
constructor
Creates a new instance of Crypt.
Constructor Details
#initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_digest: nil) ⇒ Crypt
Creates a new instance of Sygna::Crypt.
30 31 32 33 34 35 36 |
# File 'lib/sygna/crypt.rb', line 30 def initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_digest: nil) @cipher = OpenSSL::Cipher.new(cipher) @mac_digest = OpenSSL::Digest.new(mac_digest || digest) 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}" 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`.
134 135 136 137 138 139 140 141 |
# File 'lib/sygna/crypt.rb', line 134 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`.
117 118 119 120 121 122 |
# File 'lib/sygna/crypt.rb', line 117 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.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/sygna/crypt.rb', line 77 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) ephemeral_public_key_octet = .slice(0, 65) mac = .slice(65, 20) ciphertext = .slice(85, .size) 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) hashed_secret = Digest::SHA512.digest(shared_secret) cipher_key = hashed_secret.slice(0, 32) hmac_key = hashed_secret.slice(32, hashed_secret.length) data_to_mac = IV + ephemeral_public_key_octet + ciphertext computed_mac = OpenSSL::HMAC.digest("SHA1", hmac_key, data_to_mac) 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.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/sygna/crypt.rb', line 43 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) 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) hashed_secret = Digest::SHA512.digest(shared_secret) cipher_key = hashed_secret.slice(0, 32) hmac_key = hashed_secret.slice(32, hashed_secret.length - 32) @cipher.encrypt @cipher.iv = IV @cipher.key = cipher_key ciphertext = @cipher.update() + @cipher.final data_to_mac = IV + ephemeral_public_key_octet + ciphertext mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, data_to_mac) # 65 + 20 + 16 ephemeral_public_key_octet + mac + ciphertext end |