vasco.santos
vasco.santos

Reputation: 51

Web Crypto API JWK usage in Python

I am developing a P2P Infrastructure that will have data from a set of different applications, distributed through the network. This P2P overlay is composed by a set of Python Twisted Servers.

I need to guarantee the security and privacy of the stored data, for each user of each application. Consequently, I am generating pairs of RSA keys in the client side of the web app, using the Web Crypto API. The RSA key pairs will be stored in the P2P overlay as well. So, I cipher on the client side, the private keys with a derivation of the user password.

In addition, I am using jwk to pem module to convert the JWK public key into a PEM key, to be used in the Python Cryptography library (PyCrypt or m2Crypto).

Finally, I have to guarantee that the message containing those credentials, as well as the user data , maintain its integrity. Therefore, in the client side, I am signing this data with the user's private key.

I send the data, as well as the signature, both in ArrayBuffer type to the server, encoded in base64.

function signData(private_key, data, callback){

    var dataForHash = str2ab(JSON.stringify(sortObject(data)));
    computeSHA(dataForHash, "SHA-256", function(hash){


        signRSA(private_key, hash, function(data){      
            callback(data.buffer.b64encode(), dataForHash.b64encode());
        });    
    });
}

function computeSHA(data, mode, callback){
    window.crypto.subtle.digest(
        {
            name: mode,
        },
        data
    )
    .then(function(hash){
        callback(new Uint8Array(hash).buffer);
    })
    .catch(function(err){
        console.error(err);
    });
}

function signRSA(private_key, data, callback){
    window.crypto.subtle.sign(
        {
            name: "RSASSA-PKCS1-v1_5",
        },
        private_key,
        data
    )
    .then(function(signature){
        callback(new Uint8Array(signature));
    })
    .catch(function(err){
        console.error(err);
    });
}

ArrayBuffer.prototype.b64encode = function(){
    return btoa(String.fromCharCode.apply(null, new Uint8Array(this)));
};

Afterwards, when the Python Server receives this http request, it decodes data and signature from base64.

dataForHash = base64.b64decode(dataReceived['data'])
signature = base64.b64decode(dataReceived['signature'])

For validating the signature, the public key is needed. Consequently:

data = utils.byteify(json.loads(dataForHash.decode("utf-16")))
pub_key = base64.b64decode(data['pub_key']) # Get PEM Public Key

(utils.byteify() converts unicode string to regular strings)

Verifying signature:

Authentication.verifySignature(signature, dataForHash, pub_key)

Method definition:

from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA

def verifySignature(signature, data, pub_key):
    key = RSA.importKey(pub_key)
    h = SHA256.new(data)
    verifier = PKCS1_v1_5.new(key)
    return verifier.verify(h, signature)

However, the signature verification returns False. I have also tried to use the m2crypto library, but it returns 0.

Upvotes: 3

Views: 1927

Answers (1)

vasco.santos
vasco.santos

Reputation: 51

I managed to find the problem.

Although in Python (PyCrypto) the sign function should receive the hash of the data to sign, using the Web Cryptography API, the sign method applies a hash function to the received data before signing it.

Consequently, the data in JS was being hashed twice, one before invoking the sign method and one in the sign method, before creating the signature.

function signData(private_key, data, callback){

    var dataForHash = str2ab(JSON.stringify(sortObject(data)));

    signRSA(private_key, dataForHash, function(data){           

        callback(data.buffer.b64encode(), dataForHash.b64encode());
    });    
}

ArrayBuffer.prototype.b64encode = function(){
    return btoa(String.fromCharCode.apply(null, new Uint8Array(this)));
};

String.prototype.b64decode = function(){
    var binary_string = window.atob(this);
    var len = binary_string.length;
    var bytes = new Uint8Array(new ArrayBuffer(len));
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes;
};

With this modification, the verification in python returns True now.

Upvotes: 0

Related Questions