def encrypt(self, data: bytes, raw_pubkey: bytes, shared_mac_data: bytes = b'') -> bytes: """ ECIES Encrypt, where P = recipient public 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 || AES(shared-secret, plaintext) || tag """ enc_key, mac_key, ephem_pubkey = \ self.key_manager.get_ephem_key(raw_pubkey) iv = pyelliptic.Cipher.gen_IV(self.CIPHERNAME) assert len(iv) == 16 cipher = pyelliptic.Cipher(enc_key, iv, 1, self.CIPHERNAME) ciphertext = cipher.ciphering(data) assert len(ciphertext) == len(data) msg = ephem_pubkey + iv + ciphertext tag = pyelliptic.hmac_sha256(mac_key, msg[1 + 64:] + shared_mac_data) assert len(tag) == 32 msg += tag assert len(msg) == self.OVERHEAD + len(data) return msg
def ecies_encrypt(cls, data, raw_pubkey, shared_mac_data=''): """ ECIES Encrypt, where P = recipient public 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 currently used by go: ECIES_AES128_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 16, } """ # 1) generate r = random value ephem = cls(None) # 2) generate shared-secret = kdf( ecdhAgree(r, P) ) key_material = ephem.raw_get_ecdh_key(pubkey_x=raw_pubkey[:32], pubkey_y=raw_pubkey[32:]) assert len(key_material) == 32 key = eciesKDF(key_material, 32) assert len(key) == 32 key_enc, key_mac = key[:16], key[16:] key_mac = hashlib.sha256(key_mac).digest() # !!! assert len(key_mac) == 32 # 3) generate R = rG [same op as generating a public key] ephem_pubkey = ephem.raw_pubkey # encrypt iv = pyelliptic.Cipher.gen_IV(cls.ecies_ciphername) assert len(iv) == 16 ctx = pyelliptic.Cipher(key_enc, iv, 1, cls.ecies_ciphername) ciphertext = ctx.ciphering(data) assert len(ciphertext) == len(data) # 4) send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) # || tag msg = rlp_utils.ascii_chr(0x04) + ephem_pubkey + iv + ciphertext # the MAC of a message (called the tag) as per SEC 1, 3.5. tag = pyelliptic.hmac_sha256( key_mac, msg[1 + 64:] + rlp_utils.str_to_bytes(shared_mac_data)) assert len(tag) == 32 msg += tag assert len(msg) == 1 + 64 + 16 + 32 + len(data) == 113 + len(data) assert len(msg) - cls.ecies_encrypt_overhead_length == len(data) return msg
def ecies_decrypt(self, data, shared_mac_data=b''): """ Decrypt data with ECIES method using the local private key ECIES Decrypt (performed by recipient): 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) 2) verify tag 3) decrypt ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R) [where R = r*G, and recipientPublic = recipientPrivate*G] """ if data[:1] != b'\x04': raise exceptions.DecryptionError("wrong ecies header") # 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) _shared = data[1:1 + 64] # FIXME, check that _shared_pub is a valid one (on curve) key_material = self.raw_get_ecdh_key(pubkey_x=_shared[:32], pubkey_y=_shared[32:]) assert len(key_material) == 32 key = eciesKDF(key_material, 32) assert len(key) == 32 key_enc, key_mac = key[:16], key[16:] key_mac = hashlib.sha256(key_mac).digest() assert len(key_mac) == 32 tag = data[-32:] assert len(tag) == 32 # 2) verify tag hmaced_data = pyelliptic.hmac_sha256( key_mac, data[1 + 64:-32] + shared_mac_data) if not pyelliptic.equals(hmaced_data, tag): raise exceptions.DecryptionError("Fail to verify data") # 3) decrypt blocksize = pyelliptic.OpenSSL.get_cipher( self.ecies_ciphername).get_blocksize() # noqa iv = data[1 + 64:1 + 64 + blocksize] assert len(iv) == 16 ciphertext = data[1 + 64 + blocksize:-32] assert 1 + len(_shared) + len(iv) + len(ciphertext) + len(tag) == len( data) # noqa ctx = pyelliptic.Cipher(key_enc, iv, 0, self.ecies_ciphername) return ctx.ciphering(ciphertext)
def decrypt(self, data, raw_privkey, shared_mac_data=b''): """ Decrypt data with ECIES method using the local private key ECIES Decrypt (performed by recipient): 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) 2) verify tag 3) decrypt ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R) [where R = r*G, and recipientPublic = recipientPrivate*G] """ if data[:1] != b'\x04': raise exceptions.DecryptionError("Wrong ECIES header") # 1) generate shared-secret ephem_pubkey = data[:1 + 64] key_material = self.key_manager.ecdh(HashablePrivateKey(raw_privkey), ephem_pubkey) assert len(key_material) == 32 enc_key, mac_key = self.key_manager.get_derived_keys(key_material) tag = data[-32:] assert len(tag) == 32 # 2) verify tag hmaced_data = pyelliptic.hmac_sha256( mac_key, data[1 + 64:-32] + shared_mac_data) if not pyelliptic.equals(hmaced_data, tag): raise exceptions.DecryptionError("Tag verification failed") # 3) decrypt iv = data[1 + 64:1 + 64 + self.BLOCK_SIZE] assert len(iv) == 16 ciphertext = data[1 + 64 + self.BLOCK_SIZE:-32] assert len(ephem_pubkey) + len(iv) + len(ciphertext) + len(tag) \ == len(data) cipher = pyelliptic.Cipher(enc_key, iv, 0, self.CIPHERNAME) msg = cipher.ciphering(ciphertext) assert len(msg) == len(ciphertext) return msg
def encrypt(cls, data, raw_public_key, shared_mac_data=""): """ ECIES Encrypt, where P = recipient public 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 currently used by go: ECIES_AES128_SHA256 = &ECIESParams{ Hash: sha256.New, hashAlgo: crypto.SHA256, Cipher: aes.NewCipher, BlockSize: aes.BlockSize, KeyLen: 16, } :param data: data to encrypt :param raw_public_key: public key used of encryption :param shared_mac_data: shared mac :return: encrypted data """ if not data: raise ValueError("Data is required") if len(raw_public_key) != eth_common_constants.PUBLIC_KEY_LEN: raise ValueError( "Public key of len {0} is expected but was {1}".format( eth_common_constants.PUBLIC_KEY_LEN, len(raw_public_key))) # 1) generate r = random value ephem = ECCx() # 2) generate shared-secret = kdf( ecdhAgree(r, P) ) pubkey_x = raw_public_key[:eth_common_constants.PUBLIC_KEY_X_Y_LEN] pubkey_y = raw_public_key[eth_common_constants.PUBLIC_KEY_X_Y_LEN:] key_material = ephem.raw_get_ecdh_key(pubkey_x=pubkey_x, pubkey_y=pubkey_y) key = crypto_utils.ecies_kdf(key_material, eth_common_constants.SHARED_KEY_LEN) assert len(key) == eth_common_constants.SHARED_KEY_LEN key_enc, key_mac = key[:eth_common_constants.ENC_KEY_LEN], key[ eth_common_constants.ENC_KEY_LEN:] key_mac = hashlib.sha256(key_mac).digest() # 3) generate R = rG [same op as generating a public key] ephem_pubkey = ephem.get_raw_public_key() # encrypt iv = pyelliptic.Cipher.gen_IV(eth_common_constants.ECIES_CIPHER_NAME) assert len(iv) == eth_common_constants.IV_LEN ctx = pyelliptic.Cipher(key_enc, iv, eth_common_constants.CIPHER_ENCRYPT_DO, eth_common_constants.ECIES_CIPHER_NAME) cipher_text = ctx.ciphering(data) assert len(cipher_text) == len(data) # 4) send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag msg = rlp_utils.ascii_chr(eth_common_constants.ECIES_HEADER ) + ephem_pubkey + iv + cipher_text # the MAC of a message (called the tag) as per SEC 1, 3.5. tag = pyelliptic.hmac_sha256( key_mac, msg[eth_common_constants.ECIES_HEADER_LEN + eth_common_constants.PUBLIC_KEY_LEN:] + rlp_utils.str_to_bytes(shared_mac_data)) assert len(tag) == eth_common_constants.MAC_LEN msg += tag assert len( msg) - eth_common_constants.ECIES_ENCRYPT_OVERHEAD_LENGTH == len( data) return msg
def decrypt(self, data, shared_mac_data=b""): """ Decrypt data with ECIES method using the local private key ECIES Decrypt (performed by recipient): 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) 2) verify tag 3) decrypt ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R) [where R = r*G, and recipientPublic = recipientPrivate*G] :param data: data to decrypt :param shared_mac_data: shared mac :return: decrypted data """ if not data: raise ValueError("Data is required") if data[:eth_common_constants. ECIES_HEADER_LEN] != eth_common_constants.ECIES_HEADER_BYTE: raise DecryptionError("Wrong ECIES header") # 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) shared = data[eth_common_constants. ECIES_HEADER_LEN:eth_common_constants.ECIES_HEADER_LEN + eth_common_constants.PUBLIC_KEY_LEN] shared_pubkey_x = shared[:eth_common_constants.PUBLIC_KEY_X_Y_LEN] shared_pubkey_y = shared[eth_common_constants.PUBLIC_KEY_X_Y_LEN:] key_material = self.raw_get_ecdh_key(pubkey_x=shared_pubkey_x, pubkey_y=shared_pubkey_y) assert len(key_material) == eth_common_constants.KEY_MATERIAL_LEN key = crypto_utils.ecies_kdf(key_material, eth_common_constants.SHARED_KEY_LEN) assert len(key) == eth_common_constants.SHARED_KEY_LEN key_enc, key_mac = key[:eth_common_constants.ENC_KEY_LEN], key[ eth_common_constants.ENC_KEY_LEN:] key_mac = hashlib.sha256(key_mac).digest() assert len(key_mac) == eth_common_constants.MAC_LEN tag = data[-eth_common_constants.MAC_LEN:] assert len(tag) == eth_common_constants.MAC_LEN # 2) verify tag header_len = eth_common_constants.ECIES_HEADER_LEN + eth_common_constants.PUBLIC_KEY_LEN mac_data = data[header_len:- eth_common_constants.MAC_LEN] + \ shared_mac_data if not pyelliptic.equals(pyelliptic.hmac_sha256(key_mac, mac_data), tag): raise DecryptionError("Fail to verify data") # 3) decrypt block_size = pyelliptic.OpenSSL.get_cipher( eth_common_constants.ECIES_CIPHER_NAME).get_blocksize() iv = data[header_len:header_len + block_size] cipher_text = data[header_len + block_size:-eth_common_constants.MAC_LEN] assert eth_common_constants.ECIES_HEADER_LEN + len(shared) + len( iv) + len(cipher_text) + len(tag) == len(data) ctx = pyelliptic.Cipher(key_enc, iv, eth_common_constants.CIPHER_DECRYPT_DO, eth_common_constants.ECIES_CIPHER_NAME) return ctx.ciphering(cipher_text)