Ticket Created
over 5 years ago

WERETECH-7531

Cryptography module not behaving as expected?

Hi all,

I am trying to perform a Diffie Hellman key exchange with a device using the new BLE APIs in the 3.1 beta 3 release of the SDK. However, the KeyPair generation and createPublicKey method in the Cryptography module is not working as expected. Disclaimer though: I am only mildly familiar with cryptography, so this might be an issue as to which algorithm is being used rather than an issue with my implementation. 

The device supports the "FIPS P-256 Elliptic Curve" algorithm (quoted because that came directly from the datasheet). Connect IQ supports the "256-bit secp256r1 Elliptic Curve" algorithm (this came directly from the API docs). My understanding from this this page from IBM is that those are different names for the same curve (please correct me if I am wrong on this). The problem occurs when I send and subsequently receive the public keys to and from the device.  After receiving the key, the device sends an error message and immediately disconnects. Additionally, the key that I receive (before it disconnects) is handled by createPublicKey(bytes), which throws the following exception:

Exception: The public key provided is not valid for the selected algorithm

So I started doing some digging. I used OpenSSL to generate a key using the following commands:

openssl ecparam -genkey -name secp256r1 -out key.pem
openssl ec -in key.pem -text -noout
Sample output of this process is 
Private-Key: (256 bit)
priv:
1d:41:5f:23:2d:af:51:1b:e0:7c:88:5e:fe:1b:d2:
ad:e0:89:c0:fe:05:ff:93:1e:4c:a6:9e:f9:79:13:
ba:b0
pub:
04:31:e6:8e:38:9d:e5:ad:15:01:d1:ac:53:1a:f2:
a7:15:f9:0e:c8:6d:46:6d:69:73:94:b0:50:79:a0:
02:f2:a8:15:86:0c:92:64:36:ae:4f:42:40:63:ef:
9c:12:c4:85:07:23:b2:1b:bd:6a:d7:4e:2d:53:f1:
aa:86:7e:3a:be
ASN1 OID: prime256v1
NIST CURVE: P-256

