Class: DEVp2p::Crypto::ECIES

Inherits:
Object
  • Object
show all
Defined in:
lib/devp2p/crypto/ecies.rb

Constant Summary collapse

CIPHER =
'AES-128-CTR'.freeze
CIPHER_BLOCK_SIZE =

128 / 8

16
ENCRYPT_OVERHEAD_LENGTH =
113

Class Method Summary collapse

Class Method Details

.decrypt(curve, data, shared_mac_data = '') ⇒ Object

Decrypt data with ECIES method using the local private key

ECIES Decrypt (performed by recipient):

  1. generate shared-secret = kdf( ecdhAgree(myPrivKey, msg) )

  2. verify tag

  3. decrypt

ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R)

where R = r*G, recipientPublic = recipientPrivate * G

Raises:



70
71
72
73
74
75
76
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
# File 'lib/devp2p/crypto/ecies.rb', line 70

def decrypt(curve, data, shared_mac_data='')
  raise DecryptionError, 'wrong ecies header' unless data[0] == "\x04"

  # 1. generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1,64]) )
  shared = data[1,64] # ephem_pubkey
  raise DecryptionError, 'invalid shared secret' unless ECCx.valid_key?(shared)

  key_material = ECCx.get_ecdh_key curve, shared
  raise InvalidKeyError unless key_material.size == 32

  key = kdf key_material, 32
  raise InvalidKeyError unless key.size == 32
  key_enc, key_mac = key[0,16], key[16,16]

  key_mac = Digest::SHA256.digest(key_mac)
  raise InvalidKeyError unless key_mac.size == 32

  tag = data[-32..-1]
  raise InvalidMACError unless tag.size == 32

  # 2. verify tag
  raise DecryptionError, 'Fail to verify data' unless Crypto.hmac_sha256(key_mac, "#{data[65...-32]}#{shared_mac_data}") == tag

  # 3. decrypt
  iv = data[65,CIPHER_BLOCK_SIZE]
  ciphertext = data[(65+CIPHER_BLOCK_SIZE)...-32]
  raise DecryptionError unless 1 + shared.size + iv.size + ciphertext.size + tag.size == data.size

  ctx = OpenSSL::Cipher.new CIPHER
  ctx.decrypt
  ctx.key = key_enc
  ctx.iv = iv

  ctx.update(ciphertext) + ctx.final
end

.encrypt(data, remote_pubkey, shared_mac_data = '') ⇒ Object

ECIES Encrypt, where P = recipient publie key, is:

  1. generate r = random value

  2. generate shared-secret = kdf( ecdhAgree(r, P) )

  3. generate R = rG [ same op as generating a public key ]

  4. send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag

Raises:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/devp2p/crypto/ecies.rb', line 22

def encrypt(data, remote_pubkey, shared_mac_data='')
  # 1. generate r = random value
  ephem = ECCx.new

  # 2. generate shared-secret = kdf( ecdhAgree(r, P) )
  key_material = ephem.get_ecdh_key remote_pubkey
  raise InvalidKeyError unless key_material.size == 32

  key = kdf key_material, 32
  raise InvalidKeyError unless key.size == 32
  key_enc, key_mac = key[0,16], key[16,16]

  key_mac = Digest::SHA256.digest(key_mac)
  raise InvalidKeyError unless key_mac.size == 32

  # 3. generate R = rG
  ephem_pubkey = ephem.raw_pubkey

  ctx = OpenSSL::Cipher.new(CIPHER)
  ctx.encrypt
  ctx.key = key_enc
  iv = ctx.random_iv
  ctx.iv = iv

  ciphertext = ctx.update(data) + ctx.final
  raise EncryptionError unless ciphertext.size == data.size

  # 4. send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag
  tag = Crypto.hmac_sha256 key_mac, "#{iv}#{ciphertext}#{shared_mac_data}"
  raise InvalidMACError unless tag.size == 32
  msg = "\x04#{ephem_pubkey}#{iv}#{ciphertext}#{tag}"

  raise EncryptionError unless msg.size == ENCRYPT_OVERHEAD_LENGTH + data.size
  msg
end

.kdf(key_material, key_len) ⇒ Object

interop w/go ecies implementation

for sha3, blocksize is 136 bytes for sha256, blocksize is 64 bytes

NIST SP 800-56a Concatenation Key Derivation Function (section 5.8.1)



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/devp2p/crypto/ecies.rb', line 114

def kdf(key_material, key_len)
  s1 = ""
  key = ""
  hash_blocksize = 64
  reps = ((key_len + 7) * 8) / (hash_blocksize * 8)
  counter = 0
  while counter <= reps
    counter += 1
    ctx = Digest::SHA256.new
    ctx.update [counter].pack('I>')
    ctx.update key_material
    ctx.update s1
    key += ctx.digest
  end
  key[0,key_len]
end