ABE multi-authority: adaptor dabenc_adapt_hybrid.py

85 views
Skip to first unread message

Marcos Portnoi

unread,
Nov 16, 2016, 1:55:52 PM11/16/16
to Charm-Crypto Help
In the module dabenc_adapt_hybrid.py, the encryption process of a plaintext consists of picking a random value, hashing it to produce the secret symmetric key that will produce the ciphertext. The symmetric key is then encrypted with ABE in encrypt-then-mac mode.

From the file, we assume the key is hashed using SHA1 algorithm:

from charm.core.math.pairing import hashPair as sha1

However, by studying the charm.core.math.pairing library, in particular file pairingmodule.c and the cascade of functions called by hashPair, we see that in fact SHA256 is being utilized, and not SHA1. The relevant piece of code finally called by hashPair is this:

int hash_to_bytes(uint8_t *input_buf, int input_len, uint8_t *output_buf, int hash_len, uint8_t hash_prefix)
{
 SHA256_CTX sha2
;
 
int i, new_input_len = input_len + 2; // extra byte for prefix
 uint8_t first_block
= 0;
 uint8_t new_input
[new_input_len+1];
// printf("orig input => \n");
// printf_buffer_as_hex(input_buf, input_len);


 memset
(new_input, 0, new_input_len+1);
 new_input
[0] = first_block; // block number (always 0 by default)
 new_input
[1] = hash_prefix; // set hash prefix
 memcpy
((uint8_t *)(new_input+2), input_buf, input_len); // copy input bytes


// printf("new input => \n");
// printf_buffer_as_hex(new_input, new_input_len);
 
// prepare output buf
 memset
(output_buf, 0, hash_len);


 
if (hash_len <= HASH_LEN) {
 SHA256_Init
(&sha2);
 SHA256_Update
(&sha2, new_input, new_input_len);
 uint8_t md
[HASH_LEN+1];
 SHA256_Final
(md, &sha2);
 memcpy
(output_buf, md, hash_len);
 
}

...

It seems that the description within file pairingmodule.c for hashPair is misleading, as seen in this PyMethodDef object at the end of the file:

PyMethodDef pairing_methods[] = {
 
{"init", (PyCFunction)Element_elem, METH_VARARGS, "Create an element in group Zr and optionally set value."},
 
{"pair", (PyCFunction)Apply_pairing, METH_VARARGS, "Apply pairing between an element of G1 and G2 and returns an element mapped to GT"},
 
{"hashPair", (PyCFunction)sha2_hash, METH_VARARGS, "Compute a sha1 hash of an element type"},
 
{"H", (PyCFunction)Element_hash, METH_VARARGS, "Hash an element type to a specific field: Zr, G1, or G2"},
 
{"random", (PyCFunction)Element_random, METH_VARARGS, "Return a random element in a specific group: G1, G2, Zr"},
 
{"serialize", (PyCFunction)Serialize_cmp, METH_VARARGS, "Serialize an element type into bytes."},
 
{"deserialize", (PyCFunction)Deserialize_cmp, METH_VARARGS, "De-serialize an bytes object into an element object"},
 
{"ismember", (PyCFunction) Group_Check, METH_VARARGS, "Group membership test for element objects."},
 
{"order", (PyCFunction) Get_Order, METH_VARARGS, "Get the group order for a particular field."},

...
 
as in fact SHA256 is being utilized, and not SHA1.

In module charm.toolbox.symcrypto for Authenticated Encryption, the HMAC uses SHA1 from Python's hashlib module to generate the authenticating MAC after encryption, and that is the only option for HMAC algorithm. Modifying this module to allow for the use of SHA256 or other hashes from library hashlib would be simple and we would gain a few more years of better security by using SHA256 instead of SHA1.

from hashlib import sha1 as sha1hashlib
import hmac

class MessageAuthenticator(object):
   
""" Abstraction for constructing and verifying authenticated messages
       
        A large number of the schemes can only encrypt group elements
        and do not provide an efficient mechanism for encoding byte in
        those elements. As such we don't pick a symmetric key and encrypt
        it asymmetrically. Rather, we hash a random group element to get the
        symmetric key.


    >>> from charm.toolbox.pairinggroup import PairingGroup,GT
    >>> from charm.core.math.pairing import hashPair as extractor
    >>> groupObj = PairingGroup('SS512')
    >>> key = groupObj.random(GT)
    >>> m = MessageAuthenticator(extractor(key))
    >>> AuthenticatedMessage = m.mac('Hello World')
    >>> m.verify(AuthenticatedMessage)
    True
    """

   
def __init__(self,key, alg = "HMAC_SHA1"):
       
"""
        Creates a message authenticator and verifier under the specified key
        """

       
if alg != "HMAC_SHA1":
           
raise ValueError("Currently only HMAC_SHA1 is supported as an algorithm")
       
self._algorithm = alg
       
self._key = key    
   
def mac(self,msg):
       
"""
        authenticates a message
        """

       
return {
               
"alg": self._algorithm,
               
"msg": msg,
               
"digest": hmac.new(self._key,bytes(self._algorithm + msg,"utf-8"),digestmod=sha1hashlib).hexdigest()
               
}



J Ayo Akinyele

unread,
Nov 16, 2016, 7:18:28 PM11/16/16
to Charm-Crypto Help
Fixed in the repo. Thanks!

Marcos Portnoi

unread,
Nov 18, 2016, 7:34:25 PM11/18/16
to Charm-Crypto Help
May I suggest this modification of symcrypto.py, such that SHA256 is available for the HMAC and it is selected by default. The whole code for the module is below:


from charm.core.math.pairing import hashPair as sha1
from charm.toolbox.paddingschemes import PKCS7Padding
from charm.toolbox.securerandom import OpenSSLRand
from charm.core.crypto.cryptobase import MODE_CBC,AES,selectPRP
from hashlib import sha1 as sha1hashlib, sha256 as sha256hashlib
from math import ceil
import json
import hmac
from base64 import b64encode,b64decode


class MessageAuthenticator(object):
   
""" Abstraction for constructing and verifying authenticated messages
       
        A large number of the schemes can only encrypt group elements
        and do not provide an efficient mechanism for encoding byte in
        those elements. As such we don't pick a symmetric key and encrypt
        it asymmetrically. Rather, we hash a random group element to get the
        symmetric key.


    >>> from charm.toolbox.pairinggroup import PairingGroup,GT
    >>> from charm.core.math.pairing import hashPair as extractor
    >>> groupObj = PairingGroup('SS512')
    >>> key = groupObj.random(GT)
    >>> m = MessageAuthenticator(extractor(key))
    >>> AuthenticatedMessage = m.mac('Hello World')
    >>> m.verify(AuthenticatedMessage)
    True
    """

#    def __init__(self,key, alg = "HMAC_SHA1"):
   
def __init__(self,key, alg = "HMAC_SHA256"):

       
"""
        Creates a message authenticator and verifier under the specified key
        """

#        if alg not in ["HMAC_SHA1", "HMAC_SHA256"]:
#            raise ValueError("Currently only HMAC_SHA1 and HMAC_SHA256 are supported as algorithms")
        hmacAlgorithms
= {'HMAC_SHA1':sha1hashlib, 'HMAC_SHA256':sha256hashlib}
       
self._errorMessage = "Currently only [" + ', '.join(list(hmacAlgorithms.keys())) + "] are supported as algorithms"        
       
if alg not in hmacAlgorithms.keys():
           
raise ValueError(self._errorMessage)
       
self._hashFunction = hmacAlgorithms[alg]

       
self._algorithm = alg
       
self._key = key    
   
def mac(self,msg):
       
"""
        authenticates a message
        """

       
return {
               
"alg": self._algorithm,
               
"msg": msg,

               
"digest": hmac.new(self._key,bytes(self._algorithm + msg,"utf-8"),digestmod=self._hashFunction).hexdigest()
               
}


   
def verify(self,msgAndDigest):
       
"""
        verifies the result returned by mac
        """

       
if msgAndDigest['alg'] != self._algorithm:
           
raise ValueError(self._errorMessage)
        expected
= bytes(self.mac(msgAndDigest['msg'])['digest'],'utf-8')
        received
= bytes(msgAndDigest['digest'],'utf-8')
       
return self._hashFunction(expected).digest() == self._hashFunction(received).digest() # we compare the hash instead of the direct value to avoid a timing attack


class SymmetricCryptoAbstraction(object):
   
"""
    Abstraction for symmetric encryption and decryption of data.
    Ideally provide an INDCCA2 secure symmetric container for arbitrary data.
    Currently only supports primitives that JSON can encode and decode.

 
    A large number of the schemes can only encrypt group elements
    and do not provide an efficient mechanism for encoding byte in
    those elements. As such we don't pick a symmetric key and encrypt
    it asymmetrically. Rather, we hash a random group element to get the
    symmetric key.


    >>> from charm.toolbox.pairinggroup import PairingGroup,GT
    >>> groupObj = PairingGroup('SS512')
    >>> from charm.core.math.pairing import hashPair as extractor
    >>> a = SymmetricCryptoAbstraction(extractor(groupObj.random(GT)))
    >>> ct = a.encrypt(b"
Friendly Fire Isn't")
    >>> a.decrypt(ct)
    b"Friendly Fire Isn'
t"
    """



   
def __init__(self,key, alg = AES, mode = MODE_CBC):
       
self._alg = alg
       
self.key_len = 16
       
self._block_size = 16
       
self._mode = mode
       
self._key = key[0:self.key_len]
       
self._padding = PKCS7Padding();
 
   
def _initCipher(self,IV = None):
       
if IV == None :
            IV
=  OpenSSLRand().getRandomBytes(self._block_size)
       
self._IV = IV
       
return selectPRP(self._alg,(self._key,self._mode,self._IV))


   
def __encode_decode(self,data,func):
        data
['IV'] = func(data['IV'])
        data
['CipherText'] = func(data['CipherText'])
       
return data


   
#This code should be factored out into  another class
   
#Because json is only defined over strings, we need to base64 encode the encrypted data
   
# and convert the base 64 byte array into a utf8 string
   
def _encode(self,data):
       
return self.__encode_decode(data,lambda x:b64encode(x).decode('utf-8'))


   
def _decode(self,data):
       
return self.__encode_decode(data,lambda x:b64decode(bytes(x,'utf-8')))


   
def encrypt(self, message):
       
#This should be removed when all crypto functions deal with bytes"
       
if type(message) != bytes :
            message
= bytes(message,"utf-8")
        ct
= self._encrypt(message)
       
#JSON strings cannot have binary data in them, so we must base64 encode  cipher
        cte
= json.dumps(self._encode(ct))
       
return cte


   
def _encrypt(self,message):
       
#Because the IV cannot be set after instantiation, decrypt and encrypt
       
# must operate on their own instances of the cipher
        cipher
= self._initCipher()
        ct
= {'ALG':self._alg,
           
'MODE':self._mode,
           
'IV':self._IV,
           
'CipherText':cipher.encrypt(self._padding.encode(message))
           
}
       
return ct


   
def decrypt(self,cipherText):
        f
= json.loads(cipherText)
       
return self._decrypt(self._decode(f)) #.decode("utf-8")


   
def _decrypt(self,cipherText):
        cipher
= self._initCipher(cipherText['IV'])
        msg
= cipher.decrypt(cipherText['CipherText'])
       
return self._padding.decode(msg)
       
class AuthenticatedCryptoAbstraction(SymmetricCryptoAbstraction):
   
def encrypt(self,msg):
       
#mac = MessageAuthenticator(sha1hashlib(b'Poor Mans Key Extractor'+self._key).digest()) # warning only valid in the random oracle
        mac
= MessageAuthenticator(sha256hashlib(b'Poor Mans Key Extractor'+self._key).digest()) # warning only valid in the random oracle
        enc
= super(AuthenticatedCryptoAbstraction,self).encrypt(msg)
       
return mac.mac(enc)


   
def decrypt(self,cipherText):
       
#mac = MessageAuthenticator(sha1hashlib(b'Poor Mans Key Extractor'+self._key).digest()) # warning only valid in the random oracle
        mac
= MessageAuthenticator(sha256hashlib(b'Poor Mans Key Extractor'+self._key).digest()) # warning only valid in the random oracle
       
if not  mac.verify(cipherText):
           
raise ValueError("Invalid mac. Your data was tampered with or your key is wrong")
       
else:
           
return super(AuthenticatedCryptoAbstraction,self).decrypt(cipherText['msg'])

Reply all
Reply to author
Forward
0 new messages