I created a python script to call these commands and print the private and public keys as hex strings that Connect IQ understands, such as: [0x1d, 0x41, 0x5f, 0x23, 0x2d, 0xaf, 0x51, 0x1b, 0xe0, 0x7c, 0x88, 0x5e, 0xfe, 0x1b, 0xd2, 0xad, 0xe0, 0x89, 0xc0, 0xfe, 0x05, 0xff, 0x93, 0x1e, 0x4c, 0xa6, 0x9e, 0xf9, 0x79, 0x13, 0xba, 0xb0]b for the private key above. I then pasted these into a simple test case in Connect IQ, shown below: (note, the leading 0x04 of the public key from OpenSSL above is removed because it denotes an uncompressed key, which Connect IQ doesn't seem to care about/distinguish)

(:test)
function test_key_generation(logger) {
    var expected = [0x31, 0xe6, 0x8e, 0x38, 0x9d, 0xe5, 0xad, 0x15, 0x01, 0xd1, 0xac, 0x53, 0x1a, 0xf2, 0xa7, 0x15, 0xf9, 0x0e, 0xc8, 0x6d, 0x46, 0x6d, 0x69, 0x73, 0x94, 0xb0, 0x50, 0x79, 0xa0, 0x02, 0xf2, 0xa8, 0x15, 0x86, 0x0c, 0x92, 0x64, 0x36, 0xae, 0x4f, 0x42, 0x40, 0x63, 0xef, 0x9c, 0x12, 0xc4, 0x85, 0x07, 0x23, 0xb2, 0x1b, 0xbd, 0x6a, 0xd7, 0x4e, 0x2d, 0x53, 0xf1, 0xaa, 0x86, 0x7e, 0x3a, 0xbe]b;
    var privKey = [0x1d, 0x41, 0x5f, 0x23, 0x2d, 0xaf, 0x51, 0x1b, 0xe0, 0x7c, 0x88, 0x5e, 0xfe, 0x1b, 0xd2, 0xad, 0xe0, 0x89, 0xc0, 0xfe, 0x05, 0xff, 0x93, 0x1e, 0x4c, 0xa6, 0x9e, 0xf9, 0x79, 0x13, 0xba, 0xb0]b;
    var keyPair = new Crypto.KeyPair({:algorithm => Crypto.KEY_PAIR_ELLIPTIC_CURVE_SECP256R1, :privateKey => privKey});
    logger.debug("Public key: " + hexString(keyPair.getPublicKey().getBytes(), 1));
    return keyPair.getPublicKey().getBytes().equals(expected);
}

And it fails to generate the expected key. I went a little further, and used the pycryptodome library in python to test and see if the key that was generated by Connect IQ was valid:

from Crypto.PublicKey import ECC

# input format is '0xde, 0xad, 0xbe, 0xef...'
# or '0xde 0xad 0xbe 0xef...', either is okay
keystr = input('public key = ').replace(',', '')

keyarr = [entry[2:] for entry in keystr.split(' ')]
print(len(keyarr))
xstr = ''.join(keyarr[:32])
x = int(xstr, 16)
ystr = ''.join(keyarr[32:])
y = int(ystr, 16)

try:
    key = ECC.construct(curve='p256', point_x=x, point_y=y)
    print('Key successfully parsed')
except Exception as e:
    print('Error parsing key:')
    print(e)

There's no issue when I run this script with keys generated by OpenSSL, but when using the keys output from Connect IQ, I get the error:

Error parsing key: The EC point does not belong to the curve

That is interesting, to say the least. I tried it with the keys that I get back from the device, and those also work fine in the script.

[TL;DR]

The cryptography module isn't producing the expected output.

Do "FIPS P-256 Elliptic Curve" and "256-bit secp256r1 Elliptic Curve" actually refer to two completely different algorithms? Is my implementation/usage of the Cryptography module incorrect? Or is this a bug in the latest Connect IQ SDK? 

Thanks

Former Member
Former Member
  • Former Member
    Former Member over 5 years ago in reply to Travis.ConnectIQ

    Hi Travis,

    Thanks for looking into this for me. It makes sense why this error is occurring now, as endianness definitely affects how the key is interpreted. With the byte swapping I got it working - just please update here if a fix is issued. Smile

  • Okay, I've figured it out.

    It appears that the issue is that OpenSSL seems to generate its output as big-endian format, but we expect the data to be in local-endian (i.e., little-endian). So, for this to work the private key byte array needs to be reversed, and the public key array needs to be split in half, each half reversed, and then merged back into one.

    I was able to generate a secp256r1 key pair in OpenSSL, byte swap the values using scripts, and verify that everything checked out correctly. Here is my test code.

    using Toybox.Cryptography;
    
    (:test)
    module Crypto {
    
        function hexString(byteArray) {
            var s = "";
            
            var n = byteArray.size();
            if (n != 0) {
                s += byteArray[0].format("%02x");
                for (var i = 1; i < n; ++i) {
                    s += ":";
                    s += byteArray[i].format("%02x");
                }
            }
            
            return s;
        }
    
        //
        // secp224r1
        //
    
        // [ 8:58:10.69] C:\Users\vitek\Desktop>openssl.exe ecparam -genkey -name secp224r1 -out key.pem
        //
        // [11:25:24.81] C:\Users\vitek\Desktop>openssl.exe ec -in key.pem -text -noout
        //read EC key
        //Private-Key: (224 bit)
        //priv:
        //    00:89:01:46:f8:bd:64:ce:75:e0:83:02:d0:fc:e1:
        //    1d:ce:fd:eb:66:f8:81:1d:68:64:49:05:d3:ee
        //pub:
        //    04:1d:ba:39:9a:16:6e:62:0b:56:e3:16:73:f7:38:
        //    b0:d1:b7:2d:40:ca:92:3a:f8:94:26:24:22:e6:6f:
        //    d7:61:db:e8:9b:47:03:33:da:46:0e:6b:36:c9:34:
        //    a6:75:6d:d1:10:9f:c2:d7:c6:07:72:bc
        //ASN1 OID: secp224r1
        //NIST CURVE: P-224
        const PRIVATE_KEY_OPENSSL_SECP224R1 = [
            //0x00,
            //0x89, 0x01, 0x46, 0xf8, 0xbd, 0x64, 0xce, 0x75, 0xe0, 0x83, 0x02, 0xd0, 0xfc, 0xe1, 0x1d, 0xce, 0xfd, 0xeb, 0x66, 0xf8, 0x81, 0x1d, 0x68, 0x64, 0x49, 0x05, 0xd3, 0xee,
            
            // reversed
            0xee, 0xd3, 0x05, 0x49, 0x64, 0x68, 0x1d, 0x81, 0xf8, 0x66, 0xeb, 0xfd, 0xce, 0x1d, 0xe1, 0xfc, 0xd0, 0x02, 0x83, 0xe0, 0x75, 0xce, 0x64, 0xbd, 0xf8, 0x46, 0x01, 0x89,
        ]b;
    
        const PUBLIC_KEY_OPENSSL_SECP224R1 = [
            //0x04,
            //0x1d, 0xba, 0x39, 0x9a, 0x16, 0x6e, 0x62, 0x0b, 0x56, 0xe3, 0x16, 0x73, 0xf7, 0x38, 0xb0, 0xd1, 0xb7, 0x2d, 0x40, 0xca, 0x92, 0x3a, 0xf8, 0x94, 0x26, 0x24, 0x22, 0xe6,
            //0x6f, 0xd7, 0x61, 0xdb, 0xe8, 0x9b, 0x47, 0x03, 0x33, 0xda, 0x46, 0x0e, 0x6b, 0x36, 0xc9, 0x34, 0xa6, 0x75, 0x6d, 0xd1, 0x10, 0x9f, 0xc2, 0xd7, 0xc6, 0x07, 0x72, 0xbc,
            
            // reversed
            //0x04,
            0xe6, 0x22, 0x24, 0x26, 0x94, 0xf8, 0x3a, 0x92, 0xca, 0x40, 0x2d, 0xb7, 0xd1, 0xb0, 0x38, 0xf7, 0x73, 0x16, 0xe3, 0x56, 0x0b, 0x62, 0x6e, 0x16, 0x9a, 0x39, 0xba, 0x1d,
            0xbc, 0x72, 0x07, 0xc6, 0xd7, 0xc2, 0x9f, 0x10, 0xd1, 0x6d, 0x75, 0xa6, 0x34, 0xc9, 0x36, 0x6b, 0x0e, 0x46, 0xda, 0x33, 0x03, 0x47, 0x9b, 0xe8, 0xdb, 0x61, 0xd7, 0x6f,
        ]b;
    
        //
        // secp256r1
        //
    
        // generated from openSSL
        // [22:42:43.44] C:\Users\vitek\Desktop>openssl.exe ecparam -genkey -name secp256r1 -out key.pem
        //using curve name prime256v1 instead of secp256r1
        //
        // [ 8:57:45.39] C:\Users\vitek\Desktop>openssl.exe ec -in key.pem -text -noout
        //read EC key
        //Private-Key: (256 bit)
        //priv:
        //    00:dd:0b:81:0c:6e:7f:67:4a:ba:8e:a5:2f:de:4b:
        //    f4:56:a7:e7:6f:26:63:c3:63:b1:19:06:40:e7:2e:
        //    a2:88:45
        //pub:
        //    04:0b:4e:f6:b0:b7:59:30:64:74:10:b2:13:91:21:
        //    84:4b:4d:2d:bc:18:01:9c:49:85:28:97:67:f4:ea:
        //    73:7c:5f:54:29:1b:9f:85:fe:d2:f5:d4:d9:a5:e1:
        //    c7:ef:9e:d1:09:98:59:a1:f5:93:d0:5f:8f:1f:4f:
        //    43:10:54:f4:11
        //ASN1 OID: prime256v1
        //NIST CURVE: P-256
        const PRIVATE_KEY_OPENSSL_SECP256R1 = [
            //0x00,
            //0xdd, 0x0b, 0x81, 0x0c, 0x6e, 0x7f, 0x67, 0x4a, 0xba, 0x8e, 0xa5, 0x2f, 0xde, 0x4b, 0xf4, 0x56, 0xa7, 0xe7, 0x6f, 0x26, 0x63, 0xc3, 0x63, 0xb1, 0x19, 0x06, 0x40, 0xe7, 0x2e, 0xa2, 0x88, 0x45,
    
            // reversed
            //0x00,
            0x45, 0x88, 0xa2, 0x2e, 0xe7, 0x40, 0x06, 0x19, 0xb1, 0x63, 0xc3, 0x63, 0x26, 0x6f, 0xe7, 0xa7, 0x56, 0xf4, 0x4b, 0xde, 0x2f, 0xa5, 0x8e, 0xba, 0x4a, 0x67, 0x7f, 0x6e, 0x0c, 0x81, 0x0b, 0xdd,
        ]b;
    
        const PUBLIC_KEY_OPENSSL_SECP256R1 = [
            //0x04,
            //0x0b, 0x4e, 0xf6, 0xb0, 0xb7, 0x59, 0x30, 0x64, 0x74, 0x10, 0xb2, 0x13, 0x91, 0x21, 0x84, 0x4b, 0x4d, 0x2d, 0xbc, 0x18, 0x01, 0x9c, 0x49, 0x85, 0x28, 0x97, 0x67, 0xf4, 0xea, 0x73, 0x7c, 0x5f,
            //0x54, 0x29, 0x1b, 0x9f, 0x85, 0xfe, 0xd2, 0xf5, 0xd4, 0xd9, 0xa5, 0xe1, 0xc7, 0xef, 0x9e, 0xd1, 0x09, 0x98, 0x59, 0xa1, 0xf5, 0x93, 0xd0, 0x5f, 0x8f, 0x1f, 0x4f, 0x43, 0x10, 0x54, 0xf4, 0x11,
            
            // reversed
            //0x04,
            0x5f, 0x7c, 0x73, 0xea, 0xf4, 0x67, 0x97, 0x28, 0x85, 0x49, 0x9c, 0x01, 0x18, 0xbc, 0x2d, 0x4d, 0x4b, 0x84, 0x21, 0x91, 0x13, 0xb2, 0x10, 0x74, 0x64, 0x30, 0x59, 0xb7, 0xb0, 0xf6, 0x4e, 0x0b,
            0x11, 0xf4, 0x54, 0x10, 0x43, 0x4f, 0x1f, 0x8f, 0x5f, 0xd0, 0x93, 0xf5, 0xa1, 0x59, 0x98, 0x09, 0xd1, 0x9e, 0xef, 0xc7, 0xe1, 0xa5, 0xd9, 0xd4, 0xf5, 0xd2, 0xfe, 0x85, 0x9f, 0x1b, 0x29, 0x54,
        ]b;
    
    	function test_public_key(logger, algorithm, publicKeyBytes) {
    //		logger.debug("publicKey = [");
    //		logger.debug("    " + hexString(publicKeyBytes));
    //		logger.debug("];");
    
    		try {
    			Cryptography.createPublicKey(algorithm, publicKeyBytes);
    		}
    		catch(e) {
    			return false;
    		}
    		
    		return true;
    	}
    	
    	
    	function test_public_private_keys(logger, algorithm, publicKey, privateKey) {
    //		logger.debug("privateKey = [");
    //		logger.debug("    " + hexString(privateKey));
    //		logger.debug("];");
    //
    //		logger.debug("publicKey = [");
    //		logger.debug("    " + hexString(publicKey));
    //		logger.debug("];");
    
    	    var keyPair = new Cryptography.KeyPair({
    	    	:algorithm => algorithm,
    	    	:privateKey => privateKey
    	    });
    	    
    	    var generated_publicKey = keyPair.getPublicKey().getBytes();
    	    
    	    return generated_publicKey.equals(publicKey);
    	}
    	
    	function test_generate_keys(logger, algorithm) {
    	    var keyPair = new Cryptography.KeyPair({
    	    	:algorithm => algorithm
    	    });
    	    
    	    var generated_publicKey = keyPair.getPublicKey().getBytes();
    	    var generated_privateKey = keyPair.getPrivateKey().getBytes();
    	    
    	    logger.debug(hexString(generated_publicKey));
    	    logger.debug(hexString(generated_privateKey));
    	    
    	    return true;
    	}
    
    	//
    	// secp224r1
    	//
    	
    	(:test)
    	function test_openssl_secp224r1_public_key(logger) {
    		return test_public_key(logger,
    			Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP224R1,
    			PUBLIC_KEY_OPENSSL_SECP224R1);
    	}
    	
    	(:test)
    	function test_openssl_secp224r1_keys(logger) {
    		return test_public_private_keys(logger,
    			Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP224R1,
    			PUBLIC_KEY_OPENSSL_SECP224R1,
    			PRIVATE_KEY_OPENSSL_SECP224R1);
    	}
    	
    	(:test)
    	function test_generate_secp224r1_keys(logger) {
    		return test_generate_keys(logger, Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP224R1);
    	}
    	
    	//
    	// secp256r1
    	//
    	
    	(:test)
    	function test_openssl_secp256r1_public_key(logger) {
    		return test_public_key(logger,
    			Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP256R1,
    			PUBLIC_KEY_OPENSSL_SECP256R1);
    	}
    
    	(:test)
    	function test_openssl_secp256r1_keys(logger) {
    		return test_public_private_keys(logger,
    			Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP256R1,
    			PUBLIC_KEY_OPENSSL_SECP256R1,
    			PRIVATE_KEY_OPENSSL_SECP256R1);
    	}
    	
    	(:test)
    	function test_generate_secp256r1_keys(logger) {
    		return test_generate_keys(logger, Cryptography.KEY_PAIR_ELLIPTIC_CURVE_SECP256R1);
    	}
    }


    It seems to me that this is a mistake in our API. We should have done the necessary byte swapping internally, and just asked API users to provide bytes in big-endian as it appears that everyone else appears to do.

    I've filed an issue to verify my analysis and decide what to do about this. At the very least we should be documenting the unusual requirement.

    Travis

  • I'm able to reproduce the issue with keys generated using OpenSSL not working with our Cryptography module. I have not yet go around to trying to figure out why or trying to feed the key parts to the python script. I know we have testing for this functionality, but it is not clear to me where the inputs and expected outputs came from